From 076414d3e793ab907591b34fc8a3f694529277d4 Mon Sep 17 00:00:00 2001 From: Ed Rivas Date: Thu, 18 Jan 2024 02:48:21 +0000 Subject: [PATCH] [Shared Resources] dart-sass implementation (#2134) Co-authored-by: James Stuckey Weber Co-authored-by: Jonny Gerig Meyer --- CHANGELOG.md | 16 +++++- lib/src/js.dart | 6 ++ lib/src/js/compiler.dart | 113 ++++++++++++++++++++++++++++++++++++++ lib/src/js/exports.dart | 4 ++ pkg/sass_api/CHANGELOG.md | 4 ++ pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- tool/grind.dart | 4 ++ 8 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 lib/src/js/compiler.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 90117468e..7374727f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ -## 1.69.8 +## 1.70.0 + +### JavaScript API + +* Add a `sass.initCompiler()` function that returns a `sass.Compiler` object + which supports `compile()` and `compileString()` methods with the same API as + the global Sass object. On the Node.js embedded host, each `sass.Compiler` + object uses a single long-lived subprocess, making compiling multiple + stylesheets much more efficient. + +* Add a `sass.initAsyncCompiler()` function that returns a `sass.AsyncCompiler` + object which supports `compileAsync()` and `compileStringAsync()` methods with + the same API as the global Sass object. On the Node.js embedded host, each + `sass.AsynCompiler` object uses a single long-lived subprocess, making + compiling multiple stylesheets much more efficient. ### Embedded Sass diff --git a/lib/src/js.dart b/lib/src/js.dart index 9a2c51c06..92ab23f66 100644 --- a/lib/src/js.dart +++ b/lib/src/js.dart @@ -5,6 +5,7 @@ import 'js/exception.dart'; import 'js/exports.dart'; import 'js/compile.dart'; +import 'js/compiler.dart'; import 'js/legacy.dart'; import 'js/legacy/types.dart'; import 'js/legacy/value.dart'; @@ -24,6 +25,11 @@ void main() { exports.compileAsync = allowInteropNamed('sass.compileAsync', compileAsync); exports.compileStringAsync = allowInteropNamed('sass.compileStringAsync', compileStringAsync); + exports.initCompiler = allowInteropNamed('sass.initCompiler', initCompiler); + exports.initAsyncCompiler = + allowInteropNamed('sass.initAsyncCompiler', initAsyncCompiler); + exports.Compiler = compilerClass; + exports.AsyncCompiler = asyncCompilerClass; exports.Value = valueClass; exports.SassBoolean = booleanClass; exports.SassArgumentList = argumentListClass; diff --git a/lib/src/js/compiler.dart b/lib/src/js/compiler.dart new file mode 100644 index 000000000..ab1886b3f --- /dev/null +++ b/lib/src/js/compiler.dart @@ -0,0 +1,113 @@ +// Copyright 2024 Google LLC. 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:js_util'; + +import 'package:async/async.dart'; +import 'package:node_interop/js.dart'; + +import 'compile.dart'; +import 'compile_options.dart'; +import 'reflection.dart'; +import 'utils.dart'; + +/// The Dart Compiler class. +class Compiler { + /// A flag signifying whether the instance has been disposed. + bool _disposed = false; + + /// Checks if `dispose()` has been called on this instance, and throws an + /// error if it has. Used to verify that compilation methods are not called + /// after disposal. + void _throwIfDisposed() { + if (_disposed) { + jsThrow(JsError('Compiler has already been disposed.')); + } + } +} + +/// The Dart Async Compiler class. +class AsyncCompiler extends Compiler { + /// A set of all compilations, tracked to ensure all compilations complete + /// before async disposal resolves. + final FutureGroup compilations = FutureGroup(); + + /// Adds a compilation to the FutureGroup. + void addCompilation(Promise compilation) { + Future comp = promiseToFuture(compilation); + var wrappedComp = comp.catchError((err) { + /// Ignore errors so FutureGroup doesn't close when a compilation fails. + }); + compilations.add(wrappedComp); + } +} + +/// The JavaScript `Compiler` class. +final JSClass compilerClass = () { + var jsClass = createJSClass( + 'sass.Compiler', + (Object self) => { + jsThrow(JsError(("Compiler can not be directly constructed. " + "Please use `sass.initCompiler()` instead."))) + }); + + jsClass.defineMethods({ + 'compile': (Compiler self, String path, [CompileOptions? options]) { + self._throwIfDisposed(); + return compile(path, options); + }, + 'compileString': (Compiler self, String source, + [CompileStringOptions? options]) { + self._throwIfDisposed(); + return compileString(source, options); + }, + 'dispose': (Compiler self) { + self._disposed = true; + }, + }); + + getJSClass(Compiler()).injectSuperclass(jsClass); + return jsClass; +}(); + +Compiler initCompiler() => Compiler(); + +/// The JavaScript `AsyncCompiler` class. +final JSClass asyncCompilerClass = () { + var jsClass = createJSClass( + 'sass.AsyncCompiler', + (Object self) => { + jsThrow(JsError(("AsyncCompiler can not be directly constructed. " + "Please use `sass.initAsyncCompiler()` instead."))) + }); + + jsClass.defineMethods({ + 'compileAsync': (AsyncCompiler self, String path, + [CompileOptions? options]) { + self._throwIfDisposed(); + var compilation = compileAsync(path, options); + self.addCompilation(compilation); + return compilation; + }, + 'compileStringAsync': (AsyncCompiler self, String source, + [CompileStringOptions? options]) { + self._throwIfDisposed(); + var compilation = compileStringAsync(source, options); + self.addCompilation(compilation); + return compilation; + }, + 'dispose': (AsyncCompiler self) { + self._disposed = true; + return futureToPromise((() async { + self.compilations.close(); + await self.compilations.future; + })()); + } + }); + + getJSClass(AsyncCompiler()).injectSuperclass(jsClass); + return jsClass; +}(); + +Promise initAsyncCompiler() => futureToPromise((() async => AsyncCompiler())()); diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index ee5a74471..3cf5bb7a5 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -19,6 +19,10 @@ class Exports { external set compileStringAsync(Function function); external set compile(Function function); external set compileAsync(Function function); + external set initCompiler(Function function); + external set initAsyncCompiler(Function function); + external set Compiler(JSClass function); + external set AsyncCompiler(JSClass function); external set info(String info); external set Exception(JSClass function); external set Logger(LoggerNamespace namespace); diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 8faa204e3..99a7689e4 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.3.0 + +* No user-visible changes. + ## 9.2.7 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index c2bc48591..5158fc4e4 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.7 +version: 9.3.0 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.7 + sass: 1.70.0 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 2722f7bcc..f14d6b822 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.69.8-dev +version: 1.70.0 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass diff --git a/tool/grind.dart b/tool/grind.dart index 425730c03..92f919deb 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -51,6 +51,10 @@ void main(List args) { 'compileAsync', 'compileString', 'compileStringAsync', + 'initCompiler', + 'initAsyncCompiler', + 'Compiler', + 'AsyncCompiler', 'Logger', 'SassArgumentList', 'SassBoolean',