Skip to content

Commit bfdf4b3

Browse files
authored
Fix import-only files for Node importers (#919)
1 parent 79d9a73 commit bfdf4b3

File tree

8 files changed

+97
-32
lines changed

8 files changed

+97
-32
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.24.2
2+
3+
* Fix a bug introduced in the previous release that prevented custom importers
4+
in Node.js from loading import-only files.
5+
16
## 1.24.1
27

38
* Fix a bug where the wrong file could be loaded when the same URL is used by

lib/src/importer/node/implementation.dart

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ class NodeImporter {
6767
/// The [previous] URL is the URL of the stylesheet in which the import
6868
/// appeared. Returns the contents of the stylesheet and the URL to use as
6969
/// [previous] for imports within the loaded stylesheet.
70-
Tuple2<String, String> load(String url, Uri previous) {
70+
Tuple2<String, String> load(String url, Uri previous, bool forImport) {
7171
var parsed = Uri.parse(url);
7272
if (parsed.scheme == '' || parsed.scheme == 'file') {
73-
var result = _resolveRelativePath(p.fromUri(parsed), previous);
73+
var result = _resolveRelativePath(p.fromUri(parsed), previous, forImport);
7474
if (result != null) return result;
7575
}
7676

@@ -79,21 +79,24 @@ class NodeImporter {
7979
previous.scheme == 'file' ? p.fromUri(previous) : previous.toString();
8080
for (var importer in _importers) {
8181
var value = call2(importer, _context, url, previousString);
82-
if (value != null) return _handleImportResult(url, previous, value);
82+
if (value != null) {
83+
return _handleImportResult(url, previous, value, forImport);
84+
}
8385
}
8486

85-
return _resolveLoadPathFromUrl(parsed, previous);
87+
return _resolveLoadPathFromUrl(parsed, previous, forImport);
8688
}
8789

8890
/// Asynchronously loads the stylesheet at [url].
8991
///
9092
/// The [previous] URL is the URL of the stylesheet in which the import
9193
/// appeared. Returns the contents of the stylesheet and the URL to use as
9294
/// [previous] for imports within the loaded stylesheet.
93-
Future<Tuple2<String, String>> loadAsync(String url, Uri previous) async {
95+
Future<Tuple2<String, String>> loadAsync(
96+
String url, Uri previous, bool forImport) async {
9497
var parsed = Uri.parse(url);
9598
if (parsed.scheme == '' || parsed.scheme == 'file') {
96-
var result = _resolveRelativePath(p.fromUri(parsed), previous);
99+
var result = _resolveRelativePath(p.fromUri(parsed), previous, forImport);
97100
if (result != null) return result;
98101
}
99102

@@ -102,22 +105,26 @@ class NodeImporter {
102105
previous.scheme == 'file' ? p.fromUri(previous) : previous.toString();
103106
for (var importer in _importers) {
104107
var value = await _callImporterAsync(importer, url, previousString);
105-
if (value != null) return _handleImportResult(url, previous, value);
108+
if (value != null) {
109+
return _handleImportResult(url, previous, value, forImport);
110+
}
106111
}
107112

108-
return _resolveLoadPathFromUrl(parsed, previous);
113+
return _resolveLoadPathFromUrl(parsed, previous, forImport);
109114
}
110115

111116
/// Tries to load a stylesheet at the given [path] relative to [previous].
112117
///
113118
/// Returns the stylesheet at that path and the URL used to load it, or `null`
114119
/// if loading failed.
115-
Tuple2<String, String> _resolveRelativePath(String path, Uri previous) {
116-
if (p.isAbsolute(path)) return _tryPath(path);
120+
Tuple2<String, String> _resolveRelativePath(
121+
String path, Uri previous, bool forImport) {
122+
if (p.isAbsolute(path)) return _tryPath(path, forImport);
117123

118124
// 1: Filesystem imports relative to the base file.
119125
if (previous.scheme == 'file') {
120-
var result = _tryPath(p.join(p.dirname(p.fromUri(previous)), path));
126+
var result =
127+
_tryPath(p.join(p.dirname(p.fromUri(previous)), path), forImport);
121128
if (result != null) return result;
122129
}
123130
return null;
@@ -128,24 +135,26 @@ class NodeImporter {
128135
///
129136
/// Returns the stylesheet at that path and the URL used to load it, or `null`
130137
/// if loading failed.
131-
Tuple2<String, String> _resolveLoadPathFromUrl(Uri url, Uri previous) =>
138+
Tuple2<String, String> _resolveLoadPathFromUrl(
139+
Uri url, Uri previous, bool forImport) =>
132140
url.scheme == '' || url.scheme == 'file'
133-
? _resolveLoadPath(p.fromUri(url), previous)
141+
? _resolveLoadPath(p.fromUri(url), previous, forImport)
134142
: null;
135143

136144
/// Tries to load a stylesheet at the given [path] from a load path (including
137145
/// the working directory).
138146
///
139147
/// Returns the stylesheet at that path and the URL used to load it, or `null`
140148
/// if loading failed.
141-
Tuple2<String, String> _resolveLoadPath(String path, Uri previous) {
149+
Tuple2<String, String> _resolveLoadPath(
150+
String path, Uri previous, bool forImport) {
142151
// 2: Filesystem imports relative to the working directory.
143-
var cwdResult = _tryPath(p.absolute(path));
152+
var cwdResult = _tryPath(p.absolute(path), forImport);
144153
if (cwdResult != null) return cwdResult;
145154

146155
// 3: Filesystem imports relative to [_includePaths].
147156
for (var includePath in _includePaths) {
148-
var result = _tryPath(p.absolute(p.join(includePath, path)));
157+
var result = _tryPath(p.absolute(p.join(includePath, path)), forImport);
149158
if (result != null) return result;
150159
}
151160

@@ -156,8 +165,10 @@ class NodeImporter {
156165
///
157166
/// Returns the stylesheet at that path and the URL used to load it, or `null`
158167
/// if loading failed.
159-
Tuple2<String, String> _tryPath(String path) {
160-
var resolved = resolveImportPath(path);
168+
Tuple2<String, String> _tryPath(String path, bool forImport) {
169+
var resolved = forImport
170+
? inImportRule(() => resolveImportPath(path))
171+
: resolveImportPath(path);
161172
return resolved == null
162173
? null
163174
: Tuple2(readFile(resolved), p.toUri(resolved).toString());
@@ -166,14 +177,14 @@ class NodeImporter {
166177
/// Converts an importer's return [value] to a tuple that can be returned by
167178
/// [load].
168179
Tuple2<String, String> _handleImportResult(
169-
String url, Uri previous, Object value) {
180+
String url, Uri previous, Object value, bool forImport) {
170181
if (isJSError(value)) throw value;
171182
if (value is! NodeImporterResult) return null;
172183

173184
var result = value as NodeImporterResult;
174185
if (result.file != null) {
175-
var resolved = _resolveRelativePath(result.file, previous) ??
176-
_resolveLoadPath(result.file, previous);
186+
var resolved = _resolveRelativePath(result.file, previous, forImport) ??
187+
_resolveLoadPath(result.file, previous, forImport);
177188
if (resolved != null) return resolved;
178189

179190
throw "Can't find stylesheet to import.";

lib/src/importer/node/interface.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ class NodeImporter {
1010
NodeImporter(Object context, Iterable<String> includePaths,
1111
Iterable<Object> importers);
1212

13-
Tuple2<String, String> load(String url, Uri previous) => null;
13+
Tuple2<String, String> load(String url, Uri previous, bool forImport) => null;
1414

15-
Future<Tuple2<String, String>> loadAsync(String url, Uri previous) => null;
15+
Future<Tuple2<String, String>> loadAsync(
16+
String url, Uri previous, bool forImport) =>
17+
null;
1618
}

lib/src/visitor/async_evaluate.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,7 +1410,7 @@ class _EvaluateVisitor
14101410
_importSpan = span;
14111411

14121412
if (_nodeImporter != null) {
1413-
var stylesheet = await _importLikeNode(url);
1413+
var stylesheet = await _importLikeNode(url, forImport);
14141414
if (stylesheet != null) return Tuple2(null, stylesheet);
14151415
} else {
14161416
var tuple = await _importCache.import(Uri.parse(url),
@@ -1445,9 +1445,9 @@ class _EvaluateVisitor
14451445
/// Imports a stylesheet using [_nodeImporter].
14461446
///
14471447
/// Returns the [Stylesheet], or `null` if the import failed.
1448-
Future<Stylesheet> _importLikeNode(String originalUrl) async {
1449-
var result =
1450-
await _nodeImporter.loadAsync(originalUrl, _stylesheet.span?.sourceUrl);
1448+
Future<Stylesheet> _importLikeNode(String originalUrl, bool forImport) async {
1449+
var result = await _nodeImporter.loadAsync(
1450+
originalUrl, _stylesheet.span?.sourceUrl, forImport);
14511451
if (result == null) return null;
14521452

14531453
var contents = result.item1;

lib/src/visitor/evaluate.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: e3d7bea705bed417db9925546c076caed4465b2e
8+
// Checksum: 98bd4418d21d4f005485e343869b458b44841450
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1407,7 +1407,7 @@ class _EvaluateVisitor
14071407
_importSpan = span;
14081408

14091409
if (_nodeImporter != null) {
1410-
var stylesheet = _importLikeNode(url);
1410+
var stylesheet = _importLikeNode(url, forImport);
14111411
if (stylesheet != null) return Tuple2(null, stylesheet);
14121412
} else {
14131413
var tuple = _importCache.import(Uri.parse(url),
@@ -1442,8 +1442,9 @@ class _EvaluateVisitor
14421442
/// Imports a stylesheet using [_nodeImporter].
14431443
///
14441444
/// Returns the [Stylesheet], or `null` if the import failed.
1445-
Stylesheet _importLikeNode(String originalUrl) {
1446-
var result = _nodeImporter.load(originalUrl, _stylesheet.span?.sourceUrl);
1445+
Stylesheet _importLikeNode(String originalUrl, bool forImport) {
1446+
var result =
1447+
_nodeImporter.load(originalUrl, _stylesheet.span?.sourceUrl, forImport);
14471448
if (result == null) return null;
14481449

14491450
var contents = result.item1;

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.24.1
2+
version: 1.24.2
33
description: A Sass implementation in Dart.
44
author: Sass Team
55
homepage: https://github.com/sass/dart-sass

test/node_api/importer_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,32 @@ void main() {
165165
equalsIgnoringWhitespace('a { b: c; }'));
166166
});
167167

168+
test("supports import-only files", () async {
169+
await writeTextFile(p.join(sandbox, 'target.scss'), 'a {b: regular}');
170+
await writeTextFile(
171+
p.join(sandbox, 'target.import.scss'), 'a {b: import-only}');
172+
173+
expect(
174+
renderSync(RenderOptions(
175+
data: "@import 'foo'",
176+
importer: allowInterop((void _, void __) =>
177+
NodeImporterResult(file: p.join(sandbox, 'target.scss'))))),
178+
equalsIgnoringWhitespace('a { b: import-only; }'));
179+
});
180+
181+
test("supports mixed `@use` and `@import`", () async {
182+
await writeTextFile(p.join(sandbox, 'target.scss'), 'a {b: regular}');
183+
await writeTextFile(
184+
p.join(sandbox, 'target.import.scss'), 'a {b: import-only}');
185+
186+
expect(
187+
renderSync(RenderOptions(
188+
data: "@use 'foo'; @import 'foo';",
189+
importer: allowInterop((void _, void __) =>
190+
NodeImporterResult(file: p.join(sandbox, 'target.scss'))))),
191+
equalsIgnoringWhitespace('a { b: regular; } a { b: import-only; }'));
192+
});
193+
168194
test("may be extensionless", () async {
169195
expect(
170196
renderSync(RenderOptions(

test/node_api_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ void main() {
7777
equalsIgnoringWhitespace('a { b: c; }'));
7878
});
7979

80+
test("supports import-only files", () async {
81+
await writeTextFile(p.join(sandbox, 'foo.scss'), 'a {b: regular}');
82+
await writeTextFile(
83+
p.join(sandbox, 'foo.import.scss'), 'a {b: import-only}');
84+
85+
runTestInSandbox();
86+
expect(renderSync(RenderOptions(data: "@import 'foo'")),
87+
equalsIgnoringWhitespace('a { b: import-only; }'));
88+
});
89+
90+
test("supports mixed `@use` and `@import`", () async {
91+
await writeTextFile(p.join(sandbox, 'foo.scss'), 'a {b: regular}');
92+
await writeTextFile(
93+
p.join(sandbox, 'foo.import.scss'), 'a {b: import-only}');
94+
95+
runTestInSandbox();
96+
expect(renderSync(RenderOptions(data: "@use 'foo'; @import 'foo';")),
97+
equalsIgnoringWhitespace('a { b: regular; } a { b: import-only; }'));
98+
});
99+
80100
test("renders a string", () {
81101
expect(renderSync(RenderOptions(data: "a {b: c}")),
82102
equalsIgnoringWhitespace('a { b: c; }'));

0 commit comments

Comments
 (0)