sass/accepted/shared-resources.d.ts.md
James Stuckey Weber de2dc317c0 [Shared Resources] Add types and specs (#3744)
Co-authored-by: Jonny Gerig Meyer <jonny@oddbird.net>
2024-03-27 11:43:24 -07:00

7.0 KiB

Shared Resources in JavaScript API: Draft 1.3

(Issue)

This proposal adds an API design that allows for sharing resources across multiple invocations of the Sass compiler's JavaScript API. This will provide Sass's users with a more efficient way of running Sass compilations across multiple files.

Table of Contents

Summary

Currently, the JavaScript API for Sass only accommodates a single compilation per process. In practice, we have observed build tools are compiling multiple times in response to a single user action. For instance, Vue.js authors using Vite will see a Sass compilation for each <style lang="scss"> tag that appears in their codebase.

While processes can be spun up and down quickly, the combined time can add up to a noticeable impact on performance. The embedded client supports long running processes, and this proposal adds a method for the embedded host to manage the lifecycle of these processes through a Compiler interface.

Design Decisions

Splitting Sync and Async Compilers

When creating a Compiler, users will need to choose either a compiler that provides access to synchronous or asynchronous compilation. While providing both simultaneously from a single Compiler would offer more flexibility, it also adds significant complexity to the API. In practice, we expect most users will only want to use one mode, generally in the mode that is the fastest for the implementation. If synchronous and asynchronous compilations are both needed, users can create multiple Compilers.

Limited API interface

Many build tools allow passing the Sass module as a parameter, which offers flexibility to users on what implementation of Sass is used. Because users may still want to use portions of the JavaScript API unrelated to compilation, we considered having the Compiler interface mirror the top level Sass interface, which would allow users to replace instances of the imported sass class with an instance of the compiler. However, this adds an additional cost to ongoing Sass development. The proposed API does not eliminate this as a possibility in the future.

Flexibility for interfaces on process management

In environments without access to a long-running compiler—for instance, the Dart Sass implementation—the Compiler interface will continue to perform a single compilation per process.

No shared state

This proposal does not change how a single compilation is done, and no state is shared across compilations. Options and importers must be set for each compilation. Future enhancements may introduce shared state, but this proposal only adds the ability to run multiple compilations on a single process.

This also means that the proposal makes no assertions about whether file content has changed. It is also up to the user to determine when to start and stop a long-running compiler process.

Example

Sync Compiler

import * as sass from '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 = compiler.compileString('a {b: c}').css;
}

Async Compiler

import * as sass from '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 compiler.compileStringAsync('a {b: c}').css;
}

API

Types

import {CompileResult} from '../spec/js-api/compile';
import {Options, StringOptions} from '../spec/js-api/options';

initCompiler()

Returns a synchronous Compiler.

export function initCompiler(): Compiler;

initAsyncCompiler()

Resolves with an Async Compiler.

export function initAsyncCompiler(): Promise<AsyncCompiler>;

Compiler

An instance of the synchronous Compiler.

export class Compiler {

If the Compiler is directly constructed (as opposed to the Compiler being created via initCompiler), throw an error.

private constructor();
compile() and compileString()

Only synchronous compilation methods compile() and compileString() must be included, and must have with identical semantics to the Sass interface.

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.
  dispose(): void;
}

Async Compiler

An instance of the asynchronous Compiler.

export class AsyncCompiler {

If the AsyncCompiler is directly constructed (as opposed to the Compiler being created via initAsyncCompiler), throw an error.

private constructor();
compileAsync() and compileStringAsync()

Only asynchronous compilation methods compileAsync() and compileStringAsync() must be included, and must have with identical semantics to the Sass interface.

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.

  dispose(): Promise<void>;
}