Support package: URLs as a Dart library (#53)

This commit is contained in:
Luis Vargas 2017-02-03 04:08:06 -05:00 committed by Natalie Weizenbaum
parent be44245a84
commit cfc3a15041
10 changed files with 125 additions and 9 deletions

View File

@ -5,7 +5,7 @@
import 'package:path/path.dart' as p;
import 'src/ast/sass.dart';
import 'src/exception.dart';
import 'src/sync_package_resolver.dart';
import 'src/utils.dart';
import 'src/visitor/perform.dart';
import 'src/visitor/serialize.dart';
@ -14,13 +14,21 @@ import 'src/visitor/serialize.dart';
///
/// If [color] is `true`, this will use terminal colors in warnings.
///
/// Throws a [SassException] if conversion fails.
String render(String path, {bool color: false}) {
/// If [packageResolver] is provided, it's used to resolve `package:` imports.
/// Otherwise, they aren't supported. It takes a [SyncPackageResolver][] from
/// the `package_resolver` package.
///
/// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html
///
/// Finally throws a [SassException] if conversion fails.
String render(String path,
{bool color: false, SyncPackageResolver packageResolver}) {
var contents = readSassFile(path);
var url = p.toUri(path);
var sassTree = p.extension(path) == '.sass'
? new Stylesheet.parseSass(contents, url: url, color: color)
: new Stylesheet.parseScss(contents, url: url, color: color);
var cssTree = evaluate(sassTree, color: color);
var cssTree =
evaluate(sassTree, color: color, packageResolver: packageResolver);
return toCss(cssTree);
}

View File

@ -37,5 +37,8 @@ String readFileAsString(String path) => null;
/// Returns whether a file at [path] exists.
bool fileExists(String path) => null;
/// Returns whether a dir at [path] exists.
bool dirExists(String path) => null;
/// Gets and sets the exit code that the process will use when it exits.
int exitCode;

View File

@ -74,6 +74,8 @@ String _cleanErrorMessage(_SystemError error) {
bool fileExists(String path) => _fs.existsSync(path);
bool dirExists(String path) => _fs.existsSync(path);
@JS("process.stderr")
external _Stderr get _stderr;

View File

@ -15,3 +15,5 @@ List<int> readFileAsBytes(String path) => new io.File(path).readAsBytesSync();
String readFileAsString(String path) => new io.File(path).readAsStringSync();
bool fileExists(String path) => new io.File(path).existsSync();
bool dirExists(String path) => new io.Directory(path).existsSync();

View File

@ -0,0 +1,2 @@
export 'package:package_resolver/package_resolver.dart'
if (node) 'sync_package_resolver/node.dart';

View File

@ -0,0 +1,17 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
class SyncPackageResolver {
static final _error =
new UnsupportedError('SyncPackageResolver is not supported in JS.');
static Future<SyncPackageResolver> get current => throw _error;
Uri resolveUri(packageUri) => throw _error;
factory SyncPackageResolver.config(Map<String, Uri> configMap) =>
throw _error;
}

View File

@ -21,6 +21,7 @@ import '../exception.dart';
import '../extend/extender.dart';
import '../io.dart';
import '../parse/keyframe_selector.dart';
import '../sync_package_resolver.dart';
import '../utils.dart';
import '../value.dart';
import 'interface/statement.dart';
@ -43,9 +44,13 @@ typedef _ScopeCallback(callback());
CssStylesheet evaluate(Stylesheet stylesheet,
{Iterable<String> loadPaths,
Environment environment,
bool color: false}) =>
bool color: false,
SyncPackageResolver packageResolver}) =>
new _PerformVisitor(
loadPaths: loadPaths, environment: environment, color: color)
loadPaths: loadPaths,
environment: environment,
color: color,
packageResolver: packageResolver)
.run(stylesheet);
/// A visitor that executes Sass code to produce a CSS tree.
@ -131,11 +136,18 @@ class _PerformVisitor
/// 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;
_PerformVisitor(
{Iterable<String> loadPaths, Environment environment, bool color: false})
{Iterable<String> loadPaths,
Environment environment,
bool color: false,
SyncPackageResolver packageResolver})
: _loadPaths = loadPaths == null ? const [] : new List.from(loadPaths),
_environment = environment ?? new Environment(),
_color = color {
_color = color,
_packageResolver = packageResolver {
_environment.defineFunction("variable-exists", r"$name", (arguments) {
var variable = arguments[0].assertString("name");
return new SassBoolean(_environment.variableExists(variable.text));
@ -581,11 +593,27 @@ class _PerformVisitor
_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(import.url);
var path = p.fromUri(_resolveImportUrl(import));
var extension = p.extension(path);
var tryPath = extension == '.sass' || extension == '.scss'
? _tryImportPath

View File

@ -19,6 +19,7 @@ dependencies:
string_scanner: ">=0.1.5 <2.0.0"
stack_trace: ">=0.9.0 <2.0.0"
tuple: "^1.0.0"
package_resolver: ^1.0.0
dev_dependencies:
archive: "^1.0.0"

View File

@ -95,4 +95,17 @@ void sharedTests(ScheduledProcess runSass(List arguments)) {
sass.stderr.expect(consumeThrough(contains("\.dart")));
sass.shouldExit(65);
});
test("fails to import a package url", () {
d.file("test.scss", "@import 'package:nope/test';").create();
var sass = runSass(["test.scss", "test.css"]);
sass.stderr.expect(inOrder([
"Error: \"package:\" URLs aren't supported on this platform.",
"@import 'package:nope/test';",
" ^^^^^^^^^^^^^^^^^^^",
" test.scss 1:9 root stylesheet"
]));
sass.shouldExit(65);
});
}

View File

@ -0,0 +1,40 @@
// 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:package_resolver/package_resolver.dart';
import 'package:path/path.dart' as p;
import 'package:scheduled_test/descriptor.dart' as d;
import 'package:scheduled_test/scheduled_test.dart';
import 'package:sass/sass.dart';
import 'package:sass/src/exception.dart';
import 'utils.dart';
main() {
useSandbox();
test("successfully imports a package URL", () {
d.dir("subdir", [d.file("test.scss", "a {b: 1 + 2}")]).create();
d.file("test.scss", '@import "package:fake_package/test";').create();
var resolver = new SyncPackageResolver.config(
{"fake_package": p.toUri(p.join(sandbox, 'subdir'))});
schedule(() {
var css = render(p.join(sandbox, "test.scss"), packageResolver: resolver);
expect(css, equals("a {\n b: 3;\n}"));
});
});
test("imports a package URL from a missing package", () {
d.file("test.scss", '@import "package:fake_package/test_aux";').create();
var resolver = new SyncPackageResolver.config({});
schedule(() {
expect(() => render(sandbox + "/test.scss", packageResolver: resolver),
throwsA(new isInstanceOf<SassRuntimeException>()));
});
});
}