Add importer infrastructure

This isn't yet exposed by any public-facing API.
This commit is contained in:
Natalie Weizenbaum 2017-10-05 17:25:14 -07:00 committed by Natalie Weizenbaum
parent e79bba4fae
commit ed1d6ef6b1
10 changed files with 374 additions and 113 deletions

View File

@ -3,6 +3,9 @@
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'ast/sass.dart'; import 'ast/sass.dart';
import 'importer.dart';
import 'importer/filesystem.dart';
import 'importer/package.dart';
import 'io.dart'; import 'io.dart';
import 'sync_package_resolver.dart'; import 'sync_package_resolver.dart';
import 'util/path.dart'; import 'util/path.dart';
@ -14,6 +17,7 @@ import 'visitor/serialize.dart';
CompileResult compile(String path, CompileResult compile(String path,
{bool indented, {bool indented,
bool color: false, bool color: false,
Iterable<Importer> importers,
SyncPackageResolver packageResolver, SyncPackageResolver packageResolver,
Iterable<String> loadPaths, Iterable<String> loadPaths,
OutputStyle style, OutputStyle style,
@ -23,9 +27,11 @@ CompileResult compile(String path,
compileString(readFile(path), compileString(readFile(path),
indented: indented ?? p.extension(path) == '.sass', indented: indented ?? p.extension(path) == '.sass',
color: color, color: color,
importers: importers,
packageResolver: packageResolver, packageResolver: packageResolver,
style: style,
loadPaths: loadPaths, loadPaths: loadPaths,
importer: new FilesystemImporter('.'),
style: style,
useSpaces: useSpaces, useSpaces: useSpaces,
indentWidth: indentWidth, indentWidth: indentWidth,
lineFeed: lineFeed, lineFeed: lineFeed,
@ -36,8 +42,10 @@ CompileResult compile(String path,
CompileResult compileString(String source, CompileResult compileString(String source,
{bool indented: false, {bool indented: false,
bool color: false, bool color: false,
Iterable<Importer> importers,
SyncPackageResolver packageResolver, SyncPackageResolver packageResolver,
Iterable<String> loadPaths, Iterable<String> loadPaths,
Importer importer,
OutputStyle style, OutputStyle style,
bool useSpaces: true, bool useSpaces: true,
int indentWidth, int indentWidth,
@ -46,15 +54,24 @@ CompileResult compileString(String source,
var sassTree = indented var sassTree = indented
? new Stylesheet.parseSass(source, url: url, color: color) ? new Stylesheet.parseSass(source, url: url, color: color)
: new Stylesheet.parseScss(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, var evaluateResult = evaluate(sassTree,
color: color, packageResolver: packageResolver, loadPaths: loadPaths); importers: importerList, importer: importer, color: color);
var css = serialize(evaluateResult.stylesheet, var css = serialize(evaluateResult.stylesheet,
style: style, style: style,
useSpaces: useSpaces, useSpaces: useSpaces,
indentWidth: indentWidth, indentWidth: indentWidth,
lineFeed: lineFeed); 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 /// The result of compiling a Sass document to CSS, along with metadata about
@ -63,9 +80,12 @@ class CompileResult {
/// The compiled CSS. /// The compiled CSS.
final String css; final String css;
/// The URLs that were loaded during the compilation, including the main /// The set that will eventually populate the JS API's
/// file's. /// `result.stats.includedFiles` field.
final Set<Uri> includedUrls; ///
/// 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);
} }

View File

@ -58,12 +58,10 @@ main(List<String> args) async {
try { try {
String css; String css;
if (stdinFlag) { if (stdinFlag) {
css = compileString(await readStdin(), color: color); css = await _compileStdin();
} else { } else {
var input = options.rest.first; var input = options.rest.first;
css = input == '-' css = input == '-' ? await _compileStdin() : compile(input, color: color);
? compileString(await readStdin(), color: color)
: compile(input, color: color);
} }
if (css.isNotEmpty) print(css); if (css.isNotEmpty) print(css);
@ -124,6 +122,11 @@ Future<String> _loadVersion() async {
.last; .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. /// Print the usage information for Sass, with [message] as a header.
void _printUsage(ArgParser parser, String message) { void _printUsage(ArgParser parser, String message) {
print("$message\n"); print("$message\n");

66
lib/src/importer.dart Normal file
View 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);
}

View 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;
}

View 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)";
}

View 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:...";
}

View 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');
}
}
}

View File

@ -103,7 +103,7 @@ RenderResult _doRender(RenderOptions options) {
start: start.millisecondsSinceEpoch, start: start.millisecondsSinceEpoch,
end: end.millisecondsSinceEpoch, end: end.millisecondsSinceEpoch,
duration: end.difference(start).inMilliseconds, duration: end.difference(start).inMilliseconds,
includedFiles: result.includedUrls.map((url) => p.fromUri(url)).toList()); includedFiles: result.includedFiles.toList());
} }
/// Converts a [SassException] to a [RenderError]. /// Converts a [SassException] to a [RenderError].

View File

@ -18,9 +18,10 @@ import '../color_names.dart';
import '../environment.dart'; import '../environment.dart';
import '../exception.dart'; import '../exception.dart';
import '../extend/extender.dart'; import '../extend/extender.dart';
import '../importer.dart';
import '../importer/filesystem.dart';
import '../io.dart'; import '../io.dart';
import '../parse/keyframe_selector.dart'; import '../parse/keyframe_selector.dart';
import '../sync_package_resolver.dart';
import '../utils.dart'; import '../utils.dart';
import '../util/path.dart'; import '../util/path.dart';
import '../value.dart'; import '../value.dart';
@ -43,24 +44,27 @@ final _noSourceUrl = Uri.parse("-");
/// ///
/// If [color] is `true`, this will use terminal colors in warnings. /// 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. /// Throws a [SassRuntimeException] if evaluation fails.
EvaluateResult evaluate(Stylesheet stylesheet, EvaluateResult evaluate(Stylesheet stylesheet,
{Iterable<String> loadPaths, {Iterable<Importer> importers,
Importer importer,
Environment environment, Environment environment,
bool color: false, bool color: false}) =>
SyncPackageResolver packageResolver}) =>
new _EvaluateVisitor( new _EvaluateVisitor(
loadPaths: loadPaths, importers: importers,
importer: importer,
environment: environment, environment: environment,
color: color, color: color)
packageResolver: packageResolver)
.run(stylesheet); .run(stylesheet);
/// A visitor that executes Sass code to produce a CSS tree. /// A visitor that executes Sass code to produce a CSS tree.
class _EvaluateVisitor class _EvaluateVisitor
implements StatementVisitor<Value>, ExpressionVisitor<Value> { implements StatementVisitor<Value>, ExpressionVisitor<Value> {
/// The paths to search for Sass files being imported. /// The importers to use when loading new Sass files.
final List<String> _loadPaths; final List<Importer> _importers;
/// Whether to use terminal colors in warnings. /// Whether to use terminal colors in warnings.
final bool _color; final bool _color;
@ -68,6 +72,15 @@ class _EvaluateVisitor
/// The current lexical environment. /// The current lexical environment.
Environment _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. /// The style rule that defines the current parent selector, if any.
CssStyleRule _styleRule; CssStyleRule _styleRule;
@ -117,18 +130,15 @@ class _EvaluateVisitor
/// the stylesheet has been fully performed. /// the stylesheet has been fully performed.
var _outOfOrderImports = <CssImport>[]; var _outOfOrderImports = <CssImport>[];
/// The resolved URLs for each [DynamicImport] that's been seen so far. /// The parsed stylesheets for each canonicalized import URL.
/// final _importCache = <Uri, Stylesheet>{};
/// 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 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 /// For filesystem imports, this contains the import path. For all other
/// import the same stylesheet, and we don't want to parse the same stylesheet /// imports, it contains the URL passed to the `@import`.
/// multiple times. final _includedFiles = new Set<String>();
final _importedFiles = <Uri, Stylesheet>{};
final _activeImports = new Set<Uri>(); final _activeImports = new Set<Uri>();
@ -139,18 +149,15 @@ class _EvaluateVisitor
/// invocations, and imports surrounding the current context. /// invocations, and imports surrounding the current context.
final _stack = <Frame>[]; final _stack = <Frame>[];
/// The resolver to use for `package:` URLs, or `null` if no resolver exists.
final SyncPackageResolver _packageResolver;
_EvaluateVisitor( _EvaluateVisitor(
{Iterable<String> loadPaths, {Iterable<Importer> importers,
Importer importer,
Environment environment, Environment environment,
bool color: false, bool color: false})
SyncPackageResolver packageResolver}) : _importers = importers == null ? const [] : importers.toList(),
: _loadPaths = loadPaths == null ? const [] : new List.from(loadPaths), _importer = importer ?? Importer.noOp,
_environment = environment ?? new Environment(), _environment = environment ?? new Environment(),
_color = color, _color = color {
_packageResolver = packageResolver {
_environment.defineFunction("variable-exists", r"$name", (arguments) { _environment.defineFunction("variable-exists", r"$name", (arguments) {
var variable = arguments[0].assertString("name"); var variable = arguments[0].assertString("name");
return new SassBoolean(_environment.variableExists(variable.text)); return new SassBoolean(_environment.variableExists(variable.text));
@ -207,12 +214,25 @@ class _EvaluateVisitor
} }
EvaluateResult run(Stylesheet node) { EvaluateResult run(Stylesheet node) {
if (node.span?.sourceUrl != null) { _baseUrl = node.span?.sourceUrl;
_activeImports.add(node.span.sourceUrl); if (_baseUrl != null) {
_importedFiles[node.span.sourceUrl] = node; 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); visitStylesheet(node);
return new EvaluateResult(_root, new MapKeySet(_importedFiles));
return new EvaluateResult(_root, _includedFiles);
} }
// ## Statements // ## Statements
@ -582,7 +602,9 @@ class _EvaluateVisitor
/// Adds the stylesheet imported by [import] to the current document. /// Adds the stylesheet imported by [import] to the current document.
void _visitDynamicImport(DynamicImport import) { 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; var url = stylesheet.span.sourceUrl;
if (_activeImports.contains(url)) { if (_activeImports.contains(url)) {
@ -592,91 +614,88 @@ class _EvaluateVisitor
_activeImports.add(url); _activeImports.add(url);
_withStackFrame("@import", import.span, () { _withStackFrame("@import", import.span, () {
_withEnvironment(_environment.global(), () { _withEnvironment(_environment.global(), () {
var oldImporter = _importer;
var oldBaseUrl = _baseUrl;
_importer = importer;
_baseUrl = url;
for (var statement in stylesheet.children) { for (var statement in stylesheet.children) {
statement.accept(this); statement.accept(this);
} }
_importer = oldImporter;
_baseUrl = oldBaseUrl;
}); });
}); });
_activeImports.remove(url); _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 /// Loads the [Stylesheet] imported by [import], or throws a
/// [SassRuntimeException] if loading fails. /// [SassRuntimeException] if loading fails.
Stylesheet _loadImport(DynamicImport import) { Tuple2<Importer, 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;
}
for (var loadPath in _loadPaths) {
var resolved = tryPath(p.join(loadPath, path));
if (resolved != null) return resolved;
}
});
if (path == null) {
throw _exception("Can't find file to import.", import.span);
}
var url = p.toUri(path);
return _importedFiles.putIfAbsent(url, () {
String contents;
try { try {
contents = readFile(path); // 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 importer in _importers) {
var stylesheet = _tryImport(importer, import.url);
if (stylesheet != null) return new Tuple2(importer, stylesheet);
}
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) { } on SassException catch (error) {
var frames = _stack.toList()..add(_stackFrame(import.span)); var frames = error.trace.frames.toList()
..add(_stackFrame(import.span))
..addAll(_stack.toList());
throw new SassRuntimeException( throw new SassRuntimeException(
error.message, error.span, new Trace(frames)); error.message, error.span, new Trace(frames));
} on FileSystemException catch (error) { } catch (error) {
throw _exception(error.message, import.span); String message;
try {
message = error.message as String;
} catch (_) {
message = error.toString();
}
throw _exception(message, import.span);
}
} }
return p.extension(path) == '.sass' /// Parses the contents of [result] into a [Stylesheet].
? new Stylesheet.parseSass(contents, url: url, color: _color) Stylesheet _tryImport(Importer importer, Uri url) {
: new Stylesheet.parseScss(contents, url: url, color: _color); // TODO(nweiz): Measure to see if it's worth caching this, too.
var canonicalUrl = importer.canonicalize(url);
if (canonicalUrl == null) 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);
}); });
} }
/// Like [_tryImportPath], but checks both `.sass` and `.scss` extensions.
String _tryImportPathWithExtensions(String path) =>
_tryImportPath(path + '.sass') ?? _tryImportPath(path + '.scss');
/// 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;
}
/// Adds a CSS import for [import]. /// Adds a CSS import for [import].
void _visitStaticImport(StaticImport import) { void _visitStaticImport(StaticImport import) {
var url = _interpolationToValue(import.url); var url = _interpolationToValue(import.url);
@ -1676,9 +1695,12 @@ class EvaluateResult {
/// The CSS syntax tree. /// The CSS syntax tree.
final CssStylesheet stylesheet; final CssStylesheet stylesheet;
/// The URLs that were loaded during the compilation, including the main /// The set that will eventually populate the JS API's
/// file's. /// `result.stats.includedFiles` field.
final Set<Uri> includedUrls; ///
/// 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);
} }

View File

@ -15,6 +15,7 @@ dependencies:
charcode: "^1.1.0" charcode: "^1.1.0"
collection: "^1.8.0" collection: "^1.8.0"
convert: "^2.0.1" convert: "^2.0.1"
meta: "^1.0.0"
path: "^1.0.0" path: "^1.0.0"
source_span: "^1.4.0" source_span: "^1.4.0"
string_scanner: ">=0.1.5 <2.0.0" string_scanner: ">=0.1.5 <2.0.0"