mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
commit
435e1b2f71
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
57
lib/src/evaluation_context.dart
Normal file
57
lib/src/evaluation_context.dart
Normal 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});
|
@ -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});
|
||||
|
@ -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.
|
||||
|
@ -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([
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
@ -160,5 +160,6 @@ class SingleUnitSassNumber extends SassNumber {
|
||||
}
|
||||
}
|
||||
|
||||
int get hashCode => fuzzyHashCode(value * canonicalMultiplierForUnit(_unit));
|
||||
int get hashCode =>
|
||||
hashCache ??= fuzzyHashCode(value * canonicalMultiplierForUnit(_unit));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]) =>
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
130
perf.md
@ -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
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 1.0.0-beta.17
|
||||
|
||||
* No user-visible changes.
|
||||
|
||||
## 1.0.0-beta.16
|
||||
|
||||
* No user-visible changes.
|
||||
|
@ -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: ../..}
|
||||
|
@ -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
|
||||
|
||||
|
@ -229,7 +229,7 @@ void main() {
|
||||
});
|
||||
|
||||
test("throws an error outside a callback", () {
|
||||
expect(() => warn("heck"), throwsArgumentError);
|
||||
expect(() => warn("heck"), throwsStateError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user