@@ -11,10 +11,12 @@ import 'package:path/path.dart' as p;
11
11
import 'ast/sass.dart' ;
12
12
import 'deprecation.dart' ;
13
13
import 'importer.dart' ;
14
+ import 'importer/canonicalize_context.dart' ;
14
15
import 'importer/no_op.dart' ;
15
16
import 'importer/utils.dart' ;
16
17
import 'io.dart' ;
17
18
import 'logger.dart' ;
19
+ import 'util/map.dart' ;
18
20
import 'util/nullable.dart' ;
19
21
import 'utils.dart' ;
20
22
@@ -43,30 +45,28 @@ final class AsyncImportCache {
43
45
/// The `forImport` in each key is true when this canonicalization is for an
44
46
/// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule.
45
47
///
46
- /// This cache isn't used for relative imports, because they depend on the
47
- /// specific base importer. That's stored separately in
48
- /// [_relativeCanonicalizeCache ] .
48
+ /// This cache covers loads that go through the entire chain of [_importers] ,
49
+ /// but it doesn't cover individual loads or loads in which any importer
50
+ /// accesses `containingUrl` . See also [_perImporterCanonicalizeCache ] .
49
51
final _canonicalizeCache =
50
52
< (Uri , {bool forImport}), AsyncCanonicalizeResult ? > {};
51
53
52
- /// The canonicalized URLs for each non-canonical URL that's resolved using a
53
- /// relative importer .
54
+ /// Like [_canonicalizeCache] but also includes the specific importer in the
55
+ /// key .
54
56
///
55
- /// The map's keys have four parts:
57
+ /// This is used to cache both relative imports from the base importer and
58
+ /// individual importer results in the case where some other component of the
59
+ /// importer chain isn't cacheable.
60
+ final _perImporterCanonicalizeCache =
61
+ < (AsyncImporter , Uri , {bool forImport}), AsyncCanonicalizeResult ? > {};
62
+
63
+ /// A map from the keys in [_perImporterCanonicalizeCache] that are generated
64
+ /// for relative URL loads agains the base importer to the original relative
65
+ /// URLs what were loaded.
56
66
///
57
- /// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache] ).
58
- /// 2. Whether the canonicalization is for an `@import` rule.
59
- /// 3. The `baseImporter` passed to [canonicalize] .
60
- /// 4. The `baseUrl` passed to [canonicalize] .
61
- ///
62
- /// The map's values are the same as the return value of [canonicalize] .
63
- final _relativeCanonicalizeCache = < (
64
- Uri , {
65
- bool forImport,
66
- AsyncImporter baseImporter,
67
- Uri ? baseUrl
68
- }),
69
- AsyncCanonicalizeResult ? > {};
67
+ /// This is used to invalidate the cache when files are changed.
68
+ final _nonCanonicalRelativeUrls =
69
+ < (AsyncImporter , Uri , {bool forImport}), Uri > {};
70
70
71
71
/// The parsed stylesheets for each canonicalized import URL.
72
72
final _importCache = < Uri , Stylesheet ? > {};
@@ -154,18 +154,17 @@ final class AsyncImportCache {
154
154
}
155
155
156
156
if (baseImporter != null && url.scheme == '' ) {
157
- var relativeResult = await putIfAbsentAsync (_relativeCanonicalizeCache, (
158
- url,
159
- forImport: forImport,
160
- baseImporter: baseImporter,
161
- baseUrl: baseUrl
162
- ), () async {
163
- var (result, cacheable) = await _canonicalize (
164
- baseImporter, baseUrl? .resolveUri (url) ?? url, baseUrl, forImport);
157
+ var resolvedUrl = baseUrl? .resolveUri (url) ?? url;
158
+ var key = (baseImporter, resolvedUrl, forImport: forImport);
159
+ var relativeResult =
160
+ await putIfAbsentAsync (_perImporterCanonicalizeCache, key, () async {
161
+ var (result, cacheable) =
162
+ await _canonicalize (baseImporter, resolvedUrl, baseUrl, forImport);
165
163
assert (
166
164
cacheable,
167
165
"Relative loads should always be cacheable because they never "
168
166
"provide access to the containing URL." );
167
+ if (baseUrl != null ) _nonCanonicalRelativeUrls[key] = url;
169
168
return result;
170
169
});
171
170
if (relativeResult != null ) return relativeResult;
@@ -181,17 +180,41 @@ final class AsyncImportCache {
181
180
// `canonicalize()` calls we've attempted are cacheable. Only if they are do
182
181
// we store the result in the cache.
183
182
var cacheable = true ;
184
- for (var importer in _importers) {
183
+ for (var i = 0 ; i < _importers.length; i++ ) {
184
+ var importer = _importers[i];
185
+ var perImporterKey = (importer, url, forImport: forImport);
186
+ switch (_perImporterCanonicalizeCache.getOption (perImporterKey)) {
187
+ case (var result? ,):
188
+ return result;
189
+ case (null ,):
190
+ continue ;
191
+ }
192
+
185
193
switch (await _canonicalize (importer, url, baseUrl, forImport)) {
186
194
case (var result? , true ) when cacheable:
187
195
_canonicalizeCache[key] = result;
188
196
return result;
189
197
190
- case (var result? , _):
191
- return result;
192
-
193
- case (_, false ):
194
- cacheable = false ;
198
+ case (var result, true ) when ! cacheable:
199
+ _perImporterCanonicalizeCache[perImporterKey] = result;
200
+ if (result != null ) return result;
201
+
202
+ case (var result, false ):
203
+ if (cacheable) {
204
+ // If this is the first uncacheable result, add all previous results
205
+ // to the per-importer cache so we don't have to re-run them for
206
+ // future uses of this importer.
207
+ for (var j = 0 ; j < i; j++ ) {
208
+ _perImporterCanonicalizeCache[(
209
+ _importers[j],
210
+ url,
211
+ forImport: forImport
212
+ )] = null ;
213
+ }
214
+ cacheable = false ;
215
+ }
216
+
217
+ if (result != null ) return result;
195
218
}
196
219
}
197
220
@@ -206,18 +229,17 @@ final class AsyncImportCache {
206
229
/// that result is cacheable at all.
207
230
Future <(AsyncCanonicalizeResult ?, bool cacheable)> _canonicalize (
208
231
AsyncImporter importer, Uri url, Uri ? baseUrl, bool forImport) async {
209
- var canonicalize = forImport
210
- ? () => inImportRule (() => importer.canonicalize (url))
211
- : () => importer.canonicalize (url);
212
-
213
232
var passContainingUrl = baseUrl != null &&
214
233
(url.scheme == '' || await importer.isNonCanonicalScheme (url.scheme));
215
- var result = await withContainingUrl (
216
- passContainingUrl ? baseUrl : null , canonicalize);
217
234
218
- // TODO(sass/dart-sass#2208): Determine whether the containing URL was
219
- // _actually_ accessed rather than assuming it was.
220
- var cacheable = ! passContainingUrl || importer is FilesystemImporter ;
235
+ var canonicalizeContext =
236
+ CanonicalizeContext (passContainingUrl ? baseUrl : null , forImport);
237
+
238
+ var result = await withCanonicalizeContext (
239
+ canonicalizeContext, () => importer.canonicalize (url));
240
+
241
+ var cacheable =
242
+ ! passContainingUrl || ! canonicalizeContext.wasContainingUrlAccessed;
221
243
222
244
if (result == null ) return (null , cacheable);
223
245
@@ -315,7 +337,7 @@ final class AsyncImportCache {
315
337
Uri sourceMapUrl (Uri canonicalUrl) =>
316
338
_resultsCache[canonicalUrl]? .sourceMapUrl ?? canonicalUrl;
317
339
318
- /// Clears the cached canonical version of the given [url] .
340
+ /// Clears the cached canonical version of the given non-canonical [url] .
319
341
///
320
342
/// Has no effect if the canonical version of [url] has not been cached.
321
343
///
@@ -324,7 +346,8 @@ final class AsyncImportCache {
324
346
void clearCanonicalize (Uri url) {
325
347
_canonicalizeCache.remove ((url, forImport: false ));
326
348
_canonicalizeCache.remove ((url, forImport: true ));
327
- _relativeCanonicalizeCache.removeWhere ((key, _) => key.$1 == url);
349
+ _perImporterCanonicalizeCache.removeWhere (
350
+ (key, _) => key.$2 == url || _nonCanonicalRelativeUrls[key] == url);
328
351
}
329
352
330
353
/// Clears the cached parse tree for the stylesheet with the given
0 commit comments