From de2dc317c06bfd36d6d6b9d4c2b85dd0410593a8 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 17 Jan 2024 21:48:22 -0500 Subject: [PATCH] [Shared Resources] Add types and specs (#3744) Co-authored-by: Jonny Gerig Meyer --- .../shared-resources.changes.md | 0 .../shared-resources.d.ts.md | 0 js-api-doc/README.md | 49 ++++- js-api-doc/compile.d.ts | 190 +++++++++++++++++- js-api-doc/index.d.ts | 4 + spec/js-api/compile.d.ts.md | 106 ++++++++++ spec/js-api/index.d.ts.md | 4 + 7 files changed, 344 insertions(+), 9 deletions(-) rename {proposal => accepted}/shared-resources.changes.md (100%) rename {proposal => accepted}/shared-resources.d.ts.md (100%) diff --git a/proposal/shared-resources.changes.md b/accepted/shared-resources.changes.md similarity index 100% rename from proposal/shared-resources.changes.md rename to accepted/shared-resources.changes.md diff --git a/proposal/shared-resources.d.ts.md b/accepted/shared-resources.d.ts.md similarity index 100% rename from proposal/shared-resources.d.ts.md rename to accepted/shared-resources.d.ts.md diff --git a/js-api-doc/README.md b/js-api-doc/README.md index e5186312..e8de7894 100644 --- a/js-api-doc/README.md +++ b/js-api-doc/README.md @@ -1,13 +1,21 @@ -The [`sass` package] on npm is a pure-JavaScript package built from the [Dart -Sass] implementation. In addition to Dart Sass's [command-line interface], it -provides a JavaScript API that can be used to drive Sass compilations from +The Sass JavaScript API can be used to to drive Sass Compilations from JavaScript. It even allows an application to control {@link Options.importers | how stylesheets are loaded} and {@link Options.functions | define custom functions}. +The [`sass` package] on npm is a pure-JavaScript package built from the [Dart +Sass] implementation, and includes Dart Sass's [command-line interface]. + +The [`sass-embedded` package] on npm is a JavaScript wrapper around a native +Dart executable, and in general is faster than `sass`. + +Both `sass` and `sass-embedded` provide the same JavaScript API using the same +underlying [Dart Sass] implementation, but have speed and platform tradeoffs. + [`sass` package]: https://www.npmjs.com/package/sass [Dart Sass]: https://sass-lang.com/dart-sass [command-line interface]: https://sass-lang.com/documentation/cli/dart-sass +[`sass-embedded` package]: https://www.npmjs.com/package/sass-embedded ## Usage @@ -112,3 +120,38 @@ of Sass code by passing in a {@link LegacyStringOptions}. } }); ``` + +## Speed + +While multiple factors go into how long Sass compilations take, there are +general speed trends that can help you minimize your compilation time. + +### With the `sass` package + +With the `sass` package, the synchronous calls will be faster than asynchronous +calls due to the overhead of making the entire evaluation process asynchronous. +While the {@link Compiler} and {@link AsyncCompiler} class are available, they +aren't faster than than the module-level compilation methods when using `sass`. + +### With the `sass-embedded` package + +The `sass-embedded` package provides significant speed improvements in certain +situations, and is generally faster than `sass` for large or frequent +compilations. When using the module-level compilation methods, asynchronous +calls are generally faster than synchronous ones due to the overhead of +emulating synchronous messaging with worker threads and concurrent compilations +being blocked on the main thread. + +The {@link Compiler} and {@link AsyncCompiler} classes provide significant +improvements when using the `sass-embedded` package. These classes persist and +reuse a single Dart process across multiple compilations, avoiding the need to +repeatedly start up and tear down a process. + +When compiling a single file using `sass-embedded`, there is not much difference +between the synchronous and asynchronous methods. When running multiple +compilations at the same time, an {@link AsyncCompiler} will be considerably +faster than a synchronous {@link Compiler}. + +Other factors like {@link Functions}, {@link Importers} and the complexity of +your Sass files may also impact what compilation methods work best for your +particular use case. diff --git a/js-api-doc/compile.d.ts b/js-api-doc/compile.d.ts index e8a8ce65..872754c2 100644 --- a/js-api-doc/compile.d.ts +++ b/js-api-doc/compile.d.ts @@ -37,6 +37,104 @@ export interface CompileResult { sourceMap?: RawSourceMap; } +/** + * The result of creating a synchronous compiler. Returned by + * {@link initCompiler}. + * + * @category Compile + */ +export class Compiler { + /** + * Throws an error if constructed directly, instead of via + * {@link initCompiler}. + */ + private constructor(); + + /** + * The {@link compile} method exposed through a Compiler instance while it is + * active. If this is called after {@link dispose} on the Compiler + * instance, an error will be thrown. + * + * During the Compiler instance's lifespan, given the same input, this will + * return an identical result to the {@link compile} method exposed at the + * module root. + */ + compile(path: string, options?: Options<'sync'>): CompileResult; + + /** + * The {@link compileString} method exposed through a Compiler instance while + * it is active. If this is called after {@link dispose} on the Compiler + * instance, an error will be thrown. + * + * During the Compiler instance's lifespan, given the same input, this will + * return an identical result to the {@link compileString} method exposed at + * the module root. + */ + compileString(source: string, options?: StringOptions<'sync'>): CompileResult; + + /** + * Ends the lifespan of this Compiler instance. After this is invoked, all + * calls to the Compiler instance's {@link compile} or {@link compileString} + * methods will result in an error. + */ + dispose(): void; +} + +/** + * The result of creating an asynchronous compiler. Returned by + * {@link initAsyncCompiler}. + * + * @category Compile + */ +export class AsyncCompiler { + /** + * Throws an error if constructed directly, instead of via + * {@link initAsyncCompiler}. + */ + private constructor(); + + /** + * The {@link compileAsync} method exposed through an Async Compiler instance + * while it is active. If this is called after {@link dispose} on the Async + * Compiler instance, an error will be thrown. + * + * During the Async Compiler instance's lifespan, given the same input, this + * will return an identical result to the {@link compileAsync} method exposed + * at the module root. + */ + compileAsync( + path: string, + options?: Options<'async'> + ): Promise; + + /** + * The {@link compileStringAsync} method exposed through an Async Compiler + * instance while it is active. If this is called after {@link dispose} on the + * Async Compiler instance, an error will be thrown. + * + * During the Async Compiler instance's lifespan, given the same input, this + * will return an identical result to the {@link compileStringAsync} method + * exposed at the module root. + */ + compileStringAsync( + source: string, + options?: StringOptions<'async'> + ): Promise; + + /** + * Ends the lifespan of this Async Compiler instance. After this is invoked, + * all subsequent calls to the Compiler instance's `compileAsync` or + * `compileStringAsync` methods will result in an error. + * + * Any compilations that are submitted before `dispose` will not be cancelled, + * and will be allowed to settle. + * + * After all compilations have been settled and Sass completes any internal + * task cleanup, `dispose` will resolve its promise. + */ + dispose(): Promise; +} + /** * Synchronously compiles the Sass file at `path` to CSS. If it succeeds it * returns a {@link CompileResult}, and if it fails it throws an {@link @@ -44,10 +142,16 @@ export interface CompileResult { * * This only allows synchronous {@link Importer}s and {@link CustomFunction}s. * - * **Heads up!** When using the `sass-embedded` npm package, - * **{@link compileAsync} is almost always faster than {@link compile}**, due to - * the overhead of emulating synchronous messaging with worker threads and - * concurrent compilations being blocked on main thread. + * **Heads up!** When using the [sass-embedded] npm package for single + * compilations, **{@link compileAsync} is almost always faster than + * {@link compile}**, due to the overhead of emulating synchronous messaging + * with worker threads and concurrent compilations being blocked on main thread. + * + * If you are running multiple compilations with the [sass-embedded] npm + * package, using a {@link Compiler} will provide some speed improvements over + * the module-level methods, and an {@link AsyncCompiler} will be much faster. + * + * [sass-embedded]: https://www.npmjs.com/package/sass-embedded * * @example * @@ -99,12 +203,18 @@ export function compileAsync( * * This only allows synchronous {@link Importer}s and {@link CustomFunction}s. * - * **Heads up!** When using the `sass-embedded` npm package, - * **{@link compileStringAsync} is almost always faster than + * **Heads up!** When using the [sass-embedded] npm package for single + * compilations, **{@link compileStringAsync} is almost always faster than * {@link compileString}**, due to the overhead of emulating synchronous * messaging with worker threads and concurrent compilations being blocked on * main thread. * + * If you are running multiple compilations with the [sass-embedded] npm + * package, using a {@link Compiler} will provide some speed improvements over + * the module-level methods, and an {@link AsyncCompiler} will be much faster. + * + * [sass-embedded]: https://www.npmjs.com/package/sass-embedded + * * @example * * ```js @@ -162,3 +272,71 @@ export function compileStringAsync( source: string, options?: StringOptions<'async'> ): Promise; + +/** + * Creates a synchronous {@link Compiler}. Each compiler instance exposes the + * {@link compile} and {@link compileString} methods within the lifespan of the + * Compiler. Given identical input, these methods will return results identical + * to their counterparts exposed at the module root. To use asynchronous + * compilation, use {@link initAsyncCompiler}. + * + * When calling the compile functions multiple times, using a compiler instance + * with the [sass-embedded] npm package is much faster than using the top-level + * compilation methods or the [sass] npm package. + * + * [sass-embedded]: https://www.npmjs.com/package/sass-embedded + * + * [sass]: https://www.npmjs.com/package/sass + * + * @example + * + * ```js + * const sass = require('sass'); + * function setup() { + * const compiler = sass.initCompiler(); + * const result1 = compiler.compileString('a {b: c}').css; + * const result2 = compiler.compileString('a {b: c}').css; + * compiler.dispose(); + * + * // throws error + * const result3 = sass.compileString('a {b: c}').css; + * } + * ``` + * @category Compile + * @compatibility dart: "1.70.0", node: false + */ +export function initCompiler(): Compiler; + +/** + * Creates an asynchronous {@link AsyncCompiler}. Each compiler + * instance exposes the {@link compileAsync} and {@link compileStringAsync} + * methods within the lifespan of the Compiler. Given identical input, these + * methods will return results identical to their counterparts exposed at the + * module root. To use synchronous compilation, use {@link initCompiler}; + * + * When calling the compile functions multiple times, using a compiler instance + * with the [sass-embedded] npm package is much faster than using the top-level + * compilation methods or the [sass] npm package. + * + * [sass-embedded]: https://www.npmjs.com/package/sass-embedded + * + * [sass]: https://www.npmjs.com/package/sass + * + * @example + * + * ```js + * const sass = require('sass'); + * async function setup() { + * const compiler = await sass.initAsyncCompiler(); + * const result1 = await compiler.compileStringAsync('a {b: c}').css; + * const result2 = await compiler.compileStringAsync('a {b: c}').css; + * await compiler.dispose(); + * + * // throws error + * const result3 = await sass.compileStringAsync('a {b: c}').css; + * } + * ``` + * @category Compile + * @compatibility dart: "1.70.0", node: false + */ +export function initAsyncCompiler(): Promise; diff --git a/js-api-doc/index.d.ts b/js-api-doc/index.d.ts index 2a41b586..3d6793f1 100644 --- a/js-api-doc/index.d.ts +++ b/js-api-doc/index.d.ts @@ -3,11 +3,15 @@ // implementations. export { + AsyncCompiler, CompileResult, + Compiler, compile, compileAsync, compileString, compileStringAsync, + initCompiler, + initAsyncCompiler, } from './compile'; export {Exception} from './exception'; export { diff --git a/spec/js-api/compile.d.ts.md b/spec/js-api/compile.d.ts.md index 35554a63..7accee1a 100644 --- a/spec/js-api/compile.d.ts.md +++ b/spec/js-api/compile.d.ts.md @@ -12,11 +12,17 @@ import {Options, StringOptions} from './options'; * [Types](#types) * [`CompileResult`](#compileresult) + * [`Compiler`](#compiler) + * [`dispose()`](#dispose) + * [`AsyncCompiler`](#asynccompiler) + * [`dispose()`](#dispose-1) * [Functions](#functions) * [`compile`](#compile) * [`compileAsync`](#compileasync) * [`compileString`](#compilestring) * [`compileStringAsync`](#compilestringasync) + * [`initCompiler`](#initcompiler) + * [`initAsyncCompiler`](#initasynccompiler) ## Types @@ -34,6 +40,86 @@ export interface CompileResult { } ``` +### `Compiler` + +The class returned by creating a synchronous compiler with [`initCompiler()`]. +If the class is directly constructed (as opposed to the Compiler being created +via `initCompiler`), throw an error. + +Only synchronous compilation methods [`compile()`] and [`compileString()`] must +be included, and must have identical semantics to the [Sass interface]. + +[`initCompiler()`]: #initcompiler +[`compile()`]: #compile +[`compilestring()`]: #compilestring +[Sass interface]: ./index.d.ts.md + +```ts +export class Compiler { + private constructor(); + compile(path: string, options?: Options<'sync'>): CompileResult; + + compileString(source: string, options?: StringOptions<'sync'>): CompileResult; +``` + +#### `dispose()` + +When `dispose` is invoked on a Compiler: + +* Any subsequent invocations of `compile` and `compileString` must throw an + error. + +```ts + dispose(): void; +} +``` + +### `AsyncCompiler` + +The object returned by creating an asynchronous compiler with +[`initAsyncCompiler()`]. If the class is directly constructed (as opposed to the +AsyncCompiler being created via `initAsyncCompiler`), throw an error. + +Only asynchronous compilation methods [`compileAsync()`] and +[`compileStringAsync()`] must be included, and must have identical semantics to +the [Sass interface]. + +[`initAsyncCompiler()`]: #initasynccompiler +[`compileasync()`]: #compileasync +[`compilestringasync()`]: #compilestringasync + +```ts +export class AsyncCompiler { + private constructor(); + compileAsync( + path: string, + options?: Options<'async'> + ): Promise; + + compileStringAsync( + source: string, + options?: StringOptions<'async'> + ): Promise; +``` + +#### `dispose()` + +When `dispose` is invoked on an Async Compiler: + +* Any subsequent invocations of `compileAsync` and `compileStringAsync` must + throw an error. + +* Any compilations that have not yet been settled must be allowed to settle, and + not be cancelled. + +* Resolves a Promise when all compilations have been settled, and disposal is + complete. + +```ts + dispose(): Promise; +} +``` + ## Functions ### `compile` @@ -146,3 +232,23 @@ export function compileStringAsync( options?: StringOptions<'async'> ): Promise; ``` + +### `initCompiler` + +Returns a synchronous [Compiler]. + +[Compiler]: #compiler + +```ts +export function initCompiler(): Compiler; +``` + +### `initAsyncCompiler` + +Resolves with an [Async Compiler]. + +[Async Compiler]: #asynccompiler + +```ts +export function initAsyncCompiler(): Promise; +``` diff --git a/spec/js-api/index.d.ts.md b/spec/js-api/index.d.ts.md index e57fd0dd..1821dd4c 100644 --- a/spec/js-api/index.d.ts.md +++ b/spec/js-api/index.d.ts.md @@ -40,11 +40,15 @@ proposals. ```ts export { + AsyncCompiler, CompileResult, + Compiler, compile, compileAsync, compileString, compileStringAsync, + initCompiler, + initAsyncCompiler, } from './compile'; export {Exception} from './exception'; export {