mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 21:31:11 +01:00
Add importer infrastructure
This isn't yet exposed by any public-facing API.
This commit is contained in:
parent
e79bba4fae
commit
ed1d6ef6b1
@ -3,6 +3,9 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'ast/sass.dart';
|
||||
import 'importer.dart';
|
||||
import 'importer/filesystem.dart';
|
||||
import 'importer/package.dart';
|
||||
import 'io.dart';
|
||||
import 'sync_package_resolver.dart';
|
||||
import 'util/path.dart';
|
||||
@ -14,6 +17,7 @@ import 'visitor/serialize.dart';
|
||||
CompileResult compile(String path,
|
||||
{bool indented,
|
||||
bool color: false,
|
||||
Iterable<Importer> importers,
|
||||
SyncPackageResolver packageResolver,
|
||||
Iterable<String> loadPaths,
|
||||
OutputStyle style,
|
||||
@ -23,9 +27,11 @@ CompileResult compile(String path,
|
||||
compileString(readFile(path),
|
||||
indented: indented ?? p.extension(path) == '.sass',
|
||||
color: color,
|
||||
importers: importers,
|
||||
packageResolver: packageResolver,
|
||||
style: style,
|
||||
loadPaths: loadPaths,
|
||||
importer: new FilesystemImporter('.'),
|
||||
style: style,
|
||||
useSpaces: useSpaces,
|
||||
indentWidth: indentWidth,
|
||||
lineFeed: lineFeed,
|
||||
@ -36,8 +42,10 @@ CompileResult compile(String path,
|
||||
CompileResult compileString(String source,
|
||||
{bool indented: false,
|
||||
bool color: false,
|
||||
Iterable<Importer> importers,
|
||||
SyncPackageResolver packageResolver,
|
||||
Iterable<String> loadPaths,
|
||||
Importer importer,
|
||||
OutputStyle style,
|
||||
bool useSpaces: true,
|
||||
int indentWidth,
|
||||
@ -46,15 +54,24 @@ CompileResult compileString(String source,
|
||||
var sassTree = indented
|
||||
? new Stylesheet.parseSass(source, url: url, color: color)
|
||||
: new Stylesheet.parseScss(source, url: url, color: color);
|
||||
|
||||
var importerList = (importers?.toList() ?? []);
|
||||
if (loadPaths != null) {
|
||||
importerList.addAll(loadPaths.map((path) => new FilesystemImporter(path)));
|
||||
}
|
||||
if (packageResolver != null) {
|
||||
importerList.add(new PackageImporter(packageResolver));
|
||||
}
|
||||
|
||||
var evaluateResult = evaluate(sassTree,
|
||||
color: color, packageResolver: packageResolver, loadPaths: loadPaths);
|
||||
importers: importerList, importer: importer, color: color);
|
||||
var css = serialize(evaluateResult.stylesheet,
|
||||
style: style,
|
||||
useSpaces: useSpaces,
|
||||
indentWidth: indentWidth,
|
||||
lineFeed: lineFeed);
|
||||
|
||||
return new CompileResult(css, evaluateResult.includedUrls);
|
||||
return new CompileResult(css, evaluateResult.includedFiles);
|
||||
}
|
||||
|
||||
/// The result of compiling a Sass document to CSS, along with metadata about
|
||||
@ -63,9 +80,12 @@ class CompileResult {
|
||||
/// The compiled CSS.
|
||||
final String css;
|
||||
|
||||
/// The URLs that were loaded during the compilation, including the main
|
||||
/// file's.
|
||||
final Set<Uri> includedUrls;
|
||||
/// The set that will eventually populate the JS API's
|
||||
/// `result.stats.includedFiles` field.
|
||||
///
|
||||
/// For filesystem imports, this contains the import path. For all other
|
||||
/// imports, it contains the URL passed to the `@import`.
|
||||
final Set<String> includedFiles;
|
||||
|
||||
CompileResult(this.css, this.includedUrls);
|
||||
CompileResult(this.css, this.includedFiles);
|
||||
}
|
||||
|
@ -58,12 +58,10 @@ main(List<String> args) async {
|
||||
try {
|
||||
String css;
|
||||
if (stdinFlag) {
|
||||
css = compileString(await readStdin(), color: color);
|
||||
css = await _compileStdin();
|
||||
} else {
|
||||
var input = options.rest.first;
|
||||
css = input == '-'
|
||||
? compileString(await readStdin(), color: color)
|
||||
: compile(input, color: color);
|
||||
css = input == '-' ? await _compileStdin() : compile(input, color: color);
|
||||
}
|
||||
|
||||
if (css.isNotEmpty) print(css);
|
||||
@ -124,6 +122,11 @@ Future<String> _loadVersion() async {
|
||||
.last;
|
||||
}
|
||||
|
||||
/// Compiles Sass from standard input and returns the result.
|
||||
Future<String> _compileStdin({bool color: false}) async =>
|
||||
compileString(await readStdin(),
|
||||
color: color, importer: new FilesystemImporter('.'));
|
||||
|
||||
/// Print the usage information for Sass, with [message] as a header.
|
||||
void _printUsage(ArgParser parser, String message) {
|
||||
print("$message\n");
|
||||
|
66
lib/src/importer.dart
Normal file
66
lib/src/importer.dart
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2017 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'importer/no_op.dart';
|
||||
import 'importer/result.dart';
|
||||
|
||||
/// An interface for importers that resolves URLs in `@import`s to the contents
|
||||
/// of Sass files.
|
||||
///
|
||||
/// Importers should override [toString] to provide a human-readable description
|
||||
/// of the importer. For example, the default filesystem importer returns its
|
||||
/// load path.
|
||||
///
|
||||
/// Subclasses should extend [Importer], not implement it.
|
||||
abstract class Importer {
|
||||
/// An importer that never imports any stylesheets.
|
||||
///
|
||||
/// This is used for stylesheets which don't support relative imports, such as
|
||||
/// those created from Dart code with plain strings.
|
||||
static final Importer noOp = new NoOpImporter();
|
||||
|
||||
/// If [url] is recognized by this importer, returns its canonical format.
|
||||
///
|
||||
/// If Sass has already loaded a stylesheet with the returned canonical URL,
|
||||
/// it re-uses the existing parse tree. This means that importers **must
|
||||
/// ensure** that the same canonical URL always refers to the same stylesheet,
|
||||
/// *even across different importers*.
|
||||
///
|
||||
/// This may return `null` if [url] isn't recognized by this importer.
|
||||
///
|
||||
/// If this importer's URL format supports file extensions, it should
|
||||
/// canonicalize them the same way as the default filesystem importer:
|
||||
///
|
||||
/// * If the [url] ends in `.sass` or `.scss`, the importer should look for
|
||||
/// a stylesheet with that exact URL and return `null` if it's not found.
|
||||
///
|
||||
/// * Otherwise, the importer should look for a stylesheet at `"$url.sass"` or
|
||||
/// one at `"$url.scss"`, in that order. If neither is found, it should
|
||||
/// return `null`.
|
||||
///
|
||||
/// Sass assumes that calling [canonicalize] multiple times with the same URL
|
||||
/// will return the same result.
|
||||
Uri canonicalize(Uri url);
|
||||
|
||||
/// Loads the Sass text for the given [url], or returns `null` if
|
||||
/// this importer can't find the stylesheet it refers to.
|
||||
///
|
||||
/// The [url] comes from a call to [canonicalize] for this importer.
|
||||
///
|
||||
/// When Sass encounters an `@import` rule in a stylesheet, it first calls
|
||||
/// [canonicalize] and [load] on the importer that first loaded that
|
||||
/// stylesheet with the imported URL resolved relative to the stylesheet's
|
||||
/// original URL. If either of those returns `null`, it then calls
|
||||
/// [canonicalize] and [load] on each importer in order with the URL as it
|
||||
/// appears in the `@import` rule.
|
||||
///
|
||||
/// If the importer finds a stylesheet at [url] but it fails to load for some
|
||||
/// reason, or if [url] is uniquely associated with this importer but doesn't
|
||||
/// refer to a real stylesheet, the importer may throw an exception that will
|
||||
/// be wrapped by Sass. If the exception object has a `message` property, it
|
||||
/// will be used as the wrapped exception's message; otherwise, the exception
|
||||
/// object's `toString()` will be used. This means it's safe for importers to
|
||||
/// throw plain strings.
|
||||
ImporterResult load(Uri url);
|
||||
}
|
51
lib/src/importer/filesystem.dart
Normal file
51
lib/src/importer/filesystem.dart
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2017 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../importer.dart';
|
||||
import '../io.dart';
|
||||
import 'result.dart';
|
||||
|
||||
/// An importer that loads files from a load path on the filesystem.
|
||||
class FilesystemImporter extends Importer {
|
||||
/// The path relative to which this importer looks for files.
|
||||
final String _loadPath;
|
||||
|
||||
/// Creates an importer that loads files relative to [loadPath].
|
||||
FilesystemImporter(this._loadPath);
|
||||
|
||||
Uri canonicalize(Uri url) {
|
||||
var urlPath = p.fromUri(url);
|
||||
var path = p.join(_loadPath, urlPath);
|
||||
var extension = p.extension(path);
|
||||
var resolved = extension == '.sass' || extension == '.scss'
|
||||
? _tryPath(path)
|
||||
: _tryPathWithExtensions(path);
|
||||
return resolved == null ? null : p.toUri(p.canonicalize(resolved));
|
||||
}
|
||||
|
||||
/// Like [_tryPath], but checks both `.sass` and `.scss` extensions.
|
||||
String _tryPathWithExtensions(String path) =>
|
||||
_tryPath(path + '.sass') ?? _tryPath(path + '.scss');
|
||||
|
||||
/// If a file exists at [path], or a partial with the same name exists,
|
||||
/// returns the resolved path.
|
||||
///
|
||||
/// Otherwise, returns `null`.
|
||||
String _tryPath(String path) {
|
||||
var partial = p.join(p.dirname(path), "_${p.basename(path)}");
|
||||
if (fileExists(partial)) return partial;
|
||||
if (fileExists(path)) return path;
|
||||
return null;
|
||||
}
|
||||
|
||||
ImporterResult load(Uri url) {
|
||||
var path = p.fromUri(url);
|
||||
return new ImporterResult(readFile(path),
|
||||
sourceMapUrl: url, indented: p.extension(path) == '.sass');
|
||||
}
|
||||
|
||||
String toString() => _loadPath;
|
||||
}
|
17
lib/src/importer/no_op.dart
Normal file
17
lib/src/importer/no_op.dart
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2017 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import '../importer.dart';
|
||||
import 'result.dart';
|
||||
|
||||
/// An importer that never imports any stylesheets.
|
||||
///
|
||||
/// This is used for stylesheets which don't support relative imports, such as
|
||||
/// those created from Dart code with plain strings.
|
||||
class NoOpImporter extends Importer {
|
||||
Uri canonicalize(Uri url) => null;
|
||||
ImporterResult load(Uri url) => null;
|
||||
|
||||
String toString() => "(unknown)";
|
||||
}
|
44
lib/src/importer/package.dart
Normal file
44
lib/src/importer/package.dart
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2017 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import '../importer.dart';
|
||||
import '../sync_package_resolver.dart';
|
||||
import 'filesystem.dart';
|
||||
import 'result.dart';
|
||||
|
||||
/// A filesystem importer to use when resolving the results of `package:` URLs.
|
||||
///
|
||||
/// This allows us to avoid duplicating the logic for choosing an extension and
|
||||
/// looking for partials.
|
||||
final _filesystemImporter = new FilesystemImporter('.');
|
||||
|
||||
/// An importer that loads stylesheets from `package:` imports.
|
||||
class PackageImporter extends Importer {
|
||||
/// The resolver that converts `package:` imports to `file:`.
|
||||
final SyncPackageResolver _packageResolver;
|
||||
|
||||
/// Creates an importer that loads stylesheets from `package:` URLs according
|
||||
/// to [packageResolver], which is a [SyncPackageResolver][] from the
|
||||
/// `package_resolver` package.
|
||||
///
|
||||
/// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html
|
||||
PackageImporter(this._packageResolver);
|
||||
|
||||
Uri canonicalize(Uri url) {
|
||||
if (url.scheme != 'package') return null;
|
||||
|
||||
var resolved = _packageResolver.resolveUri(url);
|
||||
if (resolved == null) throw "Unknown package.";
|
||||
|
||||
if (resolved.scheme.isNotEmpty && resolved.scheme != 'file') {
|
||||
throw "Unsupported URL ${resolved}.";
|
||||
}
|
||||
|
||||
return _filesystemImporter.canonicalize(resolved);
|
||||
}
|
||||
|
||||
ImporterResult load(Uri url) => _filesystemImporter.load(url);
|
||||
|
||||
String toString() => "package:...";
|
||||
}
|
37
lib/src/importer/result.dart
Normal file
37
lib/src/importer/result.dart
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../importer.dart';
|
||||
|
||||
/// The result of importing a Sass stylesheet, as returned by [Importer.load].
|
||||
class ImporterResult {
|
||||
/// The contents of the stylesheet.
|
||||
final String contents;
|
||||
|
||||
/// An absolute, browser-accessible URL indicating the resolved location of
|
||||
/// the imported stylesheet.
|
||||
///
|
||||
/// This should be a `file:` URL if one is available, but an `http:` URL is
|
||||
/// acceptable as well. If no URL is supplied, a `data:` URL is generated
|
||||
/// automatically from [contents].
|
||||
Uri get sourceMapUrl =>
|
||||
_sourceMapUrl ?? new Uri.dataFromString(contents, encoding: UTF8);
|
||||
final Uri _sourceMapUrl;
|
||||
|
||||
/// Whether the stylesheet uses the indented syntax.
|
||||
final bool isIndented;
|
||||
|
||||
ImporterResult(this.contents, {Uri sourceMapUrl, @required bool indented})
|
||||
: _sourceMapUrl = sourceMapUrl,
|
||||
isIndented = indented {
|
||||
if (sourceMapUrl?.scheme == '') {
|
||||
throw new ArgumentError.value(
|
||||
sourceMapUrl, 'sourceMapUrl', 'must be absolute');
|
||||
}
|
||||
}
|
||||
}
|
@ -103,7 +103,7 @@ RenderResult _doRender(RenderOptions options) {
|
||||
start: start.millisecondsSinceEpoch,
|
||||
end: end.millisecondsSinceEpoch,
|
||||
duration: end.difference(start).inMilliseconds,
|
||||
includedFiles: result.includedUrls.map((url) => p.fromUri(url)).toList());
|
||||
includedFiles: result.includedFiles.toList());
|
||||
}
|
||||
|
||||
/// Converts a [SassException] to a [RenderError].
|
||||
|
@ -18,9 +18,10 @@ import '../color_names.dart';
|
||||
import '../environment.dart';
|
||||
import '../exception.dart';
|
||||
import '../extend/extender.dart';
|
||||
import '../importer.dart';
|
||||
import '../importer/filesystem.dart';
|
||||
import '../io.dart';
|
||||
import '../parse/keyframe_selector.dart';
|
||||
import '../sync_package_resolver.dart';
|
||||
import '../utils.dart';
|
||||
import '../util/path.dart';
|
||||
import '../value.dart';
|
||||
@ -43,24 +44,27 @@ final _noSourceUrl = Uri.parse("-");
|
||||
///
|
||||
/// If [color] is `true`, this will use terminal colors in warnings.
|
||||
///
|
||||
/// If [importer] is passed, it's used to resolve relative imports in
|
||||
/// [stylesheet] relative to `stylesheet.span.sourceUrl`.
|
||||
///
|
||||
/// Throws a [SassRuntimeException] if evaluation fails.
|
||||
EvaluateResult evaluate(Stylesheet stylesheet,
|
||||
{Iterable<String> loadPaths,
|
||||
{Iterable<Importer> importers,
|
||||
Importer importer,
|
||||
Environment environment,
|
||||
bool color: false,
|
||||
SyncPackageResolver packageResolver}) =>
|
||||
bool color: false}) =>
|
||||
new _EvaluateVisitor(
|
||||
loadPaths: loadPaths,
|
||||
importers: importers,
|
||||
importer: importer,
|
||||
environment: environment,
|
||||
color: color,
|
||||
packageResolver: packageResolver)
|
||||
color: color)
|
||||
.run(stylesheet);
|
||||
|
||||
/// A visitor that executes Sass code to produce a CSS tree.
|
||||
class _EvaluateVisitor
|
||||
implements StatementVisitor<Value>, ExpressionVisitor<Value> {
|
||||
/// The paths to search for Sass files being imported.
|
||||
final List<String> _loadPaths;
|
||||
/// The importers to use when loading new Sass files.
|
||||
final List<Importer> _importers;
|
||||
|
||||
/// Whether to use terminal colors in warnings.
|
||||
final bool _color;
|
||||
@ -68,6 +72,15 @@ class _EvaluateVisitor
|
||||
/// The current lexical environment.
|
||||
Environment _environment;
|
||||
|
||||
/// The importer that's currently being used to resolve relative imports.
|
||||
///
|
||||
/// If this is `null`, relative imports aren't supported in the current
|
||||
/// stylesheet.
|
||||
Importer _importer;
|
||||
|
||||
/// The base URL to use for resolving relative imports.
|
||||
Uri _baseUrl;
|
||||
|
||||
/// The style rule that defines the current parent selector, if any.
|
||||
CssStyleRule _styleRule;
|
||||
|
||||
@ -117,18 +130,15 @@ class _EvaluateVisitor
|
||||
/// the stylesheet has been fully performed.
|
||||
var _outOfOrderImports = <CssImport>[];
|
||||
|
||||
/// The resolved URLs for each [DynamicImport] that's been seen so far.
|
||||
///
|
||||
/// This is cached in case the same file is imported multiple times, and thus
|
||||
/// its imports need to be resolved multiple times.
|
||||
final _importPaths = <DynamicImport, String>{};
|
||||
/// The parsed stylesheets for each canonicalized import URL.
|
||||
final _importCache = <Uri, Stylesheet>{};
|
||||
|
||||
/// The parsed stylesheets for each resolved import URL.
|
||||
/// The set that will eventually populate the JS API's
|
||||
/// `result.stats.includedFiles` field.
|
||||
///
|
||||
/// This is separate from [_importPaths] because multiple `@import` rules may
|
||||
/// import the same stylesheet, and we don't want to parse the same stylesheet
|
||||
/// multiple times.
|
||||
final _importedFiles = <Uri, Stylesheet>{};
|
||||
/// For filesystem imports, this contains the import path. For all other
|
||||
/// imports, it contains the URL passed to the `@import`.
|
||||
final _includedFiles = new Set<String>();
|
||||
|
||||
final _activeImports = new Set<Uri>();
|
||||
|
||||
@ -139,18 +149,15 @@ class _EvaluateVisitor
|
||||
/// invocations, and imports surrounding the current context.
|
||||
final _stack = <Frame>[];
|
||||
|
||||
/// The resolver to use for `package:` URLs, or `null` if no resolver exists.
|
||||
final SyncPackageResolver _packageResolver;
|
||||
|
||||
_EvaluateVisitor(
|
||||
{Iterable<String> loadPaths,
|
||||
{Iterable<Importer> importers,
|
||||
Importer importer,
|
||||
Environment environment,
|
||||
bool color: false,
|
||||
SyncPackageResolver packageResolver})
|
||||
: _loadPaths = loadPaths == null ? const [] : new List.from(loadPaths),
|
||||
bool color: false})
|
||||
: _importers = importers == null ? const [] : importers.toList(),
|
||||
_importer = importer ?? Importer.noOp,
|
||||
_environment = environment ?? new Environment(),
|
||||
_color = color,
|
||||
_packageResolver = packageResolver {
|
||||
_color = color {
|
||||
_environment.defineFunction("variable-exists", r"$name", (arguments) {
|
||||
var variable = arguments[0].assertString("name");
|
||||
return new SassBoolean(_environment.variableExists(variable.text));
|
||||
@ -207,12 +214,25 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
EvaluateResult run(Stylesheet node) {
|
||||
if (node.span?.sourceUrl != null) {
|
||||
_activeImports.add(node.span.sourceUrl);
|
||||
_importedFiles[node.span.sourceUrl] = node;
|
||||
_baseUrl = node.span?.sourceUrl;
|
||||
if (_baseUrl != null) {
|
||||
if (_importer is FilesystemImporter) {
|
||||
_includedFiles.add(p.fromUri(_baseUrl));
|
||||
} else {
|
||||
_includedFiles.add(_baseUrl.toString());
|
||||
}
|
||||
|
||||
var canonicalUrl = _importer?.canonicalize(_baseUrl);
|
||||
if (canonicalUrl != null) {
|
||||
_activeImports.add(canonicalUrl);
|
||||
_importCache[canonicalUrl] = node;
|
||||
}
|
||||
}
|
||||
_baseUrl ??= new Uri(path: '.');
|
||||
|
||||
visitStylesheet(node);
|
||||
return new EvaluateResult(_root, new MapKeySet(_importedFiles));
|
||||
|
||||
return new EvaluateResult(_root, _includedFiles);
|
||||
}
|
||||
|
||||
// ## Statements
|
||||
@ -582,7 +602,9 @@ class _EvaluateVisitor
|
||||
|
||||
/// Adds the stylesheet imported by [import] to the current document.
|
||||
void _visitDynamicImport(DynamicImport import) {
|
||||
var stylesheet = _loadImport(import);
|
||||
var result = _loadImport(import);
|
||||
var importer = result.item1;
|
||||
var stylesheet = result.item2;
|
||||
|
||||
var url = stylesheet.span.sourceUrl;
|
||||
if (_activeImports.contains(url)) {
|
||||
@ -592,89 +614,86 @@ class _EvaluateVisitor
|
||||
_activeImports.add(url);
|
||||
_withStackFrame("@import", import.span, () {
|
||||
_withEnvironment(_environment.global(), () {
|
||||
var oldImporter = _importer;
|
||||
var oldBaseUrl = _baseUrl;
|
||||
_importer = importer;
|
||||
_baseUrl = url;
|
||||
for (var statement in stylesheet.children) {
|
||||
statement.accept(this);
|
||||
}
|
||||
_importer = oldImporter;
|
||||
_baseUrl = oldBaseUrl;
|
||||
});
|
||||
});
|
||||
_activeImports.remove(url);
|
||||
}
|
||||
|
||||
/// Returns [import]'s URL, resolved to a `file:` URL if possible.
|
||||
Uri _resolveImportUrl(DynamicImport import) {
|
||||
var packageUrl = import.url;
|
||||
if (packageUrl.scheme != 'package') return packageUrl;
|
||||
|
||||
if (_packageResolver == null) {
|
||||
throw _exception(
|
||||
'"package:" URLs aren\'t supported on this platform.', import.span);
|
||||
}
|
||||
|
||||
var resolvedPackageUrl = _packageResolver.resolveUri(packageUrl);
|
||||
if (resolvedPackageUrl != null) return resolvedPackageUrl;
|
||||
|
||||
throw _exception("Unknown package.", import.span);
|
||||
}
|
||||
|
||||
/// Loads the [Stylesheet] imported by [import], or throws a
|
||||
/// [SassRuntimeException] if loading fails.
|
||||
Stylesheet _loadImport(DynamicImport import) {
|
||||
var path = _importPaths.putIfAbsent(import, () {
|
||||
var path = p.fromUri(_resolveImportUrl(import));
|
||||
var extension = p.extension(path);
|
||||
var tryPath = extension == '.sass' || extension == '.scss'
|
||||
? _tryImportPath
|
||||
: _tryImportPathWithExtensions;
|
||||
|
||||
if (import.span.file.url != null) {
|
||||
var base = p.dirname(p.fromUri(import.span.file.url));
|
||||
|
||||
var resolved = tryPath(p.join(base, path));
|
||||
if (resolved != null) return resolved;
|
||||
Tuple2<Importer, Stylesheet> _loadImport(DynamicImport import) {
|
||||
try {
|
||||
// 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 (stylesheet != null) return new Tuple2(_importer, stylesheet);
|
||||
}
|
||||
|
||||
for (var loadPath in _loadPaths) {
|
||||
var resolved = tryPath(p.join(loadPath, path));
|
||||
if (resolved != null) return resolved;
|
||||
for (var importer in _importers) {
|
||||
var stylesheet = _tryImport(importer, import.url);
|
||||
if (stylesheet != null) return new Tuple2(importer, stylesheet);
|
||||
}
|
||||
});
|
||||
|
||||
if (path == null) {
|
||||
throw _exception("Can't find file to import.", import.span);
|
||||
}
|
||||
|
||||
var url = p.toUri(path);
|
||||
return _importedFiles.putIfAbsent(url, () {
|
||||
String contents;
|
||||
if (import.url.scheme == 'package') {
|
||||
// Special-case this error message, since it's tripped people up in the
|
||||
// past.
|
||||
throw "\"package:\" URLs aren't supported on this platform.";
|
||||
} else {
|
||||
throw "Can't find stylesheet to import.";
|
||||
}
|
||||
} on SassException catch (error) {
|
||||
var frames = error.trace.frames.toList()
|
||||
..add(_stackFrame(import.span))
|
||||
..addAll(_stack.toList());
|
||||
throw new SassRuntimeException(
|
||||
error.message, error.span, new Trace(frames));
|
||||
} catch (error) {
|
||||
String message;
|
||||
try {
|
||||
contents = readFile(path);
|
||||
} on SassException catch (error) {
|
||||
var frames = _stack.toList()..add(_stackFrame(import.span));
|
||||
throw new SassRuntimeException(
|
||||
error.message, error.span, new Trace(frames));
|
||||
} on FileSystemException catch (error) {
|
||||
throw _exception(error.message, import.span);
|
||||
message = error.message as String;
|
||||
} catch (_) {
|
||||
message = error.toString();
|
||||
}
|
||||
|
||||
return p.extension(path) == '.sass'
|
||||
? new Stylesheet.parseSass(contents, url: url, color: _color)
|
||||
: new Stylesheet.parseScss(contents, url: url, color: _color);
|
||||
});
|
||||
throw _exception(message, import.span);
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [_tryImportPath], but checks both `.sass` and `.scss` extensions.
|
||||
String _tryImportPathWithExtensions(String path) =>
|
||||
_tryImportPath(path + '.sass') ?? _tryImportPath(path + '.scss');
|
||||
/// Parses the contents of [result] into a [Stylesheet].
|
||||
Stylesheet _tryImport(Importer importer, Uri url) {
|
||||
// TODO(nweiz): Measure to see if it's worth caching this, too.
|
||||
var canonicalUrl = importer.canonicalize(url);
|
||||
if (canonicalUrl == null) return null;
|
||||
|
||||
/// If a file exists at [path], or a partial with the same name exists,
|
||||
/// returns the resolved path.
|
||||
///
|
||||
/// Otherwise, returns `null`.
|
||||
String _tryImportPath(String path) {
|
||||
var partial = p.join(p.dirname(path), "_${p.basename(path)}");
|
||||
if (fileExists(partial)) return partial;
|
||||
if (fileExists(path)) return path;
|
||||
return null;
|
||||
return _importCache.putIfAbsent(canonicalUrl, () {
|
||||
var result = importer.load(canonicalUrl);
|
||||
if (result == null) return null;
|
||||
|
||||
if (importer is FilesystemImporter) {
|
||||
_includedFiles.add(p.fromUri(canonicalUrl));
|
||||
} else {
|
||||
_includedFiles.add(url.toString());
|
||||
}
|
||||
|
||||
// Use the canonicalized basename so that we display e.g.
|
||||
// package:example/_example.scss rather than package:example/example in
|
||||
// stack traces.
|
||||
var displayUrl = url.resolve(p.basename(canonicalUrl.path));
|
||||
return result.isIndented
|
||||
? new Stylesheet.parseSass(result.contents,
|
||||
url: displayUrl, color: _color)
|
||||
: new Stylesheet.parseScss(result.contents,
|
||||
url: displayUrl, color: _color);
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a CSS import for [import].
|
||||
@ -1676,9 +1695,12 @@ class EvaluateResult {
|
||||
/// The CSS syntax tree.
|
||||
final CssStylesheet stylesheet;
|
||||
|
||||
/// The URLs that were loaded during the compilation, including the main
|
||||
/// file's.
|
||||
final Set<Uri> includedUrls;
|
||||
/// The set that will eventually populate the JS API's
|
||||
/// `result.stats.includedFiles` field.
|
||||
///
|
||||
/// For filesystem imports, this contains the import path. For all other
|
||||
/// imports, it contains the URL passed to the `@import`.
|
||||
final Set<String> includedFiles;
|
||||
|
||||
EvaluateResult(this.stylesheet, this.includedUrls);
|
||||
EvaluateResult(this.stylesheet, this.includedFiles);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ dependencies:
|
||||
charcode: "^1.1.0"
|
||||
collection: "^1.8.0"
|
||||
convert: "^2.0.1"
|
||||
meta: "^1.0.0"
|
||||
path: "^1.0.0"
|
||||
source_span: "^1.4.0"
|
||||
string_scanner: ">=0.1.5 <2.0.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user