Skip to content

Commit

Permalink
Cache relative import results as well as absolute ones
Browse files Browse the repository at this point in the history
This dramatically improves the performance of a sample app using the
Carbon design system, and should generally improve apps that have a
lot of repeated @imports of library files. It's possible we can back
this out if it's not pulling its weight once we no longer support
@import.
  • Loading branch information
nex3 committed Oct 20, 2021
1 parent 6069708 commit d13a38e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 39 deletions.
61 changes: 41 additions & 20 deletions lib/src/async_import_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,31 @@ class AsyncImportCache {
///
/// This map's values are the same as the return value of [canonicalize].
///
/// This cache isn't used for relative imports, because they're
/// context-dependent.
final Map<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>
_canonicalizeCache;
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
final _canonicalizeCache =
<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>{};

/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache = <Tuple4<Uri, bool, AsyncImporter, Uri?>,
Tuple3<AsyncImporter, Uri, Uri>?>{};

/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};

/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};

/// Creates an import cache that resolves imports using [importers].
///
Expand All @@ -67,18 +82,12 @@ class AsyncImportCache {
PackageConfig? packageConfig,
Logger? logger})
: _importers = _toImporters(importers, loadPaths, packageConfig),
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();

/// Creates an import cache without any globally-available importers.
AsyncImportCache.none({Logger? logger})
: _importers = const [],
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();

/// Converts the user's [importers], [loadPaths], and [packageConfig]
/// options into a single list of importers.
Expand Down Expand Up @@ -113,12 +122,16 @@ class AsyncImportCache {
Uri? baseUrl,
bool forImport = false}) async {
if (baseImporter != null) {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl =
await _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache,
Tuple4(url, forImport, baseImporter, baseUrl), () async {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl =
await _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
});
if (relativeResult != null) return relativeResult;
}

return await putIfAbsentAsync(_canonicalizeCache, Tuple2(url, forImport),
Expand Down Expand Up @@ -236,6 +249,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed.
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove(Tuple2(url, false));
_canonicalizeCache.remove(Tuple2(url, true));

var relativeKeysToClear = [
for (var key in _relativeCanonicalizeCache.keys)
if (key.item1 == url) key
];
for (var key in relativeKeysToClear) {
_relativeCanonicalizeCache.remove(key);
}
}

/// Clears the cached parse tree for the stylesheet with the given
Expand Down
59 changes: 40 additions & 19 deletions lib/src/import_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_import_cache.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: b3b80fe96623c1579809528e46d9c75b87bf82ea
// Checksum: 3e290e40f4576be99217ddfbd7a76c4d38721af1
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -40,15 +40,30 @@ class ImportCache {
///
/// This map's values are the same as the return value of [canonicalize].
///
/// This cache isn't used for relative imports, because they're
/// context-dependent.
final Map<Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?> _canonicalizeCache;
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
final _canonicalizeCache = <Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?>{};

/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache =
<Tuple4<Uri, bool, Importer, Uri?>, Tuple3<Importer, Uri, Uri>?>{};

/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};

/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};

/// Creates an import cache that resolves imports using [importers].
///
Expand All @@ -73,18 +88,12 @@ class ImportCache {
PackageConfig? packageConfig,
Logger? logger})
: _importers = _toImporters(importers, loadPaths, packageConfig),
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();

/// Creates an import cache without any globally-available importers.
ImportCache.none({Logger? logger})
: _importers = const [],
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();

/// Converts the user's [importers], [loadPaths], and [packageConfig]
/// options into a single list of importers.
Expand Down Expand Up @@ -117,11 +126,15 @@ class ImportCache {
Tuple3<Importer, Uri, Uri>? canonicalize(Uri url,
{Importer? baseImporter, Uri? baseUrl, bool forImport = false}) {
if (baseImporter != null) {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
var relativeResult = _relativeCanonicalizeCache
.putIfAbsent(Tuple4(url, forImport, baseImporter, baseUrl), () {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
});
if (relativeResult != null) return relativeResult;
}

return _canonicalizeCache.putIfAbsent(Tuple2(url, forImport), () {
Expand Down Expand Up @@ -235,6 +248,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed.
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove(Tuple2(url, false));
_canonicalizeCache.remove(Tuple2(url, true));

var relativeKeysToClear = [
for (var key in _relativeCanonicalizeCache.keys)
if (key.item1 == url) key
];
for (var key in relativeKeysToClear) {
_relativeCanonicalizeCache.remove(key);
}
}

/// Clears the cached parse tree for the stylesheet with the given
Expand Down

0 comments on commit d13a38e

Please sign in to comment.