diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index 576094cb4..ac4c9a745 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -43,30 +43,20 @@ final class AsyncImportCache { /// The `forImport` in each key is true when this canonicalization is for an /// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule. /// - /// This cache isn't used for relative imports, because they depend on the - /// specific base importer. That's stored separately in - /// [_relativeCanonicalizeCache]. + /// This cache covers loads that go through the entire chain of [_importers], + /// but it doesn't cover individual loads or loads in which any importer + /// accesses `containingUrl`. See also [_perImporterCanonicalizeCache]. final _canonicalizeCache = <(Uri, {bool forImport}), AsyncCanonicalizeResult?>{}; - /// The canonicalized URLs for each non-canonical URL that's resolved using a - /// relative importer. + /// Like [_canonicalizeCache] but also includes the specific importer in the + /// key. /// - /// 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 = <( - Uri, { - bool forImport, - AsyncImporter baseImporter, - Uri? baseUrl - }), - AsyncCanonicalizeResult?>{}; + /// This is used to cache both relative imports from the base importer and + /// individual importer results in the case where some other component of the + /// importer chain isn't cacheable. + final _perImporterCanonicalizeCache = + <(AsyncImporter, Uri, {bool forImport}), AsyncCanonicalizeResult?>{}; /// The parsed stylesheets for each canonicalized import URL. final _importCache = {}; @@ -154,14 +144,11 @@ final class AsyncImportCache { } if (baseImporter != null && url.scheme == '') { - var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache, ( - url, - forImport: forImport, - baseImporter: baseImporter, - baseUrl: baseUrl - ), () async { - var (result, cacheable) = await _canonicalize( - baseImporter, baseUrl?.resolveUri(url) ?? url, baseUrl, forImport); + var resolvedUrl = baseUrl?.resolveUri(url) ?? url; + var relativeResult = await putIfAbsentAsync(_perImporterCanonicalizeCache, + (baseImporter, resolvedUrl, forImport: forImport), () async { + var (result, cacheable) = + await _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport); assert( cacheable, "Relative loads should always be cacheable because they never " @@ -181,17 +168,34 @@ final class AsyncImportCache { // `canonicalize()` calls we've attempted are cacheable. Only if they are do // we store the result in the cache. var cacheable = true; - for (var importer in _importers) { + for (var i = 0; i < _importers.length; i++) { + var importer = _importers[i]; switch (await _canonicalize(importer, url, baseUrl, forImport)) { case (var result?, true) when cacheable: _canonicalizeCache[key] = result; return result; - case (var result?, _): - return result; - - case (_, false): - cacheable = false; + case (var result, true) when !cacheable: + _perImporterCanonicalizeCache[( + importer, + url, + {forImport: forImport} + )] = result; + if (result != null) return result; + + case (var result, false): + if (cacheable) { + // If this is the first uncacheable result, add all previous results + // to the per-importer cache so we don't have to re-run them for + // future uses of this importer. + var perImporterKey = (_importers[j], url, {forImport: forImport}); + for (var j = 0; i < j; j++) { + _perImporterCanonicalizeCache[perImporterKey] = null; + } + cacheable = false; + } + + if (result != null) return result; } } diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index 6204971e4..3cd53f58f 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 37dd173d676ec6cf201a25b3cca9ac81d92b1433 +// Checksum: cee1a9f159b153663ae361b862e66fe15b38518a // // ignore_for_file: unused_import @@ -46,29 +46,19 @@ final class ImportCache { /// The `forImport` in each key is true when this canonicalization is for an /// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule. /// - /// This cache isn't used for relative imports, because they depend on the - /// specific base importer. That's stored separately in - /// [_relativeCanonicalizeCache]. + /// This cache covers loads that go through the entire chain of [_importers], + /// but it doesn't cover individual loads or loads in which any importer + /// accesses `containingUrl`. See also [_perImporterCanonicalizeCache]. final _canonicalizeCache = <(Uri, {bool forImport}), CanonicalizeResult?>{}; - /// The canonicalized URLs for each non-canonical URL that's resolved using a - /// relative importer. + /// Like [_canonicalizeCache] but also includes the specific importer in the + /// key. /// - /// 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 = <( - Uri, { - bool forImport, - Importer baseImporter, - Uri? baseUrl - }), - CanonicalizeResult?>{}; + /// This is used to cache both relative imports from the base importer and + /// individual importer results in the case where some other component of the + /// importer chain isn't cacheable. + final _perImporterCanonicalizeCache = + <(Importer, Uri, {bool forImport}), CanonicalizeResult?>{}; /// The parsed stylesheets for each canonicalized import URL. final _importCache = {}; @@ -154,14 +144,11 @@ final class ImportCache { } if (baseImporter != null && url.scheme == '') { - var relativeResult = _relativeCanonicalizeCache.putIfAbsent(( - url, - forImport: forImport, - baseImporter: baseImporter, - baseUrl: baseUrl - ), () { - var (result, cacheable) = _canonicalize( - baseImporter, baseUrl?.resolveUri(url) ?? url, baseUrl, forImport); + var resolvedUrl = baseUrl?.resolveUri(url) ?? url; + var relativeResult = _perImporterCanonicalizeCache + .putIfAbsent((baseImporter, resolvedUrl, forImport: forImport), () { + var (result, cacheable) = + _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport); assert( cacheable, "Relative loads should always be cacheable because they never " @@ -181,17 +168,34 @@ final class ImportCache { // `canonicalize()` calls we've attempted are cacheable. Only if they are do // we store the result in the cache. var cacheable = true; - for (var importer in _importers) { + for (var i = 0; i < _importers.length; i++) { + var importer = _importers[i]; switch (_canonicalize(importer, url, baseUrl, forImport)) { case (var result?, true) when cacheable: _canonicalizeCache[key] = result; return result; - case (var result?, _): - return result; - - case (_, false): - cacheable = false; + case (var result, true) when !cacheable: + _perImporterCanonicalizeCache[( + importer, + url, + {forImport: forImport} + )] = result; + if (result != null) return result; + + case (var result, false): + if (cacheable) { + // If this is the first uncacheable result, add all previous results + // to the per-importer cache so we don't have to re-run them for + // future uses of this importer. + var perImporterKey = (_importers[j], url, {forImport: forImport}); + for (var j = 0; i < j; j++) { + _perImporterCanonicalizeCache[perImporterKey] = null; + } + cacheable = false; + } + + if (result != null) return result; } }