mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
Represent DynamicImport.url as a String (#250)
This works around dart-lang/sdk#32490. We need to preserve the leading "./" to match Node Sass's behavior. Closes #246
This commit is contained in:
parent
24635614df
commit
07b02174e0
@ -3,6 +3,12 @@
|
||||
* Add support for importing an `_index.scss` or `_index.sass` file when
|
||||
importing a directory.
|
||||
|
||||
### Node JS API
|
||||
|
||||
* Import URLs passed to importers are no longer normalized. For example, if a
|
||||
stylesheet contains `@import "./foo.scss"`, importers will now receive
|
||||
`"./foo.scss"` rather than `"foo.scss"`.
|
||||
|
||||
## 1.0.0-beta.5.3
|
||||
|
||||
* Support hard tabs in the indented syntax.
|
||||
|
@ -9,14 +9,16 @@ import '../import.dart';
|
||||
|
||||
/// An import that will load a Sass file at runtime.
|
||||
class DynamicImport implements Import {
|
||||
// TODO(nweiz): Make this a [Url] when dart-lang/sdk#32490 is fixed, or when
|
||||
// Node Sass imports no longer expose a leading `./`.
|
||||
/// The URI of the file to import.
|
||||
///
|
||||
/// If this is relative, it's relative to the containing file.
|
||||
final Uri url;
|
||||
final String url;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
DynamicImport(this.url, this.span);
|
||||
|
||||
String toString() => StringExpression.quoteText(url.toString());
|
||||
String toString() => StringExpression.quoteText(url);
|
||||
}
|
||||
|
@ -58,18 +58,18 @@ class NodeImporter {
|
||||
/// The [previous] URL is the URL of the stylesheet in which the import
|
||||
/// appeared. Returns the contents of the stylesheet and the URL to use as
|
||||
/// [previous] for imports within the loaded stylesheet.
|
||||
Tuple2<String, Uri> load(Uri url, Uri previous) {
|
||||
if (url.scheme == '' || url.scheme == 'file') {
|
||||
var result = _resolvePath(p.fromUri(url), previous);
|
||||
Tuple2<String, String> load(String url, Uri previous) {
|
||||
var parsed = Uri.parse(url);
|
||||
if (parsed.scheme == '' || parsed.scheme == 'file') {
|
||||
var result = _resolvePath(p.fromUri(parsed), previous);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
// The previous URL is always an absolute file path for filesystem imports.
|
||||
var urlString = url.toString();
|
||||
var previousString =
|
||||
previous.scheme == 'file' ? p.fromUri(previous) : previous.toString();
|
||||
for (var importer in _importers) {
|
||||
var value = call2(importer, _context, urlString, previousString);
|
||||
var value = call2(importer, _context, url, previousString);
|
||||
if (value != null) return _handleImportResult(url, previous, value);
|
||||
}
|
||||
|
||||
@ -81,18 +81,18 @@ class NodeImporter {
|
||||
/// The [previous] URL is the URL of the stylesheet in which the import
|
||||
/// appeared. Returns the contents of the stylesheet and the URL to use as
|
||||
/// [previous] for imports within the loaded stylesheet.
|
||||
Future<Tuple2<String, Uri>> loadAsync(Uri url, Uri previous) async {
|
||||
if (url.scheme == '' || url.scheme == 'file') {
|
||||
var result = _resolvePath(p.fromUri(url), previous);
|
||||
Future<Tuple2<String, String>> loadAsync(String url, Uri previous) async {
|
||||
var parsed = Uri.parse(url);
|
||||
if (parsed.scheme == '' || parsed.scheme == 'file') {
|
||||
var result = _resolvePath(p.fromUri(parsed), previous);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
// The previous URL is always an absolute file path for filesystem imports.
|
||||
var urlString = url.toString();
|
||||
var previousString =
|
||||
previous.scheme == 'file' ? p.fromUri(previous) : previous.toString();
|
||||
for (var importer in _importers) {
|
||||
var value = await _callImporterAsync(importer, urlString, previousString);
|
||||
var value = await _callImporterAsync(importer, url, previousString);
|
||||
if (value != null) return _handleImportResult(url, previous, value);
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ class NodeImporter {
|
||||
///
|
||||
/// Returns the stylesheet at that path and the URL used to load it, or `null`
|
||||
/// if loading failed.
|
||||
Tuple2<String, Uri> _resolvePath(String path, Uri previous) {
|
||||
Tuple2<String, String> _resolvePath(String path, Uri previous) {
|
||||
if (p.isAbsolute(path)) return _tryPath(path);
|
||||
|
||||
// 1: Filesystem imports relative to the base file.
|
||||
@ -130,16 +130,17 @@ class NodeImporter {
|
||||
///
|
||||
/// Returns the stylesheet at that path and the URL used to load it, or `null`
|
||||
/// if loading failed.
|
||||
Tuple2<String, Uri> _tryPath(String path) {
|
||||
Tuple2<String, String> _tryPath(String path) {
|
||||
var resolved = resolveImportPath(path);
|
||||
return resolved == null
|
||||
? null
|
||||
: new Tuple2(readFile(resolved), p.toUri(resolved));
|
||||
: new Tuple2(readFile(resolved), p.toUri(resolved).toString());
|
||||
}
|
||||
|
||||
/// Converts an [_Importer]'s return [value] to a tuple that can be returned
|
||||
/// by [load].
|
||||
Tuple2<String, Uri> _handleImportResult(Uri url, Uri previous, Object value) {
|
||||
Tuple2<String, String> _handleImportResult(
|
||||
String url, Uri previous, Object value) {
|
||||
if (isJSError(value)) throw value;
|
||||
|
||||
NodeImporterResult result;
|
||||
@ -163,10 +164,10 @@ class NodeImporter {
|
||||
|
||||
/// Calls an importer that may or may not be asynchronous.
|
||||
Future<Object> _callImporterAsync(
|
||||
_Importer importer, String urlString, String previousString) async {
|
||||
_Importer importer, String url, String previousString) async {
|
||||
var completer = new Completer();
|
||||
|
||||
var result = call3(importer, _context, urlString, previousString,
|
||||
var result = call3(importer, _context, url, previousString,
|
||||
allowInterop(completer.complete));
|
||||
if (isUndefined(result)) return await completer.future;
|
||||
return result;
|
||||
|
@ -12,7 +12,7 @@ class NodeImporter {
|
||||
NodeImporter(Object context, Iterable<String> includePaths,
|
||||
Iterable<_Importer> importers);
|
||||
|
||||
Tuple2<String, Uri> load(Uri url, Uri previous) => null;
|
||||
Tuple2<String, String> load(String url, Uri previous) => null;
|
||||
|
||||
Future<Tuple2<String, Uri>> loadAsync(Uri url, Uri previous) => null;
|
||||
Future<Tuple2<String, String>> loadAsync(String url, Uri previous) => null;
|
||||
}
|
||||
|
@ -762,12 +762,14 @@ abstract class StylesheetParser extends Parser {
|
||||
|
||||
/// Parses [url] as an import URL.
|
||||
@protected
|
||||
Uri parseImportUrl(String url) {
|
||||
String parseImportUrl(String url) {
|
||||
// Backwards-compatibility for implementations that allow absolute Windows
|
||||
// paths in imports.
|
||||
if (p.windows.isAbsolute(url)) return p.windows.toUri(url);
|
||||
if (p.windows.isAbsolute(url)) return p.windows.toUri(url).toString();
|
||||
|
||||
return Uri.parse(url);
|
||||
// Throw a [FormatException] if [url] is invalid.
|
||||
Uri.parse(url);
|
||||
return url;
|
||||
}
|
||||
|
||||
/// Returns whether [url] indicates that an `@import` is a plain CSS import.
|
||||
|
@ -696,21 +696,23 @@ class _EvaluateVisitor
|
||||
var stylesheet = await _importLikeNode(import);
|
||||
if (stylesheet != null) return new Tuple2(null, stylesheet);
|
||||
} else {
|
||||
var url = Uri.parse(import.url);
|
||||
|
||||
// Try to resolve [import.url] relative to the current URL with the
|
||||
// current importer.
|
||||
if (import.url.scheme.isEmpty && _importer != null) {
|
||||
if (url.scheme.isEmpty && _importer != null) {
|
||||
var stylesheet =
|
||||
await _tryImport(_importer, _baseUrl.resolveUri(import.url));
|
||||
await _tryImport(_importer, _baseUrl.resolveUri(url));
|
||||
if (stylesheet != null) return new Tuple2(_importer, stylesheet);
|
||||
}
|
||||
|
||||
for (var importer in _importers) {
|
||||
var stylesheet = await _tryImport(importer, import.url);
|
||||
var stylesheet = await _tryImport(importer, url);
|
||||
if (stylesheet != null) return new Tuple2(importer, stylesheet);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.url.scheme == 'package') {
|
||||
if (import.url.startsWith('package:')) {
|
||||
// Special-case this error message, since it's tripped people up in the
|
||||
// past.
|
||||
throw "\"package:\" URLs aren't supported on this platform.";
|
||||
@ -744,13 +746,13 @@ class _EvaluateVisitor
|
||||
var contents = result.item1;
|
||||
var url = result.item2;
|
||||
|
||||
if (url.scheme == 'file') {
|
||||
if (url.startsWith('file:')) {
|
||||
_includedFiles.add(p.fromUri(url));
|
||||
} else {
|
||||
_includedFiles.add(url.toString());
|
||||
_includedFiles.add(url);
|
||||
}
|
||||
|
||||
return url.scheme == 'file' && pUrl.extension(url.path) == '.sass'
|
||||
return url.startsWith('file') && pUrl.extension(url) == '.sass'
|
||||
? new Stylesheet.parseSass(contents, url: url, color: _color)
|
||||
: new Stylesheet.parseScss(contents, url: url, color: _color);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 199b3bd52a5c5c147444ee45c2f3d46ef8af2d28
|
||||
// Checksum: 53f8eb4f4e6ed2cad3fe359490a50e12911a278a
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
@ -690,21 +690,22 @@ class _EvaluateVisitor
|
||||
var stylesheet = _importLikeNode(import);
|
||||
if (stylesheet != null) return new Tuple2(null, stylesheet);
|
||||
} else {
|
||||
var url = Uri.parse(import.url);
|
||||
|
||||
// Try to resolve [import.url] relative to the current URL with the
|
||||
// current importer.
|
||||
if (import.url.scheme.isEmpty && _importer != null) {
|
||||
var stylesheet =
|
||||
_tryImport(_importer, _baseUrl.resolveUri(import.url));
|
||||
if (url.scheme.isEmpty && _importer != null) {
|
||||
var stylesheet = _tryImport(_importer, _baseUrl.resolveUri(url));
|
||||
if (stylesheet != null) return new Tuple2(_importer, stylesheet);
|
||||
}
|
||||
|
||||
for (var importer in _importers) {
|
||||
var stylesheet = _tryImport(importer, import.url);
|
||||
var stylesheet = _tryImport(importer, url);
|
||||
if (stylesheet != null) return new Tuple2(importer, stylesheet);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.url.scheme == 'package') {
|
||||
if (import.url.startsWith('package:')) {
|
||||
// Special-case this error message, since it's tripped people up in the
|
||||
// past.
|
||||
throw "\"package:\" URLs aren't supported on this platform.";
|
||||
@ -738,13 +739,13 @@ class _EvaluateVisitor
|
||||
var contents = result.item1;
|
||||
var url = result.item2;
|
||||
|
||||
if (url.scheme == 'file') {
|
||||
if (url.startsWith('file:')) {
|
||||
_includedFiles.add(p.fromUri(url));
|
||||
} else {
|
||||
_includedFiles.add(url.toString());
|
||||
_includedFiles.add(url);
|
||||
}
|
||||
|
||||
return url.scheme == 'file' && pUrl.extension(url.path) == '.sass'
|
||||
return url.startsWith('file') && pUrl.extension(url) == '.sass'
|
||||
? new Stylesheet.parseSass(contents, url: url, color: _color)
|
||||
: new Stylesheet.parseScss(contents, url: url, color: _color);
|
||||
}
|
||||
|
@ -266,6 +266,16 @@ void main() {
|
||||
}))));
|
||||
});
|
||||
|
||||
// Regression test for #246.
|
||||
test("doesn't remove ./", () {
|
||||
renderSync(new RenderOptions(
|
||||
data: "@import './foo'",
|
||||
importer: allowInterop(expectAsync2((url, _) {
|
||||
expect(url, equals('./foo'));
|
||||
return new NodeImporterResult(contents: '');
|
||||
}))));
|
||||
});
|
||||
|
||||
test("isn't resolved relative to the current file", () {
|
||||
renderSync(new RenderOptions(
|
||||
data: "@import 'foo/bar'",
|
||||
|
Loading…
Reference in New Issue
Block a user