Fully support the return value for the Node render() functions (#165)

Closes #11
This commit is contained in:
Natalie Weizenbaum 2017-07-09 14:52:14 -07:00 committed by GitHub
parent 15d2064f33
commit 214a2af2e9
6 changed files with 153 additions and 29 deletions

View File

@ -19,7 +19,7 @@ import 'src/sync_package_resolver.dart';
/// Throws a [SassException] if conversion fails.
String compile(String path,
{bool color: false, SyncPackageResolver packageResolver}) =>
c.compile(path, color: color, packageResolver: packageResolver);
c.compile(path, color: color, packageResolver: packageResolver).css;
/// Compiles [source] to CSS and returns the result.
///
@ -38,18 +38,20 @@ String compile(String path,
///
/// Throws a [SassException] if conversion fails.
String compileString(String source,
{bool indented: false,
bool color: false,
SyncPackageResolver packageResolver,
url}) =>
c.compileString(source,
indented: indented,
color: color,
packageResolver: packageResolver,
url: url);
{bool indented: false,
bool color: false,
SyncPackageResolver packageResolver,
url}) {
var result = c.compileString(source,
indented: indented,
color: color,
packageResolver: packageResolver,
url: url);
return result.css;
}
/// Use [compile] instead.
@Deprecated('Will be removed in 1.0.0')
String render(String path,
{bool color: false, SyncPackageResolver packageResolver}) =>
c.compile(path, color: color, packageResolver: packageResolver);
compile(path, color: color, packageResolver: packageResolver);

View File

@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:collection';
import 'ast/sass.dart';
import 'io.dart';
import 'sync_package_resolver.dart';
@ -11,7 +13,7 @@ import 'visitor/serialize.dart';
/// Like [compile] in `lib/sass.dart`, but provides more options to support the
/// node-sass compatible API.
String compile(String path,
CompileResult compile(String path,
{bool indented,
bool color: false,
SyncPackageResolver packageResolver,
@ -33,7 +35,7 @@ String compile(String path,
/// Like [compileString] in `lib/sass.dart`, but provides more options to support
/// the node-sass compatible API.
String compileString(String source,
CompileResult compileString(String source,
{bool indented: false,
bool color: false,
SyncPackageResolver packageResolver,
@ -46,11 +48,26 @@ String compileString(String source,
var sassTree = indented
? new Stylesheet.parseSass(source, url: url, color: color)
: new Stylesheet.parseScss(source, url: url, color: color);
var cssTree = evaluate(sassTree,
var evaluateResult = evaluate(sassTree,
color: color, packageResolver: packageResolver, loadPaths: loadPaths);
return serialize(cssTree,
var css = serialize(evaluateResult.stylesheet,
style: style,
useSpaces: useSpaces,
indentWidth: indentWidth,
lineFeed: lineFeed);
return new CompileResult(css, evaluateResult.includedUrls);
}
/// The result of compiling a Sass document to CSS, along with metadata about
/// the compilation process.
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;
CompileResult(this.css, this.includedUrls);
}

View File

@ -12,6 +12,7 @@ import 'node/render_error.dart';
import 'node/render_options.dart';
import 'node/render_result.dart';
import 'node/utils.dart';
import 'util/path.dart';
import 'visitor/serialize.dart';
/// The entrypoint for Node.js.
@ -71,14 +72,15 @@ RenderResult _renderSync(RenderOptions options) {
/// Unlike [_render] and [_renderSync], this doesn't do any special handling for
/// Dart exceptions.
RenderResult _doRender(RenderOptions options) {
String output;
var start = new DateTime.now();
CompileResult result;
if (options.data != null) {
if (options.file != null) {
throw new ArgumentError(
"options.data and options.file may not both be set.");
}
output = compileString(options.data,
result = compileString(options.data,
loadPaths: options.includePaths,
indented: options.indentedSyntax ?? false,
style: _parseOutputStyle(options.outputStyle),
@ -86,7 +88,7 @@ RenderResult _doRender(RenderOptions options) {
indentWidth: _parseIndentWidth(options.indentWidth),
lineFeed: _parseLineFeed(options.linefeed));
} else if (options.file != null) {
output = compile(options.file,
result = compile(options.file,
loadPaths: options.includePaths,
indented: options.indentedSyntax,
style: _parseOutputStyle(options.outputStyle),
@ -96,8 +98,14 @@ RenderResult _doRender(RenderOptions options) {
} else {
throw new ArgumentError("Either options.data or options.file must be set.");
}
var end = new DateTime.now();
return newRenderResult(output);
return newRenderResult(result.css,
entry: options.file ?? 'data',
start: start.millisecondsSinceEpoch,
end: end.millisecondsSinceEpoch,
duration: end.difference(start).inMilliseconds,
includedFiles: result.includedUrls.map((url) => p.fromUri(url)).toList());
}
/// Parse [style] into an [OutputStyle].

View File

@ -13,9 +13,39 @@ external _buffer(String source, String encoding);
@anonymous
class RenderResult {
external Uint8List get css;
external RenderResultStats get stats;
external factory RenderResult._({css});
external factory RenderResult._({css, RenderResultStats stats});
}
RenderResult newRenderResult(String css) =>
new RenderResult._(css: _buffer(css, 'utf8'));
@JS()
@anonymous
class RenderResultStats {
external String get entry;
external int get start;
external int get end;
external int get duration;
external List<String> get includedFiles;
external factory RenderResultStats._(
{String entry,
int start,
int end,
int duration,
List<String> includedFiles});
}
RenderResult newRenderResult(String css,
{String entry,
int start,
int end,
int duration,
List<String> includedFiles}) =>
new RenderResult._(
css: _buffer(css, 'utf8'),
stats: new RenderResultStats._(
entry: entry,
start: start,
end: end,
duration: duration,
includedFiles: includedFiles));

View File

@ -41,7 +41,7 @@ typedef _ScopeCallback(callback());
/// If [color] is `true`, this will use terminal colors in warnings.
///
/// Throws a [SassRuntimeException] if evaluation fails.
CssStylesheet evaluate(Stylesheet stylesheet,
EvaluateResult evaluate(Stylesheet stylesheet,
{Iterable<String> loadPaths,
Environment environment,
bool color: false,
@ -125,7 +125,7 @@ class _EvaluateVisitor
/// 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 = <String, Stylesheet>{};
final _importedFiles = <Uri, Stylesheet>{};
final _activeImports = new Set<Uri>();
@ -203,10 +203,13 @@ class _EvaluateVisitor
});
}
CssStylesheet run(Stylesheet node) {
if (node.span != null) _activeImports.add(node.span.sourceUrl);
EvaluateResult run(Stylesheet node) {
if (node.span?.sourceUrl != null) {
_activeImports.add(node.span.sourceUrl);
_importedFiles[node.span.sourceUrl] = node;
}
visitStylesheet(node);
return _root;
return new EvaluateResult(_root, new MapKeySet(_importedFiles));
}
// ## Statements
@ -637,7 +640,8 @@ class _EvaluateVisitor
throw _exception("Can't find file to import.", import.span);
}
return _importedFiles.putIfAbsent(path, () {
var url = p.toUri(path);
return _importedFiles.putIfAbsent(url, () {
String contents;
try {
contents = readFile(path);
@ -649,7 +653,6 @@ class _EvaluateVisitor
throw _exception(error.message, import.span);
}
var url = p.toUri(path);
return p.extension(path) == '.sass'
? new Stylesheet.parseSass(contents, url: url, color: _color)
: new Stylesheet.parseScss(contents, url: url, color: _color);
@ -1664,3 +1667,16 @@ class _EvaluateVisitor
}
}
}
/// The result of compiling a Sass document to a CSS tree, along with metadata
/// about the compilation process.
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;
EvaluateResult(this.stylesheet, this.includedUrls);
}

View File

@ -200,6 +200,57 @@ a {
});
});
group("the result object", () {
test("includes the filename", () {
var result = sass.renderSync(new RenderOptions(file: sassPath));
expect(result.stats.entry, equals(sassPath));
});
test("includes data without a filename", () {
var result = sass.renderSync(new RenderOptions(data: 'a {b: c}'));
expect(result.stats.entry, equals('data'));
});
test("includes timing information", () {
var result = sass.renderSync(new RenderOptions(file: sassPath));
expect(result.stats.start, new isInstanceOf<int>());
expect(result.stats.end, new isInstanceOf<int>());
expect(result.stats.start, lessThan(result.stats.end));
expect(result.stats.duration,
equals(result.stats.end - result.stats.start));
});
group("has includedFiles which", () {
test("contains the root path if available", () {
var result = sass.renderSync(new RenderOptions(file: sassPath));
expect(result.stats.includedFiles, equals([sassPath]));
});
test("doesn't contain the root path if it's not available", () {
var result = sass.renderSync(new RenderOptions(data: 'a {b: c}'));
expect(result.stats.includedFiles, isEmpty);
});
test("contains imported paths", () async {
var importerPath = p.join(sandbox, 'importer.scss');
await writeTextFile(importerPath, '@import "test"');
var result = sass.renderSync(new RenderOptions(file: importerPath));
expect(result.stats.includedFiles,
unorderedEquals([importerPath, sassPath]));
});
test("only contains each path once", () async {
var importerPath = p.join(sandbox, 'importer.scss');
await writeTextFile(importerPath, '@import "test"; @import "test";');
var result = sass.renderSync(new RenderOptions(file: importerPath));
expect(result.stats.includedFiles,
unorderedEquals([importerPath, sassPath]));
});
});
});
group("throws an error that", () {
setUp(() => writeTextFile(sassPath, 'a {b: }'));