[Shared Resources] Add types and specs (#3744)

Co-authored-by: Jonny Gerig Meyer <jonny@oddbird.net>
This commit is contained in:
James Stuckey Weber 2024-01-17 21:48:22 -05:00 committed by Natalie Weizenbaum
parent 327b995cae
commit de2dc317c0
7 changed files with 344 additions and 9 deletions

View File

@ -1,13 +1,21 @@
The [`sass` package] on npm is a pure-JavaScript package built from the [Dart The Sass JavaScript API can be used to to drive Sass Compilations from
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
JavaScript. It even allows an application to control {@link Options.importers | JavaScript. It even allows an application to control {@link Options.importers |
how stylesheets are loaded} and {@link Options.functions | define custom how stylesheets are loaded} and {@link Options.functions | define custom
functions}. 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 [`sass` package]: https://www.npmjs.com/package/sass
[Dart Sass]: https://sass-lang.com/dart-sass [Dart Sass]: https://sass-lang.com/dart-sass
[command-line interface]: https://sass-lang.com/documentation/cli/dart-sass [command-line interface]: https://sass-lang.com/documentation/cli/dart-sass
[`sass-embedded` package]: https://www.npmjs.com/package/sass-embedded
## Usage ## 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.

View File

@ -37,6 +37,104 @@ export interface CompileResult {
sourceMap?: RawSourceMap; 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<CompileResult>;
/**
* 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<CompileResult>;
/**
* 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<void>;
}
/** /**
* Synchronously compiles the Sass file at `path` to CSS. If it succeeds it * 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 * 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. * This only allows synchronous {@link Importer}s and {@link CustomFunction}s.
* *
* **Heads up!** When using the `sass-embedded` npm package, * **Heads up!** When using the [sass-embedded] npm package for single
* **{@link compileAsync} is almost always faster than {@link compile}**, due to * compilations, **{@link compileAsync} is almost always faster than
* the overhead of emulating synchronous messaging with worker threads and * {@link compile}**, due to the overhead of emulating synchronous messaging
* concurrent compilations being blocked on main thread. * 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 * @example
* *
@ -99,12 +203,18 @@ export function compileAsync(
* *
* This only allows synchronous {@link Importer}s and {@link CustomFunction}s. * This only allows synchronous {@link Importer}s and {@link CustomFunction}s.
* *
* **Heads up!** When using the `sass-embedded` npm package, * **Heads up!** When using the [sass-embedded] npm package for single
* **{@link compileStringAsync} is almost always faster than * compilations, **{@link compileStringAsync} is almost always faster than
* {@link compileString}**, due to the overhead of emulating synchronous * {@link compileString}**, due to the overhead of emulating synchronous
* messaging with worker threads and concurrent compilations being blocked on * messaging with worker threads and concurrent compilations being blocked on
* main thread. * 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 * @example
* *
* ```js * ```js
@ -162,3 +272,71 @@ export function compileStringAsync(
source: string, source: string,
options?: StringOptions<'async'> options?: StringOptions<'async'>
): Promise<CompileResult>; ): Promise<CompileResult>;
/**
* 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<AsyncCompiler>;

View File

@ -3,11 +3,15 @@
// implementations. // implementations.
export { export {
AsyncCompiler,
CompileResult, CompileResult,
Compiler,
compile, compile,
compileAsync, compileAsync,
compileString, compileString,
compileStringAsync, compileStringAsync,
initCompiler,
initAsyncCompiler,
} from './compile'; } from './compile';
export {Exception} from './exception'; export {Exception} from './exception';
export { export {

View File

@ -12,11 +12,17 @@ import {Options, StringOptions} from './options';
* [Types](#types) * [Types](#types)
* [`CompileResult`](#compileresult) * [`CompileResult`](#compileresult)
* [`Compiler`](#compiler)
* [`dispose()`](#dispose)
* [`AsyncCompiler`](#asynccompiler)
* [`dispose()`](#dispose-1)
* [Functions](#functions) * [Functions](#functions)
* [`compile`](#compile) * [`compile`](#compile)
* [`compileAsync`](#compileasync) * [`compileAsync`](#compileasync)
* [`compileString`](#compilestring) * [`compileString`](#compilestring)
* [`compileStringAsync`](#compilestringasync) * [`compileStringAsync`](#compilestringasync)
* [`initCompiler`](#initcompiler)
* [`initAsyncCompiler`](#initasynccompiler)
## Types ## 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<CompileResult>;
compileStringAsync(
source: string,
options?: StringOptions<'async'>
): Promise<CompileResult>;
```
#### `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<void>;
}
```
## Functions ## Functions
### `compile` ### `compile`
@ -146,3 +232,23 @@ export function compileStringAsync(
options?: StringOptions<'async'> options?: StringOptions<'async'>
): Promise<CompileResult>; ): Promise<CompileResult>;
``` ```
### `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<AsyncCompiler>;
```

View File

@ -40,11 +40,15 @@ proposals.
```ts ```ts
export { export {
AsyncCompiler,
CompileResult, CompileResult,
Compiler,
compile, compile,
compileAsync, compileAsync,
compileString, compileString,
compileStringAsync, compileStringAsync,
initCompiler,
initAsyncCompiler,
} from './compile'; } from './compile';
export {Exception} from './exception'; export {Exception} from './exception';
export { export {