mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 21:31:11 +01:00
Add support for the fibers package to speed up render()
This commit is contained in:
parent
0a67d3845f
commit
aa5fd1d060
28
README.md
28
README.md
@ -113,10 +113,30 @@ That's it!
|
||||
When installed via NPM, Dart Sass supports a JavaScript API that aims to be
|
||||
compatible with [Node Sass](https://github.com/sass/node-sass#usage). Full
|
||||
compatibility is a work in progress, but Dart Sass currently supports the
|
||||
`render()` and `renderSync()` functions. Note however that **`renderSync()` is
|
||||
much faster than `render()`**, due to the overhead of asynchronous callbacks.
|
||||
It's highly recommended that users use `renderSync()` unless they absolutely
|
||||
require support for asynchronous importers.
|
||||
`render()` and `renderSync()` functions. Note however that by default,
|
||||
**`renderSync()` is more than twice as fast as `render()`**, due to the overhead
|
||||
of asynchronous callbacks.
|
||||
|
||||
To avoid this performance hit, `render()` can use the [`fibers`][fibers] package
|
||||
to call asynchronous importers from the synchronous code path. To enable this,
|
||||
pass the `Fiber` class to the `fiber` option:
|
||||
|
||||
[fibers]: https://www.npmjs.com/package/fibers
|
||||
|
||||
```js
|
||||
var sass = require("sass");
|
||||
var Fiber = require("fibers");
|
||||
|
||||
render({
|
||||
file: "input.scss",
|
||||
importer: function(url, prev, done) {
|
||||
// ...
|
||||
},
|
||||
fiber: Fiber
|
||||
}, function(err, result) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Both `render()` and `renderSync()` support the following options:
|
||||
|
||||
|
@ -48,15 +48,25 @@ void main() {
|
||||
/// [render]: https://github.com/sass/node-sass#options
|
||||
void _render(RenderOptions options,
|
||||
void callback(RenderError error, RenderResult result)) {
|
||||
_renderAsync(options).then((result) {
|
||||
callback(null, result);
|
||||
}, onError: (error, stackTrace) {
|
||||
if (error is SassException) {
|
||||
callback(_wrapException(error), null);
|
||||
} else {
|
||||
callback(newRenderError(error.toString(), status: 3), null);
|
||||
}
|
||||
});
|
||||
if (options.fiber != null) {
|
||||
options.fiber.call(allowInterop(() {
|
||||
try {
|
||||
callback(null, _renderSync(options));
|
||||
} catch (error) {
|
||||
callback(error as RenderError, null);
|
||||
}
|
||||
})).run();
|
||||
} else {
|
||||
_renderAsync(options).then((result) {
|
||||
callback(null, result);
|
||||
}, onError: (error, stackTrace) {
|
||||
if (error is SassException) {
|
||||
callback(_wrapException(error), null);
|
||||
} else {
|
||||
callback(newRenderError(error.toString(), status: 3), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts Sass to CSS asynchronously.
|
||||
@ -206,6 +216,23 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) {
|
||||
context.options.context = context;
|
||||
}
|
||||
|
||||
if (options.fiber != null) {
|
||||
importers = importers.map((importer) {
|
||||
return allowInteropCaptureThis((thisArg, String url, String previous,
|
||||
[_]) {
|
||||
var fiber = options.fiber.current;
|
||||
var result =
|
||||
call3(importer, thisArg, url, previous, allowInterop((result) {
|
||||
// Schedule a microtask so we don't try to resume the running fiber if
|
||||
// [importer] calls `done()` synchronously.
|
||||
scheduleMicrotask(() => fiber.run(result));
|
||||
}));
|
||||
if (isUndefined(result)) return options.fiber.yield();
|
||||
return result;
|
||||
}) as _Importer;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return new NodeImporter(context, includePaths, importers);
|
||||
}
|
||||
|
||||
|
22
lib/src/node/fiber.dart
Normal file
22
lib/src/node/fiber.dart
Normal file
@ -0,0 +1,22 @@
|
||||
// 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:js/js.dart';
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
class FiberClass {
|
||||
// Work around sdk#31490.
|
||||
external Fiber call(function());
|
||||
|
||||
external Fiber get current;
|
||||
|
||||
external yield([value]);
|
||||
}
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
class Fiber {
|
||||
external run([value]);
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
|
||||
import 'package:js/js.dart';
|
||||
|
||||
import 'fiber.dart';
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
class RenderOptions {
|
||||
@ -16,6 +18,7 @@ class RenderOptions {
|
||||
external String get indentType;
|
||||
external dynamic get indentWidth;
|
||||
external String get linefeed;
|
||||
external FiberClass get fiber;
|
||||
|
||||
external factory RenderOptions(
|
||||
{String file,
|
||||
@ -26,5 +29,6 @@ class RenderOptions {
|
||||
String outputStyle,
|
||||
String indentType,
|
||||
indentWidth,
|
||||
String linefeed});
|
||||
String linefeed,
|
||||
FiberClass fiber});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
/// to Dart. This is kind of convoluted, but it allows us to test the API as it
|
||||
/// will be used in the real world without having to manually write any JS.
|
||||
|
||||
import 'package:sass/src/node/fiber.dart';
|
||||
import 'package:sass/src/node/render_error.dart';
|
||||
import 'package:sass/src/node/render_options.dart';
|
||||
import 'package:sass/src/node/render_result.dart';
|
||||
@ -20,7 +21,10 @@ export 'package:sass/src/node/render_options.dart';
|
||||
export 'package:sass/src/node/render_result.dart';
|
||||
|
||||
/// The Sass module.
|
||||
final sass = _require(p.absolute("build/npm/sass.dart"));
|
||||
final sass = _requireSass(p.absolute("build/npm/sass.dart"));
|
||||
|
||||
/// The Fiber class.
|
||||
final fiber = _requireFiber("fibers");
|
||||
|
||||
/// A `null` that's guaranteed to be represented by JavaScript's `undefined`
|
||||
/// value, not by `null`.
|
||||
@ -41,7 +45,10 @@ external Object _eval(String js);
|
||||
external void chdir(String directory);
|
||||
|
||||
@JS("require")
|
||||
external Sass _require(String path);
|
||||
external Sass _requireSass(String path);
|
||||
|
||||
@JS("require")
|
||||
external FiberClass _requireFiber(String path);
|
||||
|
||||
@JS()
|
||||
class Sass {
|
||||
|
@ -580,5 +580,76 @@ void main() {
|
||||
toStringAndMessageEqual("Can't find stylesheet to import.\n"
|
||||
" stdin 1:9 root stylesheet")));
|
||||
});
|
||||
|
||||
group("with fibers", () {
|
||||
setUpAll(() {
|
||||
try {
|
||||
fiber;
|
||||
} catch (_) {
|
||||
throw "Can't load fibers package.\n"
|
||||
"Run pub run grinder before_test.";
|
||||
}
|
||||
});
|
||||
|
||||
test("supports asynchronous importers", () {
|
||||
expect(
|
||||
render(new RenderOptions(
|
||||
data: "@import 'foo'",
|
||||
importer: allowInterop((_, __, done) {
|
||||
new Future.delayed(Duration.ZERO).then((_) {
|
||||
done(new NodeImporterResult(contents: 'a {b: c}'));
|
||||
});
|
||||
}),
|
||||
fiber: fiber)),
|
||||
completion(equalsIgnoringWhitespace('a { b: c; }')));
|
||||
});
|
||||
|
||||
test("supports synchronous calls to done", () {
|
||||
expect(
|
||||
render(new RenderOptions(
|
||||
data: "@import 'foo'",
|
||||
importer: allowInterop((_, __, done) {
|
||||
done(new NodeImporterResult(contents: 'a {b: c}'));
|
||||
}),
|
||||
fiber: fiber)),
|
||||
completion(equalsIgnoringWhitespace('a { b: c; }')));
|
||||
});
|
||||
|
||||
test("supports synchronous importers", () {
|
||||
expect(
|
||||
render(new RenderOptions(
|
||||
data: "@import 'foo'",
|
||||
importer: allowInterop((_, __, ___) {
|
||||
return new NodeImporterResult(contents: 'a {b: c}');
|
||||
}),
|
||||
fiber: fiber)),
|
||||
completion(equalsIgnoringWhitespace('a { b: c; }')));
|
||||
});
|
||||
|
||||
test("supports asynchronous errors", () {
|
||||
expect(
|
||||
renderError(new RenderOptions(
|
||||
data: "@import 'foo'",
|
||||
importer: allowInterop((_, __, done) {
|
||||
new Future.delayed(Duration.ZERO).then((_) {
|
||||
done(new JSError('oh no'));
|
||||
});
|
||||
}),
|
||||
fiber: fiber)),
|
||||
completion(toStringAndMessageEqual("oh no\n"
|
||||
" stdin 1:9 root stylesheet")));
|
||||
});
|
||||
|
||||
test("supports synchronous null returns", () {
|
||||
expect(
|
||||
renderError(new RenderOptions(
|
||||
data: "@import 'foo'",
|
||||
importer: allowInterop((_, __, ___) => jsNull),
|
||||
fiber: fiber)),
|
||||
completion(
|
||||
toStringAndMessageEqual("Can't find stylesheet to import.\n"
|
||||
" stdin 1:9 root stylesheet")));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user