mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 05:15:04 +01:00
parent
ed1d6ef6b1
commit
a003e5c31f
16
CHANGELOG.md
16
CHANGELOG.md
@ -5,6 +5,22 @@
|
||||
* Don't crash when evaluating CSS variables whose names are entirely
|
||||
interpolated (for example, `#{--foo}: ...`).
|
||||
|
||||
### Dart API
|
||||
|
||||
* Added an `Importer` class. This can be extended by users to provide support
|
||||
for custom resolution for `@import` rules.
|
||||
|
||||
* Added built-in `FilesystemImporter` and `PackageImporter` implementations that
|
||||
support resolving `file:` and `package:` URLs, respectively.
|
||||
|
||||
* Added an `importers` argument to the `compile()` and `compileString()`
|
||||
functions that provides `Importer`s to use when resolving `@import` rules.
|
||||
|
||||
* Added a `loadPaths` argument to the `compile()` and `compileString()`
|
||||
functions that provides paths to search for stylesheets when resolving
|
||||
`@import` rules. This is a shorthand for passing `FilesystemImporter`s to the
|
||||
`importers` argument.
|
||||
|
||||
## 1.0.0-beta.2
|
||||
|
||||
* Add support for the `::slotted()` pseudo-element.
|
||||
|
@ -4,22 +4,46 @@
|
||||
|
||||
import 'src/compile.dart' as c;
|
||||
import 'src/exception.dart';
|
||||
import 'src/importer.dart';
|
||||
import 'src/sync_package_resolver.dart';
|
||||
|
||||
export 'src/importer.dart';
|
||||
export 'src/importer/filesystem.dart';
|
||||
export 'src/importer/package.dart';
|
||||
export 'src/importer/result.dart';
|
||||
|
||||
/// Loads the Sass file at [path], compiles it to CSS, and returns the result.
|
||||
///
|
||||
/// If [color] is `true`, this will use terminal colors in warnings.
|
||||
///
|
||||
/// 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.
|
||||
/// Imports are resolved by trying, in order:
|
||||
///
|
||||
/// * Loading a file relative to [path].
|
||||
///
|
||||
/// * Each importer in [importers].
|
||||
///
|
||||
/// * Each load path in [loadPaths]. Note that this is a shorthand for adding
|
||||
/// [FilesystemImporter]s to [importers].
|
||||
///
|
||||
/// * `package:` resolution using [packageResolver], which is a
|
||||
/// [SyncPackageResolver][] from the `package_resolver` package. Note that
|
||||
/// this is a shorthand for adding a [PackageImporter] to [importers].
|
||||
///
|
||||
/// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html
|
||||
///
|
||||
/// Throws a [SassException] if conversion fails.
|
||||
String compile(String path,
|
||||
{bool color: false, SyncPackageResolver packageResolver}) =>
|
||||
c.compile(path, color: color, packageResolver: packageResolver).css;
|
||||
{bool color: false,
|
||||
Iterable<Importer> importers,
|
||||
Iterable<String> loadPaths,
|
||||
SyncPackageResolver packageResolver}) {
|
||||
var result = c.compile(path,
|
||||
color: color,
|
||||
importers: importers,
|
||||
loadPaths: loadPaths,
|
||||
packageResolver: packageResolver);
|
||||
return result.css;
|
||||
}
|
||||
|
||||
/// Compiles [source] to CSS and returns the result.
|
||||
///
|
||||
@ -27,25 +51,41 @@ String compile(String path,
|
||||
/// otherwise (and by default) it uses SCSS. If [color] is `true`, this will use
|
||||
/// terminal colors in warnings.
|
||||
///
|
||||
/// 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.
|
||||
/// Imports are resolved by trying, in order:
|
||||
///
|
||||
/// * The given [importer], with the imported URL resolved relative to [url].
|
||||
///
|
||||
/// * Each importer in [importers].
|
||||
///
|
||||
/// * Each load path in [loadPaths]. Note that this is a shorthand for adding
|
||||
/// [FilesystemImporter]s to [importers].
|
||||
///
|
||||
/// * `package:` resolution using [packageResolver], which is a
|
||||
/// [SyncPackageResolver][] from the `package_resolver` package. Note that
|
||||
/// this is a shorthand for adding a [PackageImporter] to [importers].
|
||||
///
|
||||
/// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html
|
||||
///
|
||||
/// The [url] indicates the location from which [source] was loaded. It may may
|
||||
/// be a [String] or a [Uri].
|
||||
/// The [url] indicates the location from which [source] was loaded. It may be a
|
||||
/// [String] or a [Uri]. If [importer] is passed, [url] must be passed as well
|
||||
/// and `importer.load(url)` should return `source`.
|
||||
///
|
||||
/// Throws a [SassException] if conversion fails.
|
||||
String compileString(String source,
|
||||
{bool indented: false,
|
||||
bool color: false,
|
||||
Iterable<Importer> importers,
|
||||
Iterable<String> loadPaths,
|
||||
SyncPackageResolver packageResolver,
|
||||
Importer importer,
|
||||
url}) {
|
||||
var result = c.compileString(source,
|
||||
indented: indented,
|
||||
color: color,
|
||||
importers: importers,
|
||||
loadPaths: loadPaths,
|
||||
packageResolver: packageResolver,
|
||||
importer: importer,
|
||||
url: url);
|
||||
return result.css;
|
||||
}
|
||||
|
113
test/dart_api/importer_test.dart
Normal file
113
test/dart_api/importer_test.dart
Normal file
@ -0,0 +1,113 @@
|
||||
// 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.
|
||||
|
||||
@TestOn('vm')
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:sass/sass.dart';
|
||||
import 'package:sass/src/exception.dart';
|
||||
|
||||
main() {
|
||||
test("uses an importer to resolve an @import", () {
|
||||
var css = compileString('@import "orange";', importers: [
|
||||
new _TestImporter((url) => url, (url) {
|
||||
return new ImporterResult('.$url {color: $url}', indented: false);
|
||||
})
|
||||
]);
|
||||
|
||||
expect(css, equals(".orange {\n color: orange;\n}"));
|
||||
});
|
||||
|
||||
test("passes the canonicalized URL to the importer", () {
|
||||
var css = compileString('@import "orange";', importers: [
|
||||
new _TestImporter((url) => new Uri(path: 'blue'), (url) {
|
||||
return new ImporterResult('.$url {color: $url}', indented: false);
|
||||
})
|
||||
]);
|
||||
|
||||
expect(css, equals(".blue {\n color: blue;\n}"));
|
||||
});
|
||||
|
||||
test("only invokes the importer once for a given canonicalization", () {
|
||||
var css = compileString("""
|
||||
@import "orange";
|
||||
@import "orange";
|
||||
""", importers: [
|
||||
new _TestImporter(
|
||||
(url) => new Uri(path: 'blue'),
|
||||
expectAsync1((url) {
|
||||
return new ImporterResult('.$url {color: $url}', indented: false);
|
||||
}, count: 1))
|
||||
]);
|
||||
|
||||
expect(css, equals("""
|
||||
.blue {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: blue;
|
||||
}"""));
|
||||
});
|
||||
|
||||
test("wraps an error in canonicalize()", () {
|
||||
expect(() {
|
||||
compileString('@import "orange";', importers: [
|
||||
new _TestImporter((url) {
|
||||
throw "this import is bad actually";
|
||||
}, expectAsync1((_) => null, count: 0))
|
||||
]);
|
||||
}, throwsA(predicate((error) {
|
||||
expect(error, new isInstanceOf<SassException>());
|
||||
expect(
|
||||
error.toString(), startsWith("Error: this import is bad actually"));
|
||||
return true;
|
||||
})));
|
||||
});
|
||||
|
||||
test("wraps an error in load()", () {
|
||||
expect(() {
|
||||
compileString('@import "orange";', importers: [
|
||||
new _TestImporter((url) => url, (url) {
|
||||
throw "this import is bad actually";
|
||||
})
|
||||
]);
|
||||
}, throwsA(predicate((error) {
|
||||
expect(error, new isInstanceOf<SassException>());
|
||||
expect(
|
||||
error.toString(), startsWith("Error: this import is bad actually"));
|
||||
return true;
|
||||
})));
|
||||
});
|
||||
|
||||
test("prefers .message to .toString() for an importer error", () {
|
||||
expect(() {
|
||||
compileString('@import "orange";', importers: [
|
||||
new _TestImporter((url) => url, (url) {
|
||||
throw new FormatException("bad format somehow");
|
||||
})
|
||||
]);
|
||||
}, throwsA(predicate((error) {
|
||||
expect(error, new isInstanceOf<SassException>());
|
||||
// FormatException.toString() starts with "FormatException:", but
|
||||
// the error message should not.
|
||||
expect(error.toString(), startsWith("Error: bad format somehow"));
|
||||
return true;
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/// An [Importer] whose [canonicalize] and [load] methods are provided by
|
||||
/// closures.
|
||||
class _TestImporter extends Importer {
|
||||
final Uri Function(Uri url) _canonicalize;
|
||||
final ImporterResult Function(Uri url) _load;
|
||||
|
||||
_TestImporter(this._canonicalize, this._load);
|
||||
|
||||
Uri canonicalize(Uri url) => _canonicalize(url);
|
||||
|
||||
ImporterResult load(Uri url) => _load(url);
|
||||
}
|
175
test/dart_api_test.dart
Normal file
175
test/dart_api_test.dart
Normal file
@ -0,0 +1,175 @@
|
||||
// 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.
|
||||
|
||||
@TestOn('vm')
|
||||
|
||||
import 'package:package_resolver/package_resolver.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_descriptor/test_descriptor.dart' as d;
|
||||
|
||||
import 'package:sass/sass.dart';
|
||||
import 'package:sass/src/exception.dart';
|
||||
import 'package:sass/src/util/path.dart';
|
||||
|
||||
main() {
|
||||
group("importers", () {
|
||||
test("is used to resolve imports", () async {
|
||||
await d.dir("subdir", [d.file("subtest.scss", "a {b: c}")]).create();
|
||||
await d.file("test.scss", '@import "subtest.scss";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
importers: [new FilesystemImporter(p.join(d.sandbox, 'subdir'))]);
|
||||
expect(css, equals("a {\n b: c;\n}"));
|
||||
});
|
||||
|
||||
test("are checked in order", () async {
|
||||
await d
|
||||
.dir("first", [d.file("other.scss", "a {b: from-first}")]).create();
|
||||
await d
|
||||
.dir("second", [d.file("other.scss", "a {b: from-second}")]).create();
|
||||
await d.file("test.scss", '@import "other";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"), importers: [
|
||||
new FilesystemImporter(p.join(d.sandbox, 'first')),
|
||||
new FilesystemImporter(p.join(d.sandbox, 'second'))
|
||||
]);
|
||||
expect(css, equals("a {\n b: from-first;\n}"));
|
||||
});
|
||||
});
|
||||
|
||||
group("loadPaths", () {
|
||||
test("is used to import file: URLs", () async {
|
||||
await d.dir("subdir", [d.file("subtest.scss", "a {b: c}")]).create();
|
||||
await d.file("test.scss", '@import "subtest.scss";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
loadPaths: [p.join(d.sandbox, 'subdir')]);
|
||||
expect(css, equals("a {\n b: c;\n}"));
|
||||
});
|
||||
|
||||
test("can import partials", () async {
|
||||
await d.dir("subdir", [d.file("_subtest.scss", "a {b: c}")]).create();
|
||||
await d.file("test.scss", '@import "subtest.scss";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
loadPaths: [p.join(d.sandbox, 'subdir')]);
|
||||
expect(css, equals("a {\n b: c;\n}"));
|
||||
});
|
||||
|
||||
test("adds a .scss extension", () async {
|
||||
await d.dir("subdir", [d.file("subtest.scss", "a {b: c}")]).create();
|
||||
await d.file("test.scss", '@import "subtest";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
loadPaths: [p.join(d.sandbox, 'subdir')]);
|
||||
expect(css, equals("a {\n b: c;\n}"));
|
||||
});
|
||||
|
||||
test("adds a .sass extension", () async {
|
||||
await d.dir("subdir", [d.file("subtest.sass", "a\n b: c")]).create();
|
||||
await d.file("test.scss", '@import "subtest";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
loadPaths: [p.join(d.sandbox, 'subdir')]);
|
||||
expect(css, equals("a {\n b: c;\n}"));
|
||||
});
|
||||
|
||||
test("are checked in order", () async {
|
||||
await d
|
||||
.dir("first", [d.file("other.scss", "a {b: from-first}")]).create();
|
||||
await d
|
||||
.dir("second", [d.file("other.scss", "a {b: from-second}")]).create();
|
||||
await d.file("test.scss", '@import "other";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
loadPaths: [p.join(d.sandbox, 'first'), p.join(d.sandbox, 'second')]);
|
||||
expect(css, equals("a {\n b: from-first;\n}"));
|
||||
});
|
||||
});
|
||||
|
||||
group("packageResolver", () {
|
||||
test("is used to import package: URLs", () async {
|
||||
await d.dir("subdir", [d.file("test.scss", "a {b: 1 + 2}")]).create();
|
||||
|
||||
await d
|
||||
.file("test.scss", '@import "package:fake_package/test";')
|
||||
.create();
|
||||
var resolver = new SyncPackageResolver.config(
|
||||
{"fake_package": p.toUri(p.join(d.sandbox, 'subdir'))});
|
||||
|
||||
var css =
|
||||
compile(p.join(d.sandbox, "test.scss"), packageResolver: resolver);
|
||||
expect(css, equals("a {\n b: 3;\n}"));
|
||||
});
|
||||
|
||||
test("doesn't import a package URL from a missing package", () async {
|
||||
await d
|
||||
.file("test.scss", '@import "package:fake_package/test_aux";')
|
||||
.create();
|
||||
var resolver = new SyncPackageResolver.config({});
|
||||
|
||||
expect(() => compile(d.sandbox + "/test.scss", packageResolver: resolver),
|
||||
throwsA(new isInstanceOf<SassRuntimeException>()));
|
||||
});
|
||||
});
|
||||
|
||||
group("import precedence", () {
|
||||
test("relative imports take precedence over importers", () async {
|
||||
await d.dir(
|
||||
"subdir", [d.file("other.scss", "a {b: from-load-path}")]).create();
|
||||
await d.file("other.scss", "a {b: from-relative}").create();
|
||||
await d.file("test.scss", '@import "other";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
importers: [new FilesystemImporter(p.join(d.sandbox, 'subdir'))]);
|
||||
expect(css, equals("a {\n b: from-relative;\n}"));
|
||||
});
|
||||
|
||||
test("the original importer takes precedence over other importers",
|
||||
() async {
|
||||
await d.dir(
|
||||
"original", [d.file("other.scss", "a {b: from-original}")]).create();
|
||||
await d
|
||||
.dir("other", [d.file("other.scss", "a {b: from-other}")]).create();
|
||||
|
||||
var css = compileString('@import "other";',
|
||||
importer: new FilesystemImporter(p.join(d.sandbox, 'original')),
|
||||
url: p.toUri(p.join(d.sandbox, 'original', 'test.scss')),
|
||||
importers: [new FilesystemImporter(p.join(d.sandbox, 'other'))]);
|
||||
expect(css, equals("a {\n b: from-original;\n}"));
|
||||
});
|
||||
|
||||
test("importers take precedence over load paths", () async {
|
||||
await d.dir("load-path",
|
||||
[d.file("other.scss", "a {b: from-load-path}")]).create();
|
||||
await d.dir(
|
||||
"importer", [d.file("other.scss", "a {b: from-importer}")]).create();
|
||||
await d.file("test.scss", '@import "other";').create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
importers: [new FilesystemImporter(p.join(d.sandbox, 'importer'))],
|
||||
loadPaths: [p.join(d.sandbox, 'load-path')]);
|
||||
expect(css, equals("a {\n b: from-importer;\n}"));
|
||||
});
|
||||
|
||||
test("importers take precedence over packageResolver", () async {
|
||||
await d.dir("package",
|
||||
[d.file("other.scss", "a {b: from-package-resolver}")]).create();
|
||||
await d.dir(
|
||||
"importer", [d.file("other.scss", "a {b: from-importer}")]).create();
|
||||
await d
|
||||
.file("test.scss", '@import "package:fake_package/other";')
|
||||
.create();
|
||||
|
||||
var css = compile(p.join(d.sandbox, "test.scss"),
|
||||
importers: [
|
||||
new PackageImporter(new SyncPackageResolver.config(
|
||||
{"fake_package": p.toUri(p.join(d.sandbox, 'importer'))}))
|
||||
],
|
||||
packageResolver: new SyncPackageResolver.config(
|
||||
{"fake_package": p.toUri(p.join(d.sandbox, 'package'))}));
|
||||
expect(css, equals("a {\n b: from-importer;\n}"));
|
||||
});
|
||||
});
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// 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.
|
||||
|
||||
@TestOn('vm')
|
||||
|
||||
import 'package:package_resolver/package_resolver.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_descriptor/test_descriptor.dart' as d;
|
||||
|
||||
import 'package:sass/sass.dart';
|
||||
import 'package:sass/src/exception.dart';
|
||||
import 'package:sass/src/util/path.dart';
|
||||
|
||||
main() {
|
||||
test("successfully imports a package URL", () async {
|
||||
await d.dir("subdir", [d.file("test.scss", "a {b: 1 + 2}")]).create();
|
||||
|
||||
await d.file("test.scss", '@import "package:fake_package/test";').create();
|
||||
var resolver = new SyncPackageResolver.config(
|
||||
{"fake_package": p.toUri(p.join(d.sandbox, 'subdir'))});
|
||||
|
||||
var css =
|
||||
compile(p.join(d.sandbox, "test.scss"), packageResolver: resolver);
|
||||
expect(css, equals("a {\n b: 3;\n}"));
|
||||
});
|
||||
|
||||
test("imports a package URL from a missing package", () async {
|
||||
await d
|
||||
.file("test.scss", '@import "package:fake_package/test_aux";')
|
||||
.create();
|
||||
var resolver = new SyncPackageResolver.config({});
|
||||
|
||||
expect(() => compile(d.sandbox + "/test.scss", packageResolver: resolver),
|
||||
throwsA(new isInstanceOf<SassRuntimeException>()));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user