Merge pull request #1535 from sass/perf

Improve performance
This commit is contained in:
Natalie Weizenbaum 2021-10-20 17:21:28 -07:00 committed by GitHub
commit 435e1b2f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 344 additions and 240 deletions

View File

@ -1,3 +1,7 @@
## 1.43.3
* Improve performance.
## 1.43.2
* Improve the error message when the default namespace of a `@use` rule is not

View File

@ -28,7 +28,7 @@ export 'src/logger.dart';
export 'src/syntax.dart';
export 'src/value.dart' hide SassApiColor;
export 'src/visitor/serialize.dart' show OutputStyle;
export 'src/warn.dart' show warn;
export 'src/evaluation_context.dart' show warn;
/// Loads the Sass file at [path], compiles it to CSS, and returns a
/// [CompileResult] containing the CSS and additional metadata about the

View File

@ -33,16 +33,31 @@ class AsyncImportCache {
///
/// This map's values are the same as the return value of [canonicalize].
///
/// This cache isn't used for relative imports, because they're
/// context-dependent.
final Map<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>
_canonicalizeCache;
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
final _canonicalizeCache =
<Tuple2<Uri, bool>, Tuple3<AsyncImporter, Uri, Uri>?>{};
/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache = <Tuple4<Uri, bool, AsyncImporter, Uri?>,
Tuple3<AsyncImporter, Uri, Uri>?>{};
/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};
/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};
/// Creates an import cache that resolves imports using [importers].
///
@ -67,18 +82,12 @@ class AsyncImportCache {
PackageConfig? packageConfig,
Logger? logger})
: _importers = _toImporters(importers, loadPaths, packageConfig),
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();
/// Creates an import cache without any globally-available importers.
AsyncImportCache.none({Logger? logger})
: _importers = const [],
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();
/// Converts the user's [importers], [loadPaths], and [packageConfig]
/// options into a single list of importers.
@ -113,12 +122,16 @@ class AsyncImportCache {
Uri? baseUrl,
bool forImport = false}) async {
if (baseImporter != null) {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl =
await _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache,
Tuple4(url, forImport, baseImporter, baseUrl), () async {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl =
await _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
});
if (relativeResult != null) return relativeResult;
}
return await putIfAbsentAsync(_canonicalizeCache, Tuple2(url, forImport),
@ -236,6 +249,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed.
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove(Tuple2(url, false));
_canonicalizeCache.remove(Tuple2(url, true));
var relativeKeysToClear = [
for (var key in _relativeCanonicalizeCache.keys)
if (key.item1 == url) key
];
for (var key in relativeKeysToClear) {
_relativeCanonicalizeCache.remove(key);
}
}
/// Clears the cached parse tree for the stylesheet with the given

View File

@ -0,0 +1,57 @@
// Copyright 2021 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:async';
import 'package:source_span/source_span.dart';
/// An interface that exposes information about the current Sass evaluation.
///
/// This allows us to expose zone-scoped information without having to create a
/// new zone variable for each piece of information.
abstract class EvaluationContext {
/// The current evaluation context.
///
/// Throws [StateError] if there isn't a Sass stylesheet currently being
/// evaluated.
static EvaluationContext get current {
var context = Zone.current[#_evaluationContext];
if (context is EvaluationContext) return context;
throw StateError("No Sass stylesheet is currently being evaluated.");
}
/// Returns the span for the currently executing callable.
///
/// For normal exception reporting, this should be avoided in favor of
/// throwing [SassScriptException]s. It should only be used when calling APIs
/// that require spans.
///
/// Throws a [StateError] if there isn't a callable being invoked.
FileSpan get currentCallableSpan;
/// Prints a warning message associated with the current `@import` or function
/// call.
///
/// If [deprecation] is `true`, the warning is emitted as a deprecation
/// warning.
void warn(String message, {bool deprecation = false});
}
/// Prints a warning message associated with the current `@import` or function
/// call.
///
/// If [deprecation] is `true`, the warning is emitted as a deprecation warning.
///
/// This may only be called within a custom function or importer callback.
///
/// {@category Compile}
void warn(String message, {bool deprecation = false}) =>
EvaluationContext.current.warn(message, deprecation: deprecation);
/// Runs [callback] with [context] as [EvaluationContext.current].
///
/// This is zone-based, so if [callback] is asynchronous [warn] is set for the
/// duration of that callback.
T withEvaluationContext<T>(EvaluationContext context, T callback()) =>
runZoned(callback, zoneValues: {#_evaluationContext: context});

View File

@ -3,12 +3,9 @@
// https://opensource.org/licenses/MIT.
import 'dart:collection';
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:source_span/source_span.dart';
import 'ast/node.dart';
import 'callable.dart';
import 'functions/color.dart' as color;
import 'functions/list.dart' as list;
@ -49,21 +46,3 @@ final coreModules = UnmodifiableListView([
selector.module,
string.module
]);
/// Returns the span for the currently executing callable.
///
/// For normal exception reporting, this should be avoided in favor of throwing
/// [SassScriptException]s. It should only be used when calling APIs that
/// require spans.
FileSpan get currentCallableSpan {
var node = Zone.current[#_currentCallableNode];
if (node is AstNode) return node.span;
throw StateError("currentCallableSpan may only be called within an "
"active Sass callable.");
}
/// Runs [callback] in a zone with [callableNode]'s span available from
/// [currentCallableSpan].
T withCurrentCallableNode<T>(AstNode callableNode, T callback()) =>
runZoned(callback, zoneValues: {#_currentCallableNode: callableNode});

View File

@ -7,13 +7,13 @@ import 'dart:collection';
import 'package:collection/collection.dart';
import '../callable.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../module/built_in.dart';
import '../util/number.dart';
import '../util/nullable.dart';
import '../utils.dart';
import '../value.dart';
import '../warn.dart';
/// A regular expression matching the beginning of a proprietary Microsoft
/// filter declaration.

View File

@ -8,11 +8,11 @@ import 'dart:math' as math;
import 'package:collection/collection.dart';
import '../callable.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../module/built_in.dart';
import '../util/number.dart';
import '../value.dart';
import '../warn.dart';
/// The global definitions of Sass math functions.
final global = UnmodifiableListView([

View File

@ -8,9 +8,9 @@ import 'package:collection/collection.dart';
import '../ast/selector.dart';
import '../callable.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../extend/extension_store.dart';
import '../functions.dart';
import '../module/built_in.dart';
import '../value.dart';
@ -88,7 +88,8 @@ final _extend =
var target = arguments[1].assertSelector(name: "extendee");
var source = arguments[2].assertSelector(name: "extender");
return ExtensionStore.extend(selector, source, target, currentCallableSpan)
return ExtensionStore.extend(selector, source, target,
EvaluationContext.current.currentCallableSpan)
.asSassList;
});
@ -98,7 +99,8 @@ final _replace =
var target = arguments[1].assertSelector(name: "original");
var source = arguments[2].assertSelector(name: "replacement");
return ExtensionStore.replace(selector, source, target, currentCallableSpan)
return ExtensionStore.replace(selector, source, target,
EvaluationContext.current.currentCallableSpan)
.asSassList;
});

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_import_cache.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: b3b80fe96623c1579809528e46d9c75b87bf82ea
// Checksum: 3e290e40f4576be99217ddfbd7a76c4d38721af1
//
// ignore_for_file: unused_import
@ -40,15 +40,30 @@ class ImportCache {
///
/// This map's values are the same as the return value of [canonicalize].
///
/// This cache isn't used for relative imports, because they're
/// context-dependent.
final Map<Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?> _canonicalizeCache;
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
final _canonicalizeCache = <Tuple2<Uri, bool>, Tuple3<Importer, Uri, Uri>?>{};
/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache =
<Tuple4<Uri, bool, Importer, Uri?>, Tuple3<Importer, Uri, Uri>?>{};
/// The parsed stylesheets for each canonicalized import URL.
final Map<Uri, Stylesheet?> _importCache;
final _importCache = <Uri, Stylesheet?>{};
/// The import results for each canonicalized import URL.
final Map<Uri, ImporterResult> _resultsCache;
final _resultsCache = <Uri, ImporterResult>{};
/// Creates an import cache that resolves imports using [importers].
///
@ -73,18 +88,12 @@ class ImportCache {
PackageConfig? packageConfig,
Logger? logger})
: _importers = _toImporters(importers, loadPaths, packageConfig),
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();
/// Creates an import cache without any globally-available importers.
ImportCache.none({Logger? logger})
: _importers = const [],
_logger = logger ?? const Logger.stderr(),
_canonicalizeCache = {},
_importCache = {},
_resultsCache = {};
_logger = logger ?? const Logger.stderr();
/// Converts the user's [importers], [loadPaths], and [packageConfig]
/// options into a single list of importers.
@ -117,11 +126,15 @@ class ImportCache {
Tuple3<Importer, Uri, Uri>? canonicalize(Uri url,
{Importer? baseImporter, Uri? baseUrl, bool forImport = false}) {
if (baseImporter != null) {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
var relativeResult = _relativeCanonicalizeCache
.putIfAbsent(Tuple4(url, forImport, baseImporter, baseUrl), () {
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var canonicalUrl = _canonicalize(baseImporter, resolvedUrl, forImport);
if (canonicalUrl != null) {
return Tuple3(baseImporter, canonicalUrl, resolvedUrl);
}
});
if (relativeResult != null) return relativeResult;
}
return _canonicalizeCache.putIfAbsent(Tuple2(url, forImport), () {
@ -235,6 +248,14 @@ Relative canonical URLs are deprecated and will eventually be disallowed.
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove(Tuple2(url, false));
_canonicalizeCache.remove(Tuple2(url, true));
var relativeKeysToClear = [
for (var key in _relativeCanonicalizeCache.keys)
if (key.item1 == url) key
];
for (var key in relativeKeysToClear) {
_relativeCanonicalizeCache.remove(key);
}
}
/// Clears the cached parse tree for the stylesheet with the given

View File

@ -183,8 +183,7 @@ T _systemErrorToFileSystemException<T>(T callback()) {
return callback();
} catch (error) {
if (error is! JsSystemError) rethrow;
throw FileSystemException._(
_cleanErrorMessage(error), error.path);
throw FileSystemException._(_cleanErrorMessage(error), error.path);
}
}

View File

@ -181,6 +181,12 @@ abstract class SassNumber extends Value {
/// integer value, or [assertInt] to do both at once.
final num value;
/// The cached hash code for this number, if it's been computed.
///
/// @nodoc
@protected
int? hashCache;
/// This number's numerator units.
List<String> get numeratorUnits;
@ -826,7 +832,7 @@ abstract class SassNumber extends Value {
}
}
int get hashCode => fuzzyHashCode(value *
int get hashCode => hashCache ??= fuzzyHashCode(value *
_canonicalMultiplier(numeratorUnits) /
_canonicalMultiplier(denominatorUnits));

View File

@ -160,5 +160,6 @@ class SingleUnitSassNumber extends SassNumber {
}
}
int get hashCode => fuzzyHashCode(value * canonicalMultiplierForUnit(_unit));
int get hashCode =>
hashCache ??= fuzzyHashCode(value * canonicalMultiplierForUnit(_unit));
}

View File

@ -142,5 +142,5 @@ class UnitlessSassNumber extends SassNumber {
bool operator ==(Object other) =>
other is UnitlessSassNumber && fuzzyEquals(value, other.value);
int get hashCode => fuzzyHashCode(value);
int get hashCode => hashCache ??= fuzzyHashCode(value);
}

View File

@ -55,6 +55,9 @@ class SassString extends Value {
/// efficient.
late final int sassLength = text.runes.length;
/// The cached hash code for this number, if it's been computed.
int? _hashCache;
/// @nodoc
@internal
bool get isSpecialNumber {
@ -189,7 +192,7 @@ class SassString extends Value {
bool operator ==(Object other) => other is SassString && text == other.text;
int get hashCode => text.hashCode;
int get hashCode => _hashCache ??= text.hashCode;
/// Throws a [SassScriptException] with the given [message].
SassScriptException _exception(String message, [String? name]) =>

View File

@ -23,6 +23,7 @@ import '../callable.dart';
import '../color_names.dart';
import '../configuration.dart';
import '../configured_value.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../extend/extension_store.dart';
import '../extend/extension.dart';
@ -39,7 +40,6 @@ import '../syntax.dart';
import '../utils.dart';
import '../util/nullable.dart';
import '../value.dart';
import '../warn.dart';
import 'interface/css.dart';
import 'interface/expression.dart';
import 'interface/modifiable_css.dart';
@ -498,7 +498,7 @@ class _EvaluateVisitor
}
Future<EvaluateResult> run(AsyncImporter? importer, Stylesheet node) async {
return _withWarnCallback(node, () async {
return withEvaluationContext(_EvaluationContext(this, node), () async {
var url = node.span.sourceUrl;
if (url != null) {
_activeModules[url] = null;
@ -512,29 +512,17 @@ class _EvaluateVisitor
}
Future<Value> runExpression(AsyncImporter? importer, Expression expression) =>
_withWarnCallback(
expression,
withEvaluationContext(
_EvaluationContext(this, expression),
() => _withFakeStylesheet(
importer, expression, () => expression.accept(this)));
Future<void> runStatement(AsyncImporter? importer, Statement statement) =>
_withWarnCallback(
statement,
withEvaluationContext(
_EvaluationContext(this, statement),
() => _withFakeStylesheet(
importer, statement, () => statement.accept(this)));
/// Runs [callback] with a definition for the top-level `warn` function.
///
/// If no other span can be found to report a warning, falls back on
/// [nodeWithSpan]'s.
T _withWarnCallback<T>(AstNode nodeWithSpan, T callback()) {
return withWarnCallback(
(message, deprecation) => _warn(
message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span,
deprecation: deprecation),
callback);
}
/// Asserts that [value] is not `null` and returns it.
///
/// This is used for fields that are set whenever the evaluator is evaluating
@ -2590,8 +2578,7 @@ class _EvaluateVisitor
Value result;
try {
result = await withCurrentCallableNode(
nodeWithSpan, () => callback(evaluated.positional));
result = await callback(evaluated.positional);
} on SassRuntimeException {
rethrow;
} on MultiSpanSassScriptException catch (error) {
@ -3462,6 +3449,34 @@ class EvaluateResult {
EvaluateResult(this.stylesheet, this.loadedUrls);
}
/// An implementation of [EvaluationContext] using the information available in
/// [_EvaluateVisitor].
class _EvaluationContext implements EvaluationContext {
/// The visitor backing this context.
final _EvaluateVisitor _visitor;
/// The AST node whose span should be used for [warn] if no other span is
/// avaiable.
final AstNode _defaultWarnNodeWithSpan;
_EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan);
FileSpan get currentCallableSpan {
var callableNode = _visitor._callableNode;
if (callableNode != null) return callableNode.span;
throw StateError("No Sass callable is currently being evaluated.");
}
void warn(String message, {bool deprecation = false}) {
_visitor._warn(
message,
_visitor._importSpan ??
_visitor._callableNode?.span ??
_defaultWarnNodeWithSpan.span,
deprecation: deprecation);
}
}
/// The result of evaluating arguments to a function or mixin.
class _ArgumentResults {
/// Arguments passed by position.

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 7a3ae06379ca09dbf3e92d01c1fd974c5b3b9154
// Checksum: 5cdb3467b517bf381d525a1a4bc4f9b6a0eeefad
//
// ignore_for_file: unused_import
@ -32,6 +32,7 @@ import '../callable.dart';
import '../color_names.dart';
import '../configuration.dart';
import '../configured_value.dart';
import '../evaluation_context.dart';
import '../exception.dart';
import '../extend/extension_store.dart';
import '../extend/extension.dart';
@ -48,7 +49,6 @@ import '../syntax.dart';
import '../utils.dart';
import '../util/nullable.dart';
import '../value.dart';
import '../warn.dart';
import 'interface/css.dart';
import 'interface/expression.dart';
import 'interface/modifiable_css.dart';
@ -503,7 +503,7 @@ class _EvaluateVisitor
}
EvaluateResult run(Importer? importer, Stylesheet node) {
return _withWarnCallback(node, () {
return withEvaluationContext(_EvaluationContext(this, node), () {
var url = node.span.sourceUrl;
if (url != null) {
_activeModules[url] = null;
@ -517,29 +517,17 @@ class _EvaluateVisitor
}
Value runExpression(Importer? importer, Expression expression) =>
_withWarnCallback(
expression,
withEvaluationContext(
_EvaluationContext(this, expression),
() => _withFakeStylesheet(
importer, expression, () => expression.accept(this)));
void runStatement(Importer? importer, Statement statement) =>
_withWarnCallback(
statement,
withEvaluationContext(
_EvaluationContext(this, statement),
() => _withFakeStylesheet(
importer, statement, () => statement.accept(this)));
/// Runs [callback] with a definition for the top-level `warn` function.
///
/// If no other span can be found to report a warning, falls back on
/// [nodeWithSpan]'s.
T _withWarnCallback<T>(AstNode nodeWithSpan, T callback()) {
return withWarnCallback(
(message, deprecation) => _warn(
message, _importSpan ?? _callableNode?.span ?? nodeWithSpan.span,
deprecation: deprecation),
callback);
}
/// Asserts that [value] is not `null` and returns it.
///
/// This is used for fields that are set whenever the evaluator is evaluating
@ -2573,8 +2561,7 @@ class _EvaluateVisitor
Value result;
try {
result = withCurrentCallableNode(
nodeWithSpan, () => callback(evaluated.positional));
result = callback(evaluated.positional);
} on SassRuntimeException {
rethrow;
} on MultiSpanSassScriptException catch (error) {
@ -3403,6 +3390,34 @@ class _ImportedCssVisitor implements ModifiableCssVisitor<void> {
_visitor._addChild(node, through: (node) => node is CssStyleRule);
}
/// An implementation of [EvaluationContext] using the information available in
/// [_EvaluateVisitor].
class _EvaluationContext implements EvaluationContext {
/// The visitor backing this context.
final _EvaluateVisitor _visitor;
/// The AST node whose span should be used for [warn] if no other span is
/// avaiable.
final AstNode _defaultWarnNodeWithSpan;
_EvaluationContext(this._visitor, this._defaultWarnNodeWithSpan);
FileSpan get currentCallableSpan {
var callableNode = _visitor._callableNode;
if (callableNode != null) return callableNode.span;
throw StateError("No Sass callable is currently being evaluated.");
}
void warn(String message, {bool deprecation = false}) {
_visitor._warn(
message,
_visitor._importSpan ??
_visitor._callableNode?.span ??
_defaultWarnNodeWithSpan.span,
deprecation: deprecation);
}
}
/// The result of evaluating arguments to a function or mixin.
class _ArgumentResults {
/// Arguments passed by position.

View File

@ -1,36 +0,0 @@
// Copyright 2019 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:async';
/// Prints a warning message associated with the current `@import` or function
/// call.
///
/// If [deprecation] is `true`, the warning is emitted as a deprecation warning.
///
/// This may only be called within a custom function or importer callback.
///
/// {@category Compile}
void warn(String message, {bool deprecation = false}) {
var warnDefinition = Zone.current[#_warn];
if (warnDefinition == null) {
throw ArgumentError(
"warn() may only be called within a custom function or importer "
"callback.");
}
warnDefinition(message, deprecation);
}
/// Runs [callback] with [warn] as the definition for the top-level `warn()` function.
///
/// This is zone-based, so if [callback] is asynchronous [warn] is set for the
/// duration of that callback.
T withWarnCallback<T>(
void warn(String message, bool deprecation), T callback()) {
return runZoned(() {
return callback();
}, zoneValues: {#_warn: warn});
}

130
perf.md
View File

@ -3,8 +3,8 @@ the benefit Dart Sass could provide relative to other implementations.
This was tested against:
* libsass d4d74ef5 and sassc 66f0ef3 compiled with g++ (Debian 10.2.0-16) 10.2.0.
* Dart Sass ae967c7 on Dart 2.10.4 (stable) (Wed Nov 11 13:35:58 2020 +0100) on "linux_x64" and Node v14.7.0.
* libsass da91d985 and sassc 66f0ef3 compiled with g++ (Debian 10.3.0-11) 10.3.0.
* Dart Sass bf318a8 on Dart 2.14.1 (stable) (Wed Sep 8 13:33:08 2021 +0200) on "linux_x64" and Node v16.10.0.
on Debian x64 with Intel Core i7-8650U CPU @ 1.90GHz.
@ -17,138 +17,138 @@ I ran five instances of each configuration and recorded the fastest time.
Running on a file containing 4 instances of `.foo {a: b}`:
* sassc: 0.002s
* Dart Sass from a script snapshot: 0.179s
* Dart Sass from a script snapshot: 0.177s
* Dart Sass native executable: 0.009s
* Dart Sass on Node.js: 0.248s
* Dart Sass on Node.js: 0.219s
Based on these numbers, Dart Sass from a native executable is approximately:
* 4.5x slower than libsass
* 27.6x faster than Dart Sass on Node
* 24.3x faster than Dart Sass on Node
## Large Plain CSS
Running on a file containing 2^17 instances of `.foo {a: b}`:
* sassc: 1.770s
* Dart Sass from a script snapshot: 1.548s
* Dart Sass native executable: 1.379s
* Dart Sass on Node.js: 2.587s
* sassc: 1.607s
* Dart Sass from a script snapshot: 1.643s
* Dart Sass native executable: 1.473s
* Dart Sass on Node.js: 2.529s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.3x faster than libsass
* 1.9x faster than Dart Sass on Node
* 1.1x faster than libsass
* 1.7x faster than Dart Sass on Node
## Preceding Sparse `@extend`
Running on a file containing `.x {@extend .y}`, 2^17 instances of `.foo {a: b}`, and then `.y {a: b}`:
* sassc: 1.797s
* Dart Sass from a script snapshot: 1.594s
* Dart Sass native executable: 1.490s
* Dart Sass on Node.js: 2.783s
* sassc: 1.643s
* Dart Sass from a script snapshot: 1.723s
* Dart Sass native executable: 1.535s
* Dart Sass on Node.js: 2.574s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.2x faster than libsass
* 1.9x faster than Dart Sass on Node
* 1.1x faster than libsass
* 1.7x faster than Dart Sass on Node
## Following Sparse `@extend`
Running on a file containing `.y {a: b}`, 2^17 instances of `.foo {a: b}`, and then `.x {@extend .y}`:
* sassc: 1.902s
* Dart Sass from a script snapshot: 1.587s
* Dart Sass native executable: 1.425s
* Dart Sass on Node.js: 2.550s
* sassc: 1.642s
* Dart Sass from a script snapshot: 1.676s
* Dart Sass native executable: 1.517s
* Dart Sass on Node.js: 2.547s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.3x faster than libsass
* 1.8x faster than Dart Sass on Node
* 1.1x faster than libsass
* 1.7x faster than Dart Sass on Node
## Preceding Dense `@extend`
Running on a file containing `.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`:
* sassc: 2.556s
* Dart Sass from a script snapshot: 2.426s
* Dart Sass native executable: 2.293s
* Dart Sass on Node.js: 4.843s
* sassc: 2.336s
* Dart Sass from a script snapshot: 2.453s
* Dart Sass native executable: 2.312s
* Dart Sass on Node.js: 5.874s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.1x faster than libsass
* 2.1x faster than Dart Sass on Node
* identical to libsass
* 2.5x faster than Dart Sass on Node
## Following Dense `@extend`
Running on a file containing 2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`:
* sassc: 2.567s
* Dart Sass from a script snapshot: 2.270s
* Dart Sass native executable: 2.174s
* Dart Sass on Node.js: 4.285s
* sassc: 2.353s
* Dart Sass from a script snapshot: 2.357s
* Dart Sass native executable: 2.220s
* Dart Sass on Node.js: 5.587s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.2x faster than libsass
* 2.0x faster than Dart Sass on Node
* 1.1x faster than libsass
* 2.5x faster than Dart Sass on Node
## Bootstrap
Running on a file containing 16 instances of importing the Bootstrap framework:
* sassc: 0.798s
* Dart Sass from a script snapshot: 1.417s
* Dart Sass native executable: 0.708s
* Dart Sass on Node.js: 2.832s
* sassc: 0.789s
* Dart Sass from a script snapshot: 1.517s
* Dart Sass native executable: 0.691s
* Dart Sass on Node.js: 2.799s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.1x faster than libsass
* 4.0x faster than Dart Sass on Node
* 4.1x faster than Dart Sass on Node
## a11ycolor
Running on a file containing test cases for a computation-intensive color-processing library:
* sassc: 0.239s
* Dart Sass from a script snapshot: 0.661s
* Dart Sass native executable: 0.319s
* Dart Sass on Node.js: 0.882s
* sassc: 0.205s
* Dart Sass from a script snapshot: 0.649s
* Dart Sass native executable: 0.245s
* Dart Sass on Node.js: 0.827s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.3x slower than libsass
* 2.8x faster than Dart Sass on Node
## Susy
Running on a file containing test cases for the computation-intensive Susy grid framework:
* sassc: 0.201s
* Dart Sass from a script snapshot: 0.706s
* Dart Sass native executable: 0.141s
* Dart Sass on Node.js: 1.187s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.4x faster than libsass
* 8.4x faster than Dart Sass on Node
* 1.2x slower than libsass
* 3.4x faster than Dart Sass on Node
## Duomo
Running on a file containing the output of the numerically-intensive Duomo framework:
Running on a file containing the output of the numerically-intensive Duomo framework (skipping LibSass due to module system use):
* Dart Sass from a script snapshot: 2.017s
* Dart Sass native executable: 1.213s
* Dart Sass on Node.js: 3.632s
* Dart Sass from a script snapshot: 2.150s
* Dart Sass native executable: 1.406s
* Dart Sass on Node.js: 4.449s
Based on these numbers, Dart Sass from a native executable is approximately:
* 3.0x faster than Dart Sass on Node
* 3.2x faster than Dart Sass on Node
## Carbon
Running on a file containing the output of the import-intensive Carbon framework:
* sassc: 7.481s
* Dart Sass from a script snapshot: 5.891s
* Dart Sass native executable: 5.734s
* Dart Sass on Node.js: 15.725s
Based on these numbers, Dart Sass from a native executable is approximately:
* 1.3x faster than libsass
* 2.7x faster than Dart Sass on Node
# Prior Measurements

View File

@ -1,3 +1,7 @@
## 1.0.0-beta.17
* No user-visible changes.
## 1.0.0-beta.16
* No user-visible changes.

View File

@ -2,7 +2,7 @@ name: sass_api
# Note: Every time we add a new Sass AST node, we need to bump the *major*
# version because it's a breaking change for anyone who's implementing the
# visitor interface(s).
version: 1.0.0-beta.16
version: 1.0.0-beta.17
description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass
@ -10,7 +10,7 @@ environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
sass: 1.43.2
sass: 1.43.3
dependency_overrides:
sass: {path: ../..}

View File

@ -1,5 +1,5 @@
name: sass
version: 1.43.2
version: 1.43.3
description: A Sass implementation in Dart.
homepage: https://github.com/sass/dart-sass

View File

@ -229,7 +229,7 @@ void main() {
});
test("throws an error outside a callback", () {
expect(() => warn("heck"), throwsArgumentError);
expect(() => warn("heck"), throwsStateError);
});
});
}

View File

@ -55,14 +55,20 @@ Future<void> benchmarkGenerate() async {
}
""");
var susy = await cloneOrCheckout("https://github.com/oddbird/susy", "v3.0.5");
await runAsync("npm", arguments: ["install"], workingDirectory: susy);
File("${sources.path}/susy.scss")
.writeAsStringSync("@import '../susy/test/scss/test.scss'");
await cloneOrCheckout("https://github.com/zaydek/duomo", "v0.7.12");
File("${sources.path}/duomo.scss")
.writeAsStringSync("@import '../duomo/scripts/duomo.scss'");
// Note: This version only supports Node Sass 5.x, which only supports up to
// Node 14.x. Once there's a version that support Node Sass 6.x, we should use
// that instead.
var carbon = await cloneOrCheckout(
"https://github.com/carbon-design-system/ibm-cloud-cognitive",
"@carbon/ibm-cloud-cognitive@0.93.2");
await runAsync("npm", arguments: ["install"], workingDirectory: carbon);
File("${sources.path}/carbon.scss")
.writeAsStringSync("@import '../ibm-cloud-cognitive/packages/"
"cloud-cognitive/src/index-without-carbon-released-only'");
}
/// Writes [times] instances of [text] to [path].
@ -96,9 +102,8 @@ Future<void> _writeNTimes(String path, String text, num times,
"pkg-npm-release")
Future<void> benchmark() async {
var libsass =
await cloneOrCheckout('https://github.com/sass/libsass', 'origin/master');
var sassc =
await cloneOrCheckout('https://github.com/sass/sassc', 'origin/master');
await cloneOrCheckout('https://github.com/sass/libsass', 'master');
var sassc = await cloneOrCheckout('https://github.com/sass/sassc', 'master');
await runAsync("make",
runOptions: RunOptions(
@ -161,15 +166,18 @@ I ran five instances of each configuration and recorded the fastest time.
"a11ycolor",
"test cases for a computation-intensive color-processing library"
],
[
"susy.scss",
"Susy",
"test cases for the computation-intensive Susy grid framework"
],
[
"duomo.scss",
"Duomo",
"the output of the numerically-intensive Duomo framework"
"the output of the numerically-intensive Duomo framework "
"(skipping LibSass due to module system use)"
],
[
"carbon.scss",
"Carbon",
"the output of the import-intensive Carbon framework",
"-I",
"build/ibm-cloud-cognitive/node_modules"
],
];
@ -179,6 +187,7 @@ I ran five instances of each configuration and recorded the fastest time.
var path = p.join('build/benchmark', info[0]);
var title = info[1];
var description = info[2];
var extraArgs = info.sublist(3);
buffer.writeln("## $title");
buffer.writeln();
@ -187,23 +196,27 @@ I ran five instances of each configuration and recorded the fastest time.
Duration? sasscTime;
if (!libsassIncompatible.contains(info[1])) {
sasscTime = await _benchmark(p.join(sassc, 'bin', 'sassc'), [path]);
sasscTime =
await _benchmark(p.join(sassc, 'bin', 'sassc'), [path, ...extraArgs]);
buffer.writeln("* sassc: ${_formatTime(sasscTime)}");
}
var scriptSnapshotTime = await _benchmark(Platform.executable,
['--no-enable-asserts', p.join('build', 'sass.snapshot'), path]);
var scriptSnapshotTime = await _benchmark(Platform.executable, [
'--no-enable-asserts',
p.join('build', 'sass.snapshot'),
path,
...extraArgs
]);
buffer.writeln("* Dart Sass from a script snapshot: "
"${_formatTime(scriptSnapshotTime)}");
var nativeExecutableTime = await _benchmark(
p.join(sdkDir.path, 'bin/dartaotruntime'),
[p.join('build', 'sass.native'), path]);
var nativeExecutableTime =
await _benchmark(p.join('build', 'sass.native'), [path, ...extraArgs]);
buffer.writeln("* Dart Sass native executable: "
"${_formatTime(nativeExecutableTime)}");
var nodeTime =
await _benchmark("node", [p.join('build', 'npm', 'sass.js'), path]);
var nodeTime = await _benchmark(
"node", [p.join('build', 'npm', 'sass.js'), path, ...extraArgs]);
buffer.writeln("* Dart Sass on Node.js: ${_formatTime(nodeTime)}");
buffer.writeln();