From 887c5113dda5a9c1dabdc8f444eb710bae56b9d3 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 9 Oct 2023 16:34:38 -0700 Subject: [PATCH 01/11] Cut another release (#2111) We should have billing set up for the M1 MacOS runners now. --- CHANGELOG.md | 4 ++++ pkg/sass_api/CHANGELOG.md | 4 ++++ pkg/sass_api/pubspec.yaml | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f4fa3ba..6a9b65948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.69.1 + +* No user-visible changes. + ## 1.69.0 * Add a `meta.get-mixin()` function that returns a mixin as a first-class Sass diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index e3bfcd0a9..f685c4bb4 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.1 + +* No user-visible changes. + ## 9.2.0 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 25caba128..89ae60992 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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: 9.2.0 +version: 9.2.1 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.69.0 + sass: 1.69.1 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index ca83d52f0..5ff89e2a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.0 +version: 1.69.1 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From c62fe6a520b277a7faafa327f2ec5c78edf06827 Mon Sep 17 00:00:00 2001 From: "Carlos (Goodwine)" <2022649+Goodwine@users.noreply.github.com> Date: Tue, 10 Oct 2023 11:18:31 -0700 Subject: [PATCH 02/11] Fix crash in browser when running alongside NextJS (#2114) * Fix crash in browser when running alongside NextJS Fixes https://github.com/sass/dart-sass/issues/2113 * update sass_api pubspec --- CHANGELOG.md | 7 +++ lib/src/async_compile.dart | 1 + lib/src/async_import_cache.dart | 1 + lib/src/compile.dart | 3 +- lib/src/deprecation.dart | 2 +- lib/src/import_cache.dart | 3 +- lib/src/io/interface.dart | 9 ---- lib/src/io/js.dart | 69 +++++++++++++---------------- lib/src/io/vm.dart | 6 --- lib/src/js/compile.dart | 4 +- lib/src/js/legacy.dart | 4 +- lib/src/value/color.dart | 2 +- lib/src/visitor/async_evaluate.dart | 2 +- lib/src/visitor/evaluate.dart | 4 +- pkg/sass_api/CHANGELOG.md | 4 ++ pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 4 +- test/ensure_npm_package.dart | 3 +- 18 files changed, 62 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9b65948..5ff0ed357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.69.2 + +### JS API + +* Fix a bug where Sass crashed when running in the browser if there was a global + variable named `process`. + ## 1.69.1 * No user-visible changes. diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index 0d95a5dd7..daa2233db 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -4,6 +4,7 @@ import 'dart:convert'; +import 'package:cli_pkg/js.dart'; import 'package:path/path.dart' as p; import 'ast/sass.dart'; diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index 33fc26cce..9b08e5597 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -2,6 +2,7 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:cli_pkg/js.dart'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; diff --git a/lib/src/compile.dart b/lib/src/compile.dart index 2b24def1b..b951c8036 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: c2982db43bcd56f81cab3f51b5669e0edd3cfafb +// Checksum: 5178e366228bde7854df12221393857bb3022628 // // ignore_for_file: unused_import @@ -13,6 +13,7 @@ export 'async_compile.dart'; import 'dart:convert'; +import 'package:cli_pkg/js.dart'; import 'package:path/path.dart' as p; import 'ast/sass.dart'; diff --git a/lib/src/deprecation.dart b/lib/src/deprecation.dart index 687e0ac43..2724d0afe 100644 --- a/lib/src/deprecation.dart +++ b/lib/src/deprecation.dart @@ -2,10 +2,10 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:cli_pkg/js.dart'; import 'package:collection/collection.dart'; import 'package:pub_semver/pub_semver.dart'; -import 'io.dart'; import 'util/nullable.dart'; /// A deprecated feature in the language. diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index bc526d7a3..397e676aa 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,10 +5,11 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: ff52307a3bc93358ddc46f1e76120894fa3e071f +// Checksum: 342e907cf10e1dd80d7045fc32db43c74376654e // // ignore_for_file: unused_import +import 'package:cli_pkg/js.dart'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index be0ec574c..29182eb45 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -19,15 +19,6 @@ bool get isMacOS => throw ''; /// Returns whether or not stdout is connected to an interactive terminal. bool get hasTerminal => throw ''; -/// Whether we're running as JS (browser or Node.js). -const bool isJS = false; - -/// Whether we're running as Node.js (not browser or Dart VM). -bool get isNode => throw ''; - -/// Whether we're running as browser (not Node.js or Dart VM). -bool get isBrowser => throw ''; - /// Whether this process is connected to a terminal that supports ANSI escape /// sequences. bool get supportsAnsiEscapes => throw ''; diff --git a/lib/src/io/js.dart b/lib/src/io/js.dart index c806dc181..94e4f1a13 100644 --- a/lib/src/io/js.dart +++ b/lib/src/io/js.dart @@ -4,11 +4,12 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:js_util'; +import 'package:cli_pkg/js.dart'; import 'package:js/js.dart'; import 'package:node_interop/fs.dart'; import 'package:node_interop/node_interop.dart' hide process; +import 'package:node_interop/util.dart'; import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; import 'package:watcher/watcher.dart'; @@ -17,7 +18,12 @@ import '../exception.dart'; import '../js/chokidar.dart'; @JS('process') -external final Process? process; // process is null in the browser +external final Process? _nodeJsProcess; // process is null in the browser + +/// The Node.JS [Process] global variable. +/// +/// This value is `null` when running the script is not run from Node.JS +Process? get _process => isNodeJs ? _nodeJsProcess : null; class FileSystemException { final String message; @@ -29,7 +35,7 @@ class FileSystemException { } void safePrint(Object? message) { - if (process case var process?) { + if (_process case var process?) { process.stdout.write("${message ?? ''}\n"); } else { console.log(message ?? ''); @@ -37,7 +43,7 @@ void safePrint(Object? message) { } void printError(Object? message) { - if (process case var process?) { + if (_process case var process?) { process.stderr.write("${message ?? ''}\n"); } else { console.error(message ?? ''); @@ -45,7 +51,7 @@ void printError(Object? message) { } String readFile(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("readFile() is only supported on Node.js"); } // TODO(nweiz): explicitly decode the bytes as UTF-8 like we do in the VM when @@ -69,7 +75,7 @@ Object? _readFile(String path, [String? encoding]) => _systemErrorToFileSystemException(() => fs.readFileSync(path, encoding)); void writeFile(String path, String contents) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("writeFile() is only supported on Node.js"); } return _systemErrorToFileSystemException( @@ -77,15 +83,15 @@ void writeFile(String path, String contents) { } void deleteFile(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("deleteFile() is only supported on Node.js"); } return _systemErrorToFileSystemException(() => fs.unlinkSync(path)); } Future readStdin() async { - var process_ = process; - if (process_ == null) { + var process = _process; + if (process == null) { throw UnsupportedError("readStdin() is only supported on Node.js"); } var completer = Completer(); @@ -96,15 +102,15 @@ Future readStdin() async { }); // Node defaults all buffers to 'utf8'. var sink = utf8.decoder.startChunkedConversion(innerSink); - process_.stdin.on('data', allowInterop(([Object? chunk]) { + process.stdin.on('data', allowInterop(([Object? chunk]) { sink.add(chunk as List); })); - process_.stdin.on('end', allowInterop(([Object? _]) { + process.stdin.on('end', allowInterop(([Object? _]) { // Callback for 'end' receives no args. assert(_ == null); sink.close(); })); - process_.stdin.on('error', allowInterop(([Object? e]) { + process.stdin.on('error', allowInterop(([Object? e]) { printError('Failed to read from stdin'); printError(e); completer.completeError(e!); @@ -121,7 +127,7 @@ String _cleanErrorMessage(JsSystemError error) { } bool fileExists(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("fileExists() is only supported on Node.js"); } return _systemErrorToFileSystemException(() { @@ -142,7 +148,7 @@ bool fileExists(String path) { } bool dirExists(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("dirExists() is only supported on Node.js"); } return _systemErrorToFileSystemException(() { @@ -163,7 +169,7 @@ bool dirExists(String path) { } void ensureDir(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("ensureDir() is only supported on Node.js"); } return _systemErrorToFileSystemException(() { @@ -180,7 +186,7 @@ void ensureDir(String path) { } Iterable listDir(String path, {bool recursive = false}) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("listDir() is only supported on Node.js"); } return _systemErrorToFileSystemException(() { @@ -202,7 +208,7 @@ Iterable listDir(String path, {bool recursive = false}) { } DateTime modificationTime(String path) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("modificationTime() is only supported on Node.js"); } return _systemErrorToFileSystemException(() => @@ -210,7 +216,7 @@ DateTime modificationTime(String path) { } String? getEnvironmentVariable(String name) { - var env = process?.env; + var env = _process?.env; return env == null ? null : getProperty(env as Object, name) as String?; } @@ -229,36 +235,21 @@ T _systemErrorToFileSystemException(T callback()) { /// from `node_interop` declares `isTTY` as always non-nullably available, but /// in practice it's undefined if stdout isn't a TTY. /// See: https://github.com/pulyaevskiy/node-interop/issues/93 -bool get hasTerminal => process?.stdout.isTTY == true; - -bool get isWindows => process?.platform == 'win32'; - -bool get isMacOS => process?.platform == 'darwin'; - -const bool isJS = true; - -/// The fs module object, used to check whether this has been loaded as Node. -/// -/// It's safest to check for a library we load in manually rather than one -/// that's ambiently available so that we don't get into a weird state in -/// environments like VS Code that support some Node.js libraries but don't load -/// Node.js entrypoints for dependencies. -@JS('fs') -external final Object? _fsNullable; +bool get hasTerminal => _process?.stdout.isTTY == true; -bool get isNode => _fsNullable != null; +bool get isWindows => _process?.platform == 'win32'; -bool get isBrowser => isJS && !isNode; +bool get isMacOS => _process?.platform == 'darwin'; // Node seems to support ANSI escapes on all terminals. bool get supportsAnsiEscapes => hasTerminal; -int get exitCode => process?.exitCode ?? 0; +int get exitCode => _process?.exitCode ?? 0; -set exitCode(int code) => process?.exitCode = code; +set exitCode(int code) => _process?.exitCode = code; Future> watchDir(String path, {bool poll = false}) { - if (!isNode) { + if (!isNodeJs) { throw UnsupportedError("watchDir() is only supported on Node.js"); } var watcher = chokidar.watch( diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index cba88e40f..1d5c7b561 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -22,12 +22,6 @@ bool get isMacOS => io.Platform.isMacOS; bool get hasTerminal => io.stdout.hasTerminal; -const bool isJS = false; - -bool get isNode => false; - -bool get isBrowser => false; - bool get supportsAnsiEscapes { if (!hasTerminal) return false; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 0e734dc77..b0a192a9e 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -27,7 +27,7 @@ import 'utils.dart'; /// See https://github.com/sass/sass/spec/tree/main/js-api/compile.d.ts for /// details. NodeCompileResult compile(String path, [CompileOptions? options]) { - if (!isNode) { + if (!isNodeJs) { jsThrow(JsError("The compile() method is only available in Node.js.")); } var color = options?.alertColor ?? hasTerminal; @@ -88,7 +88,7 @@ NodeCompileResult compileString(String text, [CompileStringOptions? options]) { /// See https://github.com/sass/sass/spec/tree/main/js-api/compile.d.ts for /// details. Promise compileAsync(String path, [CompileOptions? options]) { - if (!isNode) { + if (!isNodeJs) { jsThrow(JsError("The compileAsync() method is only available in Node.js.")); } var color = options?.alertColor ?? hasTerminal; diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 66875da51..5c5ad533a 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -39,7 +39,7 @@ import 'utils.dart'; /// [render]: https://github.com/sass/node-sass#options void render( RenderOptions options, void callback(Object? error, RenderResult? result)) { - if (!isNode) { + if (!isNodeJs) { jsThrow(JsError("The render() method is only available in Node.js.")); } if (options.fiber case var fiber?) { @@ -118,7 +118,7 @@ Future _renderAsync(RenderOptions options) async { /// /// [render]: https://github.com/sass/node-sass#options RenderResult renderSync(RenderOptions options) { - if (!isNode) { + if (!isNodeJs) { jsThrow(JsError("The renderSync() method is only available in Node.js.")); } try { diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index 794f236fd..8df328564 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -4,13 +4,13 @@ import 'dart:math' as math; +import 'package:cli_pkg/js.dart'; import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../deprecation.dart'; import '../evaluation_context.dart'; import '../exception.dart'; -import '../io.dart'; import '../util/number.dart'; import '../value.dart'; import '../visitor/interface/value.dart'; diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 389c6991a..767d2393b 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:math' as math; +import 'package:cli_pkg/js.dart'; import 'package:charcode/charcode.dart'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as p; @@ -33,7 +34,6 @@ import '../functions/meta.dart' as meta; import '../importer.dart'; import '../importer/legacy_node.dart'; import '../interpolation_map.dart'; -import '../io.dart'; import '../logger.dart'; import '../module.dart'; import '../module/built_in.dart'; diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 0aaecec85..50b2cce78 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 358960b72c6e4f48d3e2e9d52be3abbe9e8b5a9f +// Checksum: 58ef9912c6a9d9cfe9c3f5d991f625ab1a627e7a // // ignore_for_file: unused_import @@ -15,6 +15,7 @@ export 'async_evaluate.dart' show EvaluateResult; import 'dart:collection'; import 'dart:math' as math; +import 'package:cli_pkg/js.dart'; import 'package:charcode/charcode.dart'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as p; @@ -42,7 +43,6 @@ import '../functions/meta.dart' as meta; import '../importer.dart'; import '../importer/legacy_node.dart'; import '../interpolation_map.dart'; -import '../io.dart'; import '../logger.dart'; import '../module.dart'; import '../module/built_in.dart'; diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index f685c4bb4..58fae4f02 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.2 + +* No user-visible changes. + ## 9.2.1 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 89ae60992..3b1dda241 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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: 9.2.1 +version: 9.2.2 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.69.1 + sass: 1.69.2 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 5ff89e2a9..123585e64 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.1 +version: 1.69.2 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass @@ -14,7 +14,7 @@ dependencies: args: ^2.0.0 async: ^2.5.0 charcode: ^1.2.0 - cli_pkg: ^2.5.0 + cli_pkg: ^2.7.0 cli_repl: ^0.2.1 collection: ^1.16.0 http: "^1.1.0" diff --git a/test/ensure_npm_package.dart b/test/ensure_npm_package.dart index 5e73c3c54..d31041e1a 100644 --- a/test/ensure_npm_package.dart +++ b/test/ensure_npm_package.dart @@ -4,6 +4,7 @@ import 'package:test/test.dart'; +import 'package:cli_pkg/js.dart'; import 'package:sass/src/io.dart'; /// Ensures that the NPM package is compiled and up-to-date. @@ -12,7 +13,7 @@ import 'package:sass/src/io.dart'; Future ensureNpmPackage() async { // spawnHybridUri() doesn't currently work on Windows and Node due to busted // path handling in the SDK. - if (isNode && isWindows) return; + if (isNodeJs && isWindows) return; var channel = spawnHybridCode(""" import 'package:cli_pkg/testing.dart' as pkg; From 44dceea0bd57e8f78ac74dcb80f67208a88eecc7 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 11 Oct 2023 16:50:19 -0700 Subject: [PATCH 03/11] Update pubspec and changelog for sass/embedded-host-node#257 (#2116) --- CHANGELOG.md | 6 ++++++ pkg/sass_api/CHANGELOG.md | 4 ++++ pkg/sass_api/pubspec.yaml | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff0ed357..5853984c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.69.3 + +### Embedded Sass + +* Fix TypeScript type locations in `package.json`. + ## 1.69.2 ### JS API diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 58fae4f02..cb9e43424 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.3 + +* No user-visible changes. + ## 9.2.2 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 3b1dda241..431a2c6d7 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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: 9.2.2 +version: 9.2.3 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.69.2 + sass: 1.69.3 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 123585e64..045846476 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.2 +version: 1.69.3 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From f40c1fc63bcd0b778f31b99363952c7c9d2e4037 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 17 Oct 2023 14:23:20 -0700 Subject: [PATCH 04/11] Cut a release (#2120) This will release sass/embedded-host-node#258. --- CHANGELOG.md | 4 ++++ pkg/sass_api/CHANGELOG.md | 4 ++++ pkg/sass_api/pubspec.yaml | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5853984c1..10bd1469a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.69.4 + +* No user-visible changes. + ## 1.69.3 ### Embedded Sass diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index cb9e43424..7ef0666c6 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.4 + +* No user-visible changes. + ## 9.2.3 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 431a2c6d7..393d58f7e 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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: 9.2.3 +version: 9.2.4 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.69.3 + sass: 1.69.4 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 045846476..b5e92e78c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.3 +version: 1.69.4 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From ac25a7023aa0751ff7ee702be55e0767f6fe3b4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:12:54 -0700 Subject: [PATCH 05/11] Bump dartdoc from 6.3.0 to 7.0.0 (#2118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump dartdoc from 6.3.0 to 7.0.0 Bumps [dartdoc](https://github.com/dart-lang/dartdoc) from 6.3.0 to 7.0.0. - [Release notes](https://github.com/dart-lang/dartdoc/releases) - [Changelog](https://github.com/dart-lang/dartdoc/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/dartdoc/commits) --- updated-dependencies: - dependency-name: dartdoc dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Fix AST visitor for parameter types, they are no longer considered SimpleIdentifier --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Israel Ortiz García --- pubspec.yaml | 2 +- tool/grind/synchronize.dart | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b5e92e78c..0ab522c75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,7 @@ dev_dependencies: archive: ^3.1.2 crypto: ^3.0.0 dart_style: ^2.0.0 - dartdoc: ^6.0.0 + dartdoc: ">=6.0.0 <8.0.0" grinder: ^0.9.0 node_preamble: ^2.0.2 lints: ^2.0.0 diff --git a/tool/grind/synchronize.dart b/tool/grind/synchronize.dart index 87ab7af5d..de18066d2 100644 --- a/tool/grind/synchronize.dart +++ b/tool/grind/synchronize.dart @@ -247,6 +247,9 @@ class _Visitor extends RecursiveAstVisitor { } else if (node.name2.lexeme == "Module") { _skipNode(node); _buffer.write("Module"); + } else if (node.typeArguments == null) { + _skip(node.name2); + _buffer.write(_synchronizeName(node.name2.lexeme)); } else { super.visitNamedType(node); } From 69d0f07979afbcdd54c8c721a6689b584edc7f65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:43:38 -0700 Subject: [PATCH 06/11] Bump lints from 2.1.1 to 3.0.0 (#2126) Bumps [lints](https://github.com/dart-lang/lints) from 2.1.1 to 3.0.0. - [Release notes](https://github.com/dart-lang/lints/releases) - [Changelog](https://github.com/dart-lang/lints/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/lints/compare/v2.1.1...v3.0.0) --- updated-dependencies: - dependency-name: lints dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 0ab522c75..4ad9521f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,7 +45,7 @@ dev_dependencies: dartdoc: ">=6.0.0 <8.0.0" grinder: ^0.9.0 node_preamble: ^2.0.2 - lints: ^2.0.0 + lints: ">=2.0.0 <4.0.0" protoc_plugin: ">=20.0.0 <22.0.0" pub_api_client: ^2.1.1 pubspec_parse: ^1.0.0 From 89dac15c030caf728f4760bfdea3f86c814338ed Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 25 Oct 2023 17:38:50 -0700 Subject: [PATCH 07/11] Add compatibility with Node.js 21.0.0 (#2128) See dart-lang/sdk#53784. Although Node.js 21.1.0 fixed the issue on its end, this release will use Dart 3.1.5 which will ensure we're compatible even with the earlier release as well. --- CHANGELOG.md | 6 ++++++ pkg/sass_api/CHANGELOG.md | 4 ++++ pkg/sass_api/pubspec.yaml | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bd1469a..3ff3e06fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.69.5 + +### JS API + +* Compatibility with Node.js 21.0.0. + ## 1.69.4 * No user-visible changes. diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 7ef0666c6..6dd5a42b9 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.5 + +* No user-visible changes. + ## 9.2.4 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 393d58f7e..4756d33f1 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -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: 9.2.4 +version: 9.2.5 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.69.4 + sass: 1.69.5 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 4ad9521f2..9e28af5df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.4 +version: 1.69.5 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From 54caf19244ffae6334a750dca2c2b3885b6ca0ea Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Fri, 3 Nov 2023 22:47:42 +0100 Subject: [PATCH 08/11] Remove dead code (#2129) The key being removed could not exist in the map as it is not even the same type than keys. --- lib/src/extend/extension_store.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/extend/extension_store.dart b/lib/src/extend/extension_store.dart index a3f276ee9..3637b5aac 100644 --- a/lib/src/extend/extension_store.dart +++ b/lib/src/extend/extension_store.dart @@ -355,11 +355,6 @@ class ExtensionStore { } } } - - // If [selectors] doesn't contain [extension.extender], for example if it - // was replaced due to :not() expansion, we must get rid of the old - // version. - if (!containsExtension) sources.remove(extension.extender); } return additionalExtensions; From dd56d2845a37bee1e06d793eaa950d15bcdd6013 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 Nov 2023 16:09:52 -0700 Subject: [PATCH 09/11] Write implementation documentation (#2042) Also rename Dispatcher to CompilationDispatcher for clarity --- CONTRIBUTING.md | 11 + lib/src/README.md | 230 ++++++++++++++++++ lib/src/ast/css/README.md | 52 ++++ lib/src/ast/sass/README.md | 34 +++ lib/src/ast/selector/README.md | 24 ++ lib/src/embedded/README.md | 28 +++ ...tcher.dart => compilation_dispatcher.dart} | 8 +- lib/src/embedded/host_callable.dart | 4 +- lib/src/embedded/importer/base.dart | 6 +- lib/src/embedded/importer/file.dart | 5 +- lib/src/embedded/importer/host.dart | 4 +- lib/src/embedded/isolate_dispatcher.dart | 4 +- lib/src/embedded/logger.dart | 6 +- lib/src/embedded/protofier.dart | 4 +- lib/src/extend/README.md | 35 +++ lib/src/functions/README.md | 24 ++ lib/src/io/README.md | 17 ++ lib/src/js/README.md | 58 +++++ lib/src/parse/README.md | 33 +++ lib/src/value/README.md | 13 + lib/src/visitor/README.md | 15 ++ 21 files changed, 595 insertions(+), 20 deletions(-) create mode 100644 lib/src/README.md create mode 100644 lib/src/ast/css/README.md create mode 100644 lib/src/ast/sass/README.md create mode 100644 lib/src/ast/selector/README.md create mode 100644 lib/src/embedded/README.md rename lib/src/embedded/{dispatcher.dart => compilation_dispatcher.dart} (98%) create mode 100644 lib/src/extend/README.md create mode 100644 lib/src/functions/README.md create mode 100644 lib/src/io/README.md create mode 100644 lib/src/js/README.md create mode 100644 lib/src/parse/README.md create mode 100644 lib/src/value/README.md create mode 100644 lib/src/visitor/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8027cad27..268e118b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,7 @@ Want to contribute? Great! First, read this page. * [Synchronizing](#synchronizing) * [File Headers](#file-headers) * [Release Process](#release-process) +* [Package Structure](#package-structure) ## Before You Contribute @@ -245,3 +246,13 @@ few things to do before pushing that tag: You *don't* need to create tags for packages in `pkg`; that will be handled automatically by GitHub actions. + +## Package Structure + +The structure of the Sass package is documented in README.md files in most +directories under `lib/`. This documentation is intended to help contributors +quickly build a basic understanding of the structure of the compiler and how its +various pieces fit together. [`lib/src/README.md`] is a good starting point to get +an overview of the compiler as a whole. + +[`lib/src/README.md`]: lib/src/README.md diff --git a/lib/src/README.md b/lib/src/README.md new file mode 100644 index 000000000..e7b53cd91 --- /dev/null +++ b/lib/src/README.md @@ -0,0 +1,230 @@ +# The Sass Compiler + +* [Life of a Compilation](#life-of-a-compilation) + * [Late Parsing](#late-parsing) + * [Early Serialization](#early-serialization) +* [JS Support](#js-support) +* [APIs](#apis) + * [Importers](#importers) + * [Custom Functions](#custom-functions) + * [Loggers](#loggers) +* [Built-In Functions](#built-in-functions) +* [`@extend`](#extend) + +This is the root directory of Dart Sass's private implementation libraries. This +contains essentially all the business logic defining how Sass is actually +compiled, as well as the APIs that users use to interact with Sass. There are +two exceptions: + +* [`../../bin/sass.dart`] is the entrypoint for the Dart Sass CLI (on all + platforms). While most of the logic it runs exists in this directory, it does + contain some logic to drive the basic compilation logic and handle errors. All + the most complex parts of the CLI, such as option parsing and the `--watch` + command, are handled in the [`executable`] directory. Even Embedded Sass runs + through this entrypoint, although it gets immediately gets handed off to [the + embedded compiler]. + + [`../../bin/sass.dart`]: ../../bin/sass.dart + [`executable`]: executable + [the embedded compiler]: embedded/README.md + +* [`../sass.dart`] is the entrypoint for the public Dart API. This is what's + loaded when a Dart package imports Sass. It just contains the basic + compilation functions, and exports the rest of the public APIs from this + directory. + + [`../sass.dart`]: ../sass.dart + +Everything else is contained here, and each file and some subdirectories have +their own documentation. But before you dive into those, let's take a look at +the general lifecycle of a Sass compilation. + +## Life of a Compilation + +Whether it's invoked through the Dart API, the JS API, the CLI, or the embedded +host, the basic process of a Sass compilation is the same. Sass is implemented +as an AST-walking [interpreter] that operates in roughly three passes: + +[interpreter]: https://en.wikipedia.org/wiki/Interpreter_(computing) + +1. **Parsing**. The first step of a Sass compilation is always to parse the + source file, whether it's SCSS, the indented syntax, or CSS. The parsing + logic lives in the [`parse`] directory, while the abstract syntax tree that + represents the parsed file lives in [`ast/sass`]. + + [`parse`]: parse/README.md + [`ast/sass`]: ast/sass/README.md + +2. **Evaluation**. Once a Sass file is parsed, it's evaluated by + [`visitor/async_evaluate.dart`]. (Why is there both an async and a sync + version of this file? See [Synchronizing] for details!) The evaluator handles + all the Sass-specific logic: it resolves variables, includes mixins, executes + control flow, and so on. As it goes, it builds up a new AST that represents + the plain CSS that is the compilation result, which is defined in + [`ast/css`]. + + [`visitor/async_evaluate.dart`]: visitor/async_evaluate.dart + [Synchronizing]: ../../CONTRIBUTING.md#synchronizing + [`ast/css`]: ast/css/README.md + + Sass evaluation is almost entirely linear: it begins at the first statement + of the file, evaluates it (which may involve evaluating its nested children), + adds its result to the CSS AST, and then moves on to the second statement. On + it goes until it reaches the end of the file, at which point it's done. The + only exception is module resolution: every Sass module has its own compiled + CSS AST, and once the entrypoint file is done compiling the evaluator will go + back through these modules, resolve `@extend`s across them as necessary, and + stitch them together into the final stylesheet. + + SassScript, the expression-level syntax, is handled by the same evaluator. + The main difference between SassScript and statement-level evaluation is that + the same SassScript values are used during evaluation _and_ as part of the + CSS AST. This means that it's possible to end up with a Sass-specific value, + such as a map or a first-class function, as the value of a CSS declaration. + If that happens, the Serialization phase will signal an error when it + encounters the invalid value. + +3. **Serialization**. Once we have the CSS AST that represents the compiled + stylesheet, we need to convert it into actual CSS text. This is done by + [`visitor/serialize.dart`], which walks the AST and builds up a big buffer of + the resulting CSS. It uses [a special string buffer] that tracks source and + destination locations in order to generate [source maps] as well. + + [`visitor/serialize.dart`]: visitor/serialize.dart + [a special string buffer]: util/source_map_buffer.dart + [source maps]: https://web.dev/source-maps/ + +There's actually one slight complication here: the first and second pass aren't +as separate as they appear. When one Sass stylesheet loads another with `@use`, +`@forward`, or `@import`, that rule is handled by the evaluator and _only at +that point_ is the loaded file parsed. So in practice, compilation actually +switches between parsing and evaluation, although each individual stylesheet +naturally has to be parsed before it can be evaluated. + +### Late Parsing + +Some syntax within a stylesheet is only parsed _during_ evaluation. This allows +authors to use `#{}` interpolation to inject Sass variables and other dynamic +values into various locations, such as selectors, while still allowing Sass to +parse them to support features like nesting and `@extend`. The following +syntaxes are parsed during evaluation: + +* [Selectors](parse/selector.dart) +* [`@keyframes` frames](parse/keyframe_selector.dart) +* [Media queries](parse/media_query.dart) (for historical reasons, these are + parsed before evaluation and then _reparsed_ after they've been fully + evaluated) + +### Early Serialization + +There are also some cases where the evaluator can serialize values before the +main serialization pass. For example, if you inject a variable into a selector +using `#{}`, that variable's value has to be converted to a string during +evaluation so that the evaluator can then parse and handle the newly-generated +selector. The evaluator does this by invoking the serializer _just_ for that +specific value. As a rule of thumb, this happens anywhere interpolation is used +in the original stylesheet, although there are a few other circumstances as +well. + +## JS Support + +One of the main benefits of Dart as an implementation language is that it allows +us to distribute Dart Sass both as an extremely efficient stand-alone executable +_and_ an easy-to-install pure-JavaScript package, using the dart2js compilation +tool. However, properly supporting JS isn't seamless. There are two major places +where we need to think about JS support: + +1. When interfacing with the filesystem. None of Dart's IO APIs are natively + supported on JS, so for anything that needs to work on both the Dart VM _and_ + Node.js we define a shim in the [`io`] directory that will be implemented in + terms of `dart:io` if we're running on the Dart VM or the `fs` or `process` + modules if we're running on Node. (We don't support IO at all on the browser + except to print messages to the console.) + + [`io`]: io/README.md + +2. When exposing an API. Dart's JS interop is geared towards _consuming_ JS + libraries from Dart, not producing a JS library written in Dart, so we have + to jump through some hoops to make it work. This is all handled in the [`js`] + directory. + + [`js`]: js/README.md + +## APIs + +One of Sass's core features is its APIs, which not only compile stylesheets but +also allow users to provide plugins that can be invoked from within Sass. In +both the JS API, the Dart API, and the embedded compiler, Sass provides three +types of plugins: importers, custom functions, and loggers. + +### Importers + +Importers control how Sass loads stylesheets through `@use`, `@forward`, and +`@import`. Internally, _all_ stylesheet loads are modeled as importers. When a +user passes a load path to an API or compiles a stylesheet through the CLI, we +just use the built-in [`FilesystemImporter`] which implements the same interface +that we make available to users. + +[`FilesystemImporter`]: importer/filesystem.dart + +In the Dart API, the importer root class is [`importer/async_importer.dart`]. +The JS API and the embedded compiler wrap the Dart importer API in +[`importer/node_to_dart`] and [`embedded/importer`] respectively. + +[`importer/async_importer.dart`]: importer/async_importer.dart +[`importer/node_to_dart`]: importer/node_to_dart +[`embedded/importer`]: embedded/importer + +### Custom Functions + +Custom functions are defined by users of the Sass API but invoked by Sass +stylesheets. To a Sass stylesheet, they look like any other built-in function: +users pass SassScript values to them and get SassScript values back. In fact, +all the core Sass functions are implemented using the Dart custom function API. + +Because custom functions take and return SassScript values, that means we need +to make _all_ values available to the various APIs. For Dart, this is +straightforward: we need to have objects to represent those values anyway, so we +just expose those objects publicly (with a few `@internal` annotations here and +there to hide APIs we don't want users relying on). These value types live in +the [`value`] directory. + +[`value`]: value/README.md + +Exposing values is a bit more complex for other platforms. For the JS API, we do +a bit of metaprogramming in [`js/value`] so that we can return the +same Dart values we use internally while still having them expose a JS API that +feels native to that language. For the embedded host, we convert them to and +from a protocol buffer representation in [`embedded/protofier.dart`]. + +[`js/value`]: js/value/README.md +[`embedded/value.dart`]: embedded/value.dart + +### Loggers + +Loggers are the simplest of the plugins. They're just callbacks that are invoked +any time Dart Sass would emit a warning (from the language or from `@warn`) or a +debug message from `@debug`. They're defined in: + +* [`logger.dart`](logger.dart) for Dart +* [`js/logger.dart`](js/logger.dart) for Node +* [`embedded/logger.dart`](embedded/logger.dart) for the embedded compiler + +## Built-In Functions + +All of Sass's built-in functions are defined in the [`functions`] directory, +including both global functions and functions defined in core modules like +`sass:math`. As mentioned before, these are defined using the standard custom +function API, although in a few cases they use additional private features like +the ability to define multiple overloads of the same function name. + +[`functions`]: functions/README.md + +## `@extend` + +The logic for Sass's `@extend` rule is particularly complex, since it requires +Sass to not only parse selectors but to understand how to combine them and when +they can be safely optimized away. Most of the logic for this is contained +within the [`extend`] directory. + +[`extend`]: extend/README.md diff --git a/lib/src/ast/css/README.md b/lib/src/ast/css/README.md new file mode 100644 index 000000000..e75c3ff5c --- /dev/null +++ b/lib/src/ast/css/README.md @@ -0,0 +1,52 @@ +# CSS Abstract Syntax Tree + +This directory contains the abstract syntax tree that represents a plain CSS +file generated by Sass compilation. It differs from other Sass ASTs in two major +ways: + +1. Instead of being created by [a parser], it's created by [the evaluator] as it + traverses the [Sass AST]. + + [a parser]: ../../parse/README.md + [the evaluator]: ../../visitor/async_evaluate.dart + [Sass AST]: ../sass/README.md + +2. Because of various Sass features like `@extend` and at-rule hoisting, the CSS + AST is mutable even though all other ASTs are immutable. + +**Note:** the CSS AST doesn't have its own representation of declaration values. +Instead, declaration values are represented as [`Value`] objects. This does mean +that a CSS AST can be in a state where some of its values aren't representable +in plain CSS (such as maps)—in this case, [the serializer] will emit an error. + +[`Value`]: ../../value/README.md +[the serializer]: ../../visitor/serialize.dart + +## Mutable and Immutable Views + +Internally, the CSS AST is mutable to allow for operations like hoisting rules +to the root of the AST and updating existing selectors when `@extend` rules are +encountered. However, because mutability poses a high risk for "spooky [action +at a distance]", we limit access to mutating APIs exclusively to the evaluator. + +[action at a distance]: https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming) + +We do this by having an _unmodifiable_ interface (written in this directory) for +each CSS AST node which only exposes members that don't modify the node in +question. The implementations of those interfaces, which _do_ have modifying +methods, live in the [`modifiable`] directory. We then universally refer to the +immutable node interfaces except specifically in the evaluator, and the type +system automatically ensures we don't accidentally mutate anything we don't +intend to. + +[`modifiable`]: modifiable + +(Of course, it's always possible to cast an immutable node type to a mutable +one, but that's a very clear code smell that a reviewer can easily identify.) + +## CSS Source Files + +A lesser-known fact about Sass is that it actually supports _three_ syntaxes for +its source files: SCSS, the indented syntax, and plain CSS. But even when it +parses plain CSS, it uses the Sass AST rather than the CSS AST to represent it +so that parsing logic can easily be shared with the other stylesheet parsers. diff --git a/lib/src/ast/sass/README.md b/lib/src/ast/sass/README.md new file mode 100644 index 000000000..415a4b6ae --- /dev/null +++ b/lib/src/ast/sass/README.md @@ -0,0 +1,34 @@ +# Sass Abstract Syntax Tree + +This directory contains the abstract syntax tree that represents a Sass source +file, regardless of which syntax it was written in (SCSS, the indented syntax, +or plain CSS). The AST is constructed recursively by [a parser] from the leaf +nodes in towards the root, which allows it to be fully immutable. + +[a parser]: ../../parse/README.md + +The Sass AST is broken up into three categories: + +1. The [statement AST], which represents statement-level constructs like + variable assignments, style rules, and at-rules. + + [statement AST]: statement + +2. The [expression AST], which represents SassScript expressions like function + calls, operations, and value literals. + + [expression AST]: exprssion + +3. Miscellaneous AST nodes that are used by both statements and expressions or + don't fit cleanly into either category that live directly in this directory. + +The Sass AST nodes are processed (usually from the root [`Stylesheet`]) by [the +evaluator], which runs the logic they encode and builds up a [CSS AST] that +represents the compiled stylesheet. They can also be transformed back into Sass +source using the `toString()` method. Since this is only ever used for debugging +and doesn't need configuration or full-featured indentation tracking, it doesn't +use a full visitor. + +[`Stylesheet`]: statement/stylesheet.dart +[the evaluator]: ../../visitor/async_evaluate.dart +[CSS AST]: ../css/README.md diff --git a/lib/src/ast/selector/README.md b/lib/src/ast/selector/README.md new file mode 100644 index 000000000..721644977 --- /dev/null +++ b/lib/src/ast/selector/README.md @@ -0,0 +1,24 @@ +# Selector Abstract Syntax Tree + +This directory contains the abstract syntax tree that represents a parsed CSS +selector. This AST is constructed recursively by [the selector parser]. It's +fully immutable. + +[the selector parser]: ../../parse/selector.dart + +Unlike the [Sass AST], which is parsed from a raw source string before being +evaluated, the selector AST is parsed _during evaluation_. This is necessary to +ensure that there's a chance to resolve interpolation before fully parsing the +selectors in question. + +[Sass AST]: ../sass/README.md + +Although this AST doesn't include any SassScript, it _does_ include a few +Sass-specific constructs: the [parent selector] `&` and [placeholder selectors]. +Parent selectors are resolved by [the evaluator] before it hands the AST off to +[the serializer], while placeholders are omitted in the serializer itself. + +[parent selector]: parent.dart +[placeholder selectors]: placeholder.dart +[the evaluator]: ../../visitor/async_evaluate.dart +[the serializer]: ../../visitor/serialize.dart diff --git a/lib/src/embedded/README.md b/lib/src/embedded/README.md new file mode 100644 index 000000000..b892e4818 --- /dev/null +++ b/lib/src/embedded/README.md @@ -0,0 +1,28 @@ +# Embedded Sass Compiler + +This directory contains the Dart Sass embedded compiler. This is a special mode +of the Dart Sass command-line executable, only supported on the Dart VM, in +which it uses stdin and stdout to communicate with another endpoint, the +"embedded host", using a protocol buffer-based protocol. See [the embedded +protocol specification] for details. + +[the embedded protocol specification]: https://github.com/sass/sass/blob/main/spec/embedded-protocol.md + +The embedded compiler has two different levels of dispatchers for handling +incoming messages from the embedded host: + +1. The [`IsolateDispatcher`] is the first recipient of each packet. It decodes + the packets _just enough_ to determine which compilation they belong to, and + forwards them to the appropriate compilation dispatcher. It also parses and + handles messages that aren't compilation specific, such as `VersionRequest`. + + [`IsolateDispatcher`]: isolate_dispatcher.dart + +2. The [`CompilationDispatcher`] fully parses and handles messages for a single + compilation. Each `CompilationDispatcher` runs in a separate isolate so that + the embedded compiler can run multiple compilations in parallel. + + [`CompilationDispatcher`]: compilation_dispatcher.dart + +Otherwise, most of the code in this directory just wraps Dart APIs to +communicate with their protocol buffer equivalents. diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart similarity index 98% rename from lib/src/embedded/dispatcher.dart rename to lib/src/embedded/compilation_dispatcher.dart index 40e81863d..218ea6f18 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -31,7 +31,7 @@ final _outboundRequestId = 0; /// A class that dispatches messages to and from the host for a single /// compilation. -final class Dispatcher { +final class CompilationDispatcher { /// The mailbox for receiving messages from the host. final Mailbox _mailbox; @@ -54,9 +54,9 @@ final class Dispatcher { /// it'll just be a wrapper around the error. var _requestError = false; - /// Creates a [Dispatcher] that receives encoded protocol buffers through - /// [_mailbox] and sends them through [_sendPort]. - Dispatcher(this._mailbox, this._sendPort); + /// Creates a [CompilationDispatcher] that receives encoded protocol buffers + /// through [_mailbox] and sends them through [_sendPort]. + CompilationDispatcher(this._mailbox, this._sendPort); /// Listens for incoming `CompileRequests` and runs their compilations. void listen() { diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index 3518b57e2..06c3acb66 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -6,7 +6,7 @@ import '../callable.dart'; import '../exception.dart'; import '../value/function.dart'; import '../value/mixin.dart'; -import 'dispatcher.dart'; +import 'compilation_dispatcher.dart'; import 'embedded_sass.pb.dart'; import 'opaque_registry.dart'; import 'protofier.dart'; @@ -21,7 +21,7 @@ import 'utils.dart'; /// /// Throws a [SassException] if [signature] is invalid. Callable hostCallable( - Dispatcher dispatcher, + CompilationDispatcher dispatcher, OpaqueRegistry functions, OpaqueRegistry mixins, String signature, diff --git a/lib/src/embedded/importer/base.dart b/lib/src/embedded/importer/base.dart index 1de597da5..07c363cca 100644 --- a/lib/src/embedded/importer/base.dart +++ b/lib/src/embedded/importer/base.dart @@ -5,14 +5,14 @@ import 'package:meta/meta.dart'; import '../../importer.dart'; -import '../dispatcher.dart'; +import '../compilation_dispatcher.dart'; /// An abstract base class for importers that communicate with the host in some /// way. abstract base class ImporterBase extends Importer { - /// The [Dispatcher] to which to send requests. + /// The [CompilationDispatcher] to which to send requests. @protected - final Dispatcher dispatcher; + final CompilationDispatcher dispatcher; ImporterBase(this.dispatcher); diff --git a/lib/src/embedded/importer/file.dart b/lib/src/embedded/importer/file.dart index 0dbccfed5..edd0d3537 100644 --- a/lib/src/embedded/importer/file.dart +++ b/lib/src/embedded/importer/file.dart @@ -3,7 +3,7 @@ // https://opensource.org/licenses/MIT. import '../../importer.dart'; -import '../dispatcher.dart'; +import '../compilation_dispatcher.dart'; import '../embedded_sass.pb.dart' hide SourceSpan; import 'base.dart'; @@ -19,7 +19,8 @@ final class FileImporter extends ImporterBase { /// The host-provided ID of the importer to invoke. final int _importerId; - FileImporter(Dispatcher dispatcher, this._importerId) : super(dispatcher); + FileImporter(CompilationDispatcher dispatcher, this._importerId) + : super(dispatcher); Uri? canonicalize(Uri url) { if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); diff --git a/lib/src/embedded/importer/host.dart b/lib/src/embedded/importer/host.dart index 5819be1ec..66e60848a 100644 --- a/lib/src/embedded/importer/host.dart +++ b/lib/src/embedded/importer/host.dart @@ -6,7 +6,7 @@ import '../../exception.dart'; import '../../importer.dart'; import '../../importer/utils.dart'; import '../../util/span.dart'; -import '../dispatcher.dart'; +import '../compilation_dispatcher.dart'; import '../embedded_sass.pb.dart' hide SourceSpan; import '../utils.dart'; import 'base.dart'; @@ -20,7 +20,7 @@ final class HostImporter extends ImporterBase { /// [canonicalize]. final Set _nonCanonicalSchemes; - HostImporter(Dispatcher dispatcher, this._importerId, + HostImporter(CompilationDispatcher dispatcher, this._importerId, Iterable nonCanonicalSchemes) : _nonCanonicalSchemes = Set.unmodifiable(nonCanonicalSchemes), super(dispatcher) { diff --git a/lib/src/embedded/isolate_dispatcher.dart b/lib/src/embedded/isolate_dispatcher.dart index 044e2b9c2..018321cdd 100644 --- a/lib/src/embedded/isolate_dispatcher.dart +++ b/lib/src/embedded/isolate_dispatcher.dart @@ -12,7 +12,7 @@ import 'package:pool/pool.dart'; import 'package:protobuf/protobuf.dart'; import 'package:stream_channel/stream_channel.dart'; -import 'dispatcher.dart'; +import 'compilation_dispatcher.dart'; import 'embedded_sass.pb.dart'; import 'reusable_isolate.dart'; import 'util/proto_extensions.dart'; @@ -161,5 +161,5 @@ class IsolateDispatcher { } void _isolateMain(Mailbox mailbox, SendPort sendPort) { - Dispatcher(mailbox, sendPort).listen(); + CompilationDispatcher(mailbox, sendPort).listen(); } diff --git a/lib/src/embedded/logger.dart b/lib/src/embedded/logger.dart index 8da614cc5..331acdf2b 100644 --- a/lib/src/embedded/logger.dart +++ b/lib/src/embedded/logger.dart @@ -9,14 +9,14 @@ import 'package:stack_trace/stack_trace.dart'; import '../logger.dart'; import '../util/nullable.dart'; import '../utils.dart'; -import 'dispatcher.dart'; +import 'compilation_dispatcher.dart'; import 'embedded_sass.pb.dart' hide SourceSpan; import 'utils.dart'; /// A Sass logger that sends log messages as `LogEvent`s. final class EmbeddedLogger implements Logger { - /// The [Dispatcher] to which to send events. - final Dispatcher _dispatcher; + /// The [CompilationDispatcher] to which to send events. + final CompilationDispatcher _dispatcher; /// Whether the formatted message should contain terminal colors. final bool _color; diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index eec7e3d05..c2878e5ef 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -5,7 +5,7 @@ import '../util/map.dart'; import '../util/nullable.dart'; import '../value.dart'; -import 'dispatcher.dart'; +import 'compilation_dispatcher.dart'; import 'embedded_sass.pb.dart' as proto; import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator; import 'host_callable.dart'; @@ -18,7 +18,7 @@ import 'utils.dart'; /// custom function call. final class Protofier { /// The dispatcher, for invoking deprotofied [Value_HostFunction]s. - final Dispatcher _dispatcher; + final CompilationDispatcher _dispatcher; /// The IDs of first-class functions. final OpaqueRegistry _functions; diff --git a/lib/src/extend/README.md b/lib/src/extend/README.md new file mode 100644 index 000000000..e27304b6b --- /dev/null +++ b/lib/src/extend/README.md @@ -0,0 +1,35 @@ +# `@extend` Logic + +This directory contains most of the logic for running Sass's `@extend` rule. +This rule is probably the most complex corner of the Sass language, since it +involves both understanding the semantics of selectors _and_ being able to +combine them. + +The high-level lifecycle of extensions is as follows: + +1. When [the evaluator] encounters a style rule, it registers its selector in + the [`ExtensionStore`] for the current module. This applies any extensions + that have already been registered, then returns a _mutable_ + `Box` that will get updated as extensions are applied. + + [the evaluator]: ../visitor/async_evaluate.dart + [`ExtensionStore`]: extension_store.dart + +2. When the evaluator encounters an `@extend`, it registers that in the current + module's `ExtensionStore` as well. This updates any selectors that have + already been registered with that extension, _and_ updates the extension's + own extender (the selector that gets injected when the extension is applied, + which is stored along with the extension). Note that the extender has to be + extended separately from the selector in the style rule, because the latter + gets redundant selectors trimmed eagerly and the former does not. + +3. When the entrypoint stylesheet has been fully executed, the evaluator + determines which extensions are visible from which modules and adds + extensions from one store to one another accordingly using + `ExtensionStore.addExtensions()`. + +Otherwise, the process of [extending a selector] as described in the Sass spec +matches the logic here fairly closely. See `ExtensionStore._extendList()` for +the primary entrypoint for that logic. + +[extending a selector]: https://github.com/sass/sass/blob/main/spec/at-rules/extend.md#extending-a-selector diff --git a/lib/src/functions/README.md b/lib/src/functions/README.md new file mode 100644 index 000000000..6aa87f6b4 --- /dev/null +++ b/lib/src/functions/README.md @@ -0,0 +1,24 @@ +# Built-In Functions + +This directory contains the standard functions that are built into Sass itself, +both those that are available globally and those that are available only through +built-in modules. Each of the files here exports a corresponding +[`BuiltInModule`], and most define a list of global functions as well. + +[`BuiltInModule`]: ../module/built_in.dart + +There are a few functions that Sass supports that aren't defined here: + +* The `if()` function is defined directly in the [`functions.dart`] file, + although in most cases this is actually parsed as an [`IfExpression`] and + handled directly by [the evaluator] since it has special behavior about when + its arguments are evaluated. The function itself only exists for edge cases + like `if(...$args)` or `meta.get-function("if")`. + + [`functions.dart`]: ../functions.dart + [`IfExpression`]: ../ast/sass/expression/if.dart + [the evaluator]: ../visitor/async_evaluate.dart + +* Certain functions in the `sass:meta` module require runtime information that's + only available to the evaluator. These functions are defined in the evaluator + itself so that they have access to its private variables. diff --git a/lib/src/io/README.md b/lib/src/io/README.md new file mode 100644 index 000000000..08306bfc9 --- /dev/null +++ b/lib/src/io/README.md @@ -0,0 +1,17 @@ +# Input/Output Shim + +This directory contains an API shim for doing various forms of IO across +different platforms. Dart chooses at compile time which of the three files to +use: + +* `interface.dart` is used by the Dart Analyzer for static checking. It defines + the "expected" interface of the other two files, although there aren't strong + checks that their interfaces are exactly the same. + +* `vm.dart` is used by the Dart VM, and defines IO operations in terms of the + `dart:io` library. + +* `js.dart` is used by JS platforms. On Node.js, it will use Node's `fs` and + `process` APIs for IO operations. On other JS platforms, most IO operations + won't work at all, although messages will still be emitted with + `console.log()` and `console.error()`. diff --git a/lib/src/js/README.md b/lib/src/js/README.md new file mode 100644 index 000000000..d6cb699d2 --- /dev/null +++ b/lib/src/js/README.md @@ -0,0 +1,58 @@ +# JavaScript API + +This directory contains Dart Sass's implementation of the Sass JS API. Dart's JS +interop support is primarily intended for _consuming_ JS libraries from Dart, so +we have to jump through some hoops in order to effectively _produce_ a JS +library with the desired API. + +JS support has its own dedicated entrypoint in [`../js.dart`]. The [`cli_pkg` +package] ensures that when users load Dart Sass _as a library_, this entrypoint +is run instead of the CLI entrypoint, but otherwise it's up to us to set up the +library appropriately. To do so, we use JS interop to define an [`Exports`] +class that is in practice implemented by a CommonJS-like[^1] `exports` object, +and then assign various values to this object. + +[`../js.dart`]: ../js.dart +[`cli_pkg` package]: https://github.com/google/dart_cli_pkg +[`Exports`]: exports.dart + +[^1]: It's not _literally_ CommonJS because it needs to run directly on browsers + as well, but it's still an object named `exports` that we can hang names + off of. + +## Value Types + +The JS API value types pose a particular challenge from Dart. Although every +Dart class is represented by a JavaScript class when compiled to JS, Dart has no +way of specifying what the JS API of those classes should be. What's more, in +order to make the JS API as efficient as possible, we want to be able to pass +the existing Dart [`Value`] objects as-is to custom functions rather than +wrapping them with JS-only wrappers. + +[`Value`]: ../value.dart + +To solve the first problem, in [`reflection.dart`] we use JS interop to wrap the +manual method of defining a JavaScript class. We use this to create a +JS-specific class for each value type, with all the JS-specific methods and +properties defined by Sass's JS API spec. However, while normal JS constructors +just set some properties on `this`, our constructors for these classes return +Dart `Value` objects instead. + +[`reflection.dart`]: reflection.dart + +"But wait," I hear you say, "those `Value` objects aren't instances of the new +JS class you've created!" This is where the deep magic comes in. Once we've +defined our class with its phony constructor, we create a single Dart object of +the given `Value` subclass and _edit its JavaScript prototype chain_ to include +the new class we just created. Once that's done, all the Dart value types will +have exactly the right JS API (including responding correctly to `instanceof`!) +and the constructor will now correctly return an instance of the JS class. + +## Legacy API + +Dart Sass also supports the legacy JS API in the [`legacy`] directory. This hews +as close as possible to the API of the old `node-sass` package which wrapped the +old LibSass implementation. It's no longer being actively updated, but we still +need to support it at least until the next major version release of Dart Sass. + +[`legacy`]: legacy diff --git a/lib/src/parse/README.md b/lib/src/parse/README.md new file mode 100644 index 000000000..32f5ed357 --- /dev/null +++ b/lib/src/parse/README.md @@ -0,0 +1,33 @@ +# Sass Parser + +This directory contains various parsers used by Sass. The two most relevant +classes are: + +* [`Parser`]: The base class of all other parsers, which includes basic + infrastructure, utilities, and methods for parsing common CSS constructs that + appear across multiple different specific parsers. + + [`Parser`]: parser.dart + +* [`StylesheetParser`]: The base class specifically for the initial stylesheet + parse. Almost all of the logic for parsing Sass files, both statement- and + expression-level, lives here. Only places where individual syntaxes differ + from one another are left abstract or overridden by subclasses. + + [`StylesheetParser`]: stylesheet.dart + +All Sass parsing is done by hand using the [`string_scanner`] package, which we +use to read the source [code-unit]-by-code-unit while also tracking source span +information which we can then use to report errors and generate source maps. We +don't use any kind of parser generator, partly because Sass's grammar requires +arbitrary backtracking in various places and partly because handwritten code is +often easier to read and debug. + +[`string_scanner`]: https://pub.dev/packages/string_scanner +[code-unit]: https://developer.mozilla.org/en-US/docs/Glossary/Code_unit + +The parser is simple recursive descent. There's usually a method for each +logical production that either consumes text and returns its corresponding AST +node or throws an exception; in some cases, a method (conventionally beginning +with `try`) will instead consume text and return a node if it matches and return +null without consuming anything if it doesn't. diff --git a/lib/src/value/README.md b/lib/src/value/README.md new file mode 100644 index 000000000..e23644722 --- /dev/null +++ b/lib/src/value/README.md @@ -0,0 +1,13 @@ +# Value Types + +This directory contains definitions for all the SassScript value types. These +definitions are used both to represent SassScript values internally and in the +public Dart API. They are usually produced by [the evaluator] as it evaluates +the expression-level [Sass AST]. + +[the evaluator]: ../visitor/async_evaluate.dart +[Sass AST]: ../ast/sass/README.md + +Sass values are always immutable, even internally. Any changes to them must be +done by creating a new value. In some cases, it's easiest to make a mutable +copy, edit it, and then create a new immutable value from the result. diff --git a/lib/src/visitor/README.md b/lib/src/visitor/README.md new file mode 100644 index 000000000..da951508c --- /dev/null +++ b/lib/src/visitor/README.md @@ -0,0 +1,15 @@ +# Visitors + +This directory contains various types that implement the [visitor pattern] for +[various ASTs]. A few of these, such as [the evaluator] and [the serializer], +implement critical business logic for the Sass compiler. Most of the rest are +either small utilities or base classes for small utilities that need to run over +an AST to determine some kind of information about it. Some are even entirely +unused within Sass itself, and exist only to support users of the [`sass_api`] +package. + +[visitor pattern]: https://en.wikipedia.org/wiki/Visitor_pattern +[various ASTs]: ../ast +[the evaluator]: async_evaluate.dart +[the serializer]: serialize.dart +[`sass_api`]: https://pub.dev/packages/sass_api From cd3b0cc1626f31a909d21fc87112d614cbb013e2 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 13 Nov 2023 14:59:09 -0800 Subject: [PATCH 10/11] Update link to Node.js releases page (#2131) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64bbb851e..844cdf0fc 100644 --- a/README.md +++ b/README.md @@ -403,7 +403,7 @@ releases listed as Current, Active LTS, or Maintenance LTS according to [the Node.js release page][]. Once a Node.js version is out of LTS, Dart Sass considers itself free to break support if necessary. -[the Node.js release page]: https://nodejs.org/en/about/releases/ +[the Node.js release page]: https://nodejs.org/en/about/previous-releases ## Embedded Dart Sass From bf58324451bc53f8b6e152e1d690610662a103d9 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Nov 2023 16:16:49 -0800 Subject: [PATCH 11/11] Poke CI