From a7845054cca3e57c920fe5554d41d5fed0301b52 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:50:54 -0700 Subject: [PATCH 01/51] Add first class mixins to embedded sass protobuf and JS API (#3674) --- js-api-doc/index.d.ts | 1 + js-api-doc/value/index.d.ts | 10 +++++++++ js-api-doc/value/mixin.d.ts | 14 ++++++++++++ spec/EMBEDDED_PROTOCOL_VERSION | 2 +- spec/embedded_sass.proto | 12 ++++++++++ spec/js-api/index.d.ts.md | 1 + spec/js-api/value/index.d.ts.md | 15 +++++++++++++ spec/js-api/value/mixin.d.ts.md | 40 +++++++++++++++++++++++++++++++++ 8 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 js-api-doc/value/mixin.d.ts create mode 100644 spec/js-api/value/mixin.d.ts.md diff --git a/js-api-doc/index.d.ts b/js-api-doc/index.d.ts index 395c5ced..2a41b586 100644 --- a/js-api-doc/index.d.ts +++ b/js-api-doc/index.d.ts @@ -40,6 +40,7 @@ export { SassFunction, SassList, SassMap, + SassMixin, SassNumber, SassString, Value, diff --git a/js-api-doc/value/index.d.ts b/js-api-doc/value/index.d.ts index c9711da1..38d28df8 100644 --- a/js-api-doc/value/index.d.ts +++ b/js-api-doc/value/index.d.ts @@ -6,6 +6,7 @@ import {SassColor} from './color'; import {SassFunction} from './function'; import {ListSeparator} from './list'; import {SassMap} from './map'; +import {SassMixin} from './mixin'; import {SassNumber} from './number'; import {SassString} from './string'; @@ -22,6 +23,7 @@ export {SassColor} from './color'; export {SassFunction} from './function'; export {SassList, ListSeparator} from './list'; export {SassMap} from './map'; +export {SassMixin} from './mixin'; export {SassNumber} from './number'; export {SassString} from './string'; @@ -156,6 +158,14 @@ export abstract class Value implements ValueObject { */ assertMap(name?: string): SassMap; + /** + * Throws if `this` isn't a {@link SassMixin}. + * + * @param name - The name of the function argument `this` came from (without + * the `$`) if it came from an argument. Used for error reporting. + */ + assertMixin(name?: string): SassMixin; + /** * Throws if `this` isn't a {@link SassNumber}. * diff --git a/js-api-doc/value/mixin.d.ts b/js-api-doc/value/mixin.d.ts new file mode 100644 index 00000000..44af9967 --- /dev/null +++ b/js-api-doc/value/mixin.d.ts @@ -0,0 +1,14 @@ +import {Value} from './index'; + +/** + * Sass's [mixin type](https://sass-lang.com/documentation/values/mixins). + * + * @category Custom Function + */ +export class SassMixin extends Value { + /** + * It is not possible to construct a Sass mixin outside of Sass. Attempting to + * construct one will throw an exception. + */ + constructor(); +} diff --git a/spec/EMBEDDED_PROTOCOL_VERSION b/spec/EMBEDDED_PROTOCOL_VERSION index ccbccc3d..276cbf9e 100644 --- a/spec/EMBEDDED_PROTOCOL_VERSION +++ b/spec/EMBEDDED_PROTOCOL_VERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/spec/embedded_sass.proto b/spec/embedded_sass.proto index 7a931d69..b0307006 100644 --- a/spec/embedded_sass.proto +++ b/spec/embedded_sass.proto @@ -887,6 +887,17 @@ message Value { string signature = 2; } + // A first-class mixin defined in the compiler. New `CompilerMixin`s may + // only be created by the compiler, but the host may pass `CompilerMixin`s + // back to the compiler as long as their IDs match IDs of mixins received + // by the host during that same compilation. + message CompilerMixin { + // A unique ID for this mixin. The compiler is responsible for generating + // this ID and ensuring it's unique across all mixins passed to the host + // for this compilation. Mandatory. + uint32 id = 1; + } + // A SassScript argument list value. This represents rest arguments passed to // a function's `$arg...` parameter. Unlike a normal `List`, an argument list // has an associated keywords map which tracks keyword arguments passed in @@ -1000,6 +1011,7 @@ message Value { ArgumentList argument_list = 10; HwbColor hwb_color = 11; Calculation calculation = 12; + CompilerMixin compiler_mixin = 13; } } diff --git a/spec/js-api/index.d.ts.md b/spec/js-api/index.d.ts.md index 8d15838c..e57fd0dd 100644 --- a/spec/js-api/index.d.ts.md +++ b/spec/js-api/index.d.ts.md @@ -77,6 +77,7 @@ export { SassFunction, SassList, SassMap, + SassMixin, SassNumber, SassString, Value, diff --git a/spec/js-api/value/index.d.ts.md b/spec/js-api/value/index.d.ts.md index 48cc67ed..654f2f4c 100644 --- a/spec/js-api/value/index.d.ts.md +++ b/spec/js-api/value/index.d.ts.md @@ -9,6 +9,7 @@ import {SassColor} from './color'; import {SassFunction} from './function'; import {ListSeparator} from './list'; import {SassMap} from './map'; +import {SassMixin} from './mixin'; import {SassNumber} from './number'; import {SassString} from './string'; @@ -25,6 +26,7 @@ export {SassColor} from './color'; export {SassFunction} from './function'; export {SassList, ListSeparator} from './list'; export {SassMap} from './map'; +export {SassMixin} from './mixin'; export {SassNumber} from './number'; export {SassString} from './string'; ``` @@ -48,6 +50,7 @@ export {SassString} from './string'; * [`assertColor`](#assertcolor) * [`assertFunction`](#assertfunction) * [`assertMap`](#assertmap) + * [`assertMixin`](#assertmixin) * [`assertNumber`](#assertnumber) * [`assertString`](#assertstring) * [`tryMap`](#trymap) @@ -231,6 +234,18 @@ Return `this.tryMap()` if it's not null, and throw an error otherwise. assertMap(name?: string): SassMap; ``` +#### `assertMixin` + +Returns `this` if it's a [`SassMixin`] and throws an error otherwise. + +[`SassMixin`]: mixin.d.ts.md + +> The `name` parameter may be used for error reporting. + +```ts +assertMixin(name?: string): SassMixin; +``` + #### `assertNumber` Returns `this` if it's a [`SassNumber`] and throws an error otherwise. diff --git a/spec/js-api/value/mixin.d.ts.md b/spec/js-api/value/mixin.d.ts.md new file mode 100644 index 00000000..3835e872 --- /dev/null +++ b/spec/js-api/value/mixin.d.ts.md @@ -0,0 +1,40 @@ +# Mixin API + +```ts +import {Value} from './index'; +``` + +## Table of Contents + +* [Types](#types) + * [`SassMixin`](#sassmixin) + * [`internal`](#internal) + * [Constructor](#constructor) + +## Types + +### `SassMixin` + +The JS API representation of a Sass mixin. + +```ts +export class SassMixin extends Value { +``` + +#### `internal` + +The [private `internal` field] refers to a Sass mixin. + +[private `internal` field]: index.d.ts.md#internal + +#### Constructor + +Throws an error. + +```ts +constructor(); +``` + +```ts +} // SassMixin +``` From 1de725630ef99f7e3ed2c4d345f71acc36f31c07 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 6 Oct 2023 10:32:35 -0400 Subject: [PATCH 02/51] Remove generic change overload --- proposal/color-4-new-spaces-js.changes.md | 4 ++++ proposal/color-4-new-spaces-js.d.ts.md | 18 ++++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/proposal/color-4-new-spaces-js.changes.md b/proposal/color-4-new-spaces-js.changes.md index 703be397..ed4af5a7 100644 --- a/proposal/color-4-new-spaces-js.changes.md +++ b/proposal/color-4-new-spaces-js.changes.md @@ -1,3 +1,7 @@ +## Draft 1.2 + +* Remove generic `change` overload, and make `space` optional on others. + ## Draft 1.1 * Clarify values in `channels` and `channelsOrNull`. diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index 9648876e..84fe1fe6 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -565,18 +565,12 @@ as the result of changing some of [`internal`]'s components. [`Changing a Component Value`]: #changing-a-component-value ```ts -change( - options: { - [key in ChannelName]?: number | null; - } & {alpha?: number} -): SassColor; - change( options: { [key in ChannelNameHSL]?: number | null; } & { alpha?: number; - space: ColorSpaceHSL; + space?: ColorSpaceHSL; } ): SassColor; @@ -585,7 +579,7 @@ change( [key in ChannelNameHWB]?: number | null; } & { alpha?: number; - space: ColorSpaceHWB; + space?: ColorSpaceHWB; } ): SassColor; @@ -594,7 +588,7 @@ change( [key in ChannelNameLab]?: number | null; } & { alpha?: number | null; - space: ColorSpaceLab; + space?: ColorSpaceLab; } ): SassColor; @@ -603,7 +597,7 @@ change( [key in ChannelNameLCH]?: number | null; } & { alpha?: number | null; - space: ColorSpaceLCH; + space?: ColorSpaceLCH; } ): SassColor; @@ -612,7 +606,7 @@ change( [key in ChannelNameRGB]?: number | null; } & { alpha?: number | null; - space: ColorSpaceRGB; + space?: ColorSpaceRGB; } ): SassColor; @@ -621,7 +615,7 @@ change( [key in ChannelNameXYZ]?: number | null; } & { alpha?: number | null; - space: ColorSpaceXYZ; + space?: ColorSpaceXYZ; } ): SassColor; ``` From f2c8291a0de1e0354f5c91bd3f777ff50a3f343a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 6 Oct 2023 10:45:36 -0400 Subject: [PATCH 03/51] Return immutable types --- proposal/color-4-new-spaces-js.changes.md | 3 +++ proposal/color-4-new-spaces-js.d.ts.md | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/proposal/color-4-new-spaces-js.changes.md b/proposal/color-4-new-spaces-js.changes.md index ed4af5a7..b7271cfe 100644 --- a/proposal/color-4-new-spaces-js.changes.md +++ b/proposal/color-4-new-spaces-js.changes.md @@ -2,6 +2,9 @@ * Remove generic `change` overload, and make `space` optional on others. +* Return `immutable` types for `channels` and `channelsOrNull`, and remove + assumption of 3 channels. + ## Draft 1.1 * Clarify values in `channels` and `channelsOrNull`. diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index 84fe1fe6..a0c50b46 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -50,6 +50,8 @@ proposal]. ## API ```ts +import {List, ValueObject} from 'immutable'; + import {Value} from '../spec/js-api/value'; ``` @@ -197,7 +199,7 @@ Returns an array of channel values (excluding alpha) for [`internal`], with * Return `channels`. ```ts -get channelsOrNull(): [number | null, number | null, number | null]; +get channelsOrNull(): List; ``` [missing components]: ./color-4-new-spaces.md#missing-components @@ -223,7 +225,7 @@ This algorithm returns an array of channel values (excluding alpha) for [`this.channelsOrNull`]: #channelsornull ```ts -get channels(): [number, number, number]; +get channels(): List; ``` #### `channel` From 0341873b96dd1575a9d757bb15faec2c90897c9d Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Fri, 6 Oct 2023 23:55:19 +0200 Subject: [PATCH 04/51] Fix typos in the deprecation APi proposal (#3708) --- accepted/deprecations-api.d.ts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/deprecations-api.d.ts.md b/accepted/deprecations-api.d.ts.md index e8a3b19a..4e8a5fbd 100644 --- a/accepted/deprecations-api.d.ts.md +++ b/accepted/deprecations-api.d.ts.md @@ -134,7 +134,7 @@ The situations we chose to warn for are: `futureDeprecations`. In this scenario, the future deprecation will still be treated as fatal, - but we want to warn users to prevent situtations where a user tries to + but we want to warn users to prevent situations where a user tries to make every deprecation fatal and ends up including future ones too. * an obsolete deprecation is passed to `fatalDeprecations`. @@ -326,7 +326,7 @@ behavior of their global counterparts for compatibility reasons. #### `slash-div` -Deprecation for treaing `/` as division. +Deprecation for treating `/` as division. Update the proposal for forward slash as a separator to say that it emits deprecation warnings with ID 'slash-div'. From 3bd90481d5d27bf140800adfeb4ef73920360477 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 6 Oct 2023 16:46:51 -0700 Subject: [PATCH 05/51] Move and restructure how we specify scopes --- accepted/module-system.md | 2 +- accepted/more-math-functions.md | 2 +- spec/at-rules/for.md | 19 +++++++++--------- spec/at-rules/function.md | 4 ++-- spec/at-rules/import.md | 4 ++-- spec/at-rules/mixin.md | 14 ++++++++------ spec/modules.md | 7 +++---- spec/spec.md | 34 +++++++++++++++++++++++++++++++++ spec/variables.md | 21 ++++---------------- 9 files changed, 65 insertions(+), 42 deletions(-) diff --git a/accepted/module-system.md b/accepted/module-system.md index 78df7541..4531a61c 100644 --- a/accepted/module-system.md +++ b/accepted/module-system.md @@ -1187,7 +1187,7 @@ Given a source file `file`, a [configuration](#configuration) `config`, and an * Otherwise, let `scope` be the scope of the innermost block such that `scope` already has a variable named `name`. Set `scope`'s variable `name` to `value`. - [scope]: ../spec/variables.md#scope + [scope]: ../spec/spec.md#scope * When a top-level mixin or function declaration `declaration` is encountered: diff --git a/accepted/more-math-functions.md b/accepted/more-math-functions.md index 88bad35e..99e49144 100644 --- a/accepted/more-math-functions.md +++ b/accepted/more-math-functions.md @@ -125,7 +125,7 @@ To execute a `VariableDeclaration` `declaration`: variable named `name`, set the innermost block's scope's variable `name` to `value`.~~ -[scope]: ../spec/variables.md#scope +[scope]: ../spec/spec.md#scope * **Otherwise, if `resolved` is null, get the innermost block containing `declaration` and set its scope's variable `name` to `value`.** diff --git a/spec/at-rules/for.md b/spec/at-rules/for.md index 0c440062..99d168b7 100644 --- a/spec/at-rules/for.md +++ b/spec/at-rules/for.md @@ -53,15 +53,16 @@ To execute a `@for` rule `rule`: * While `i` is not equal to `to`: - * Let `scope` be a new [scope]. + * [In a new scope]: - * Add a variable with `rule`'s `VariableName` as its name and `i` as its value - to `scope`. + * Add a variable with `rule`'s `VariableName` as its name and `i` as its value + to the [current scope]. - > Note that this variable will have the same unit that `from`. - - * Execute the `ForBlock`'s statements in `scope`. - - * Set `i` to `i + direction`. + > Note that this variable will have the same unit that `from`. - [scope]: ../variables.md#scope + * Execute the `ForBlock`'s statements. + + * Set `i` to `i + direction`. + + [In a new scope]: ../spec.md#running-in-a-new-scope + [current scope]: ../spec.md#scope diff --git a/spec/at-rules/function.md b/spec/at-rules/function.md index 6011d6bc..5d118072 100644 --- a/spec/at-rules/function.md +++ b/spec/at-rules/function.md @@ -42,6 +42,6 @@ To execute a `@function` rule `rule`: [the current module]: ../spec.md#current-module [the current import context]: ../spec.md#current-import-context -* Otherwise, set the innermost block's [scope][]'s function `name` to `value`. +* Otherwise, set the [current scope]'s function `name` to `rule`. - [scope]: ../variables.md#scope + [current scope]: ../spec.md#scope diff --git a/spec/at-rules/import.md b/spec/at-rules/import.md index b5070b66..f4d1bffb 100644 --- a/spec/at-rules/import.md +++ b/spec/at-rules/import.md @@ -138,7 +138,7 @@ To execute an `@import` rule `rule`: * Add `imported`'s [extensions][] to the current module. * If the `@import` rule is nested within at-rules and/or style rules, add each - member in `imported` to the local [scope][]. + member in `imported` to the [current scope]. * Otherwise, add each member in `imported` to the current import context and the current module. @@ -158,4 +158,4 @@ To execute an `@import` rule `rule`: [current import context]: ../spec.md#current-import-context [resolving `imported`'s extensions]: extend.md#resolving-a-modules-extensions [extensions]: extend.md#extension - [scope]: ../variables.md#scope + [current scope]: ../spec.md#scope diff --git a/spec/at-rules/mixin.md b/spec/at-rules/mixin.md index c5f7c2c3..a142f620 100644 --- a/spec/at-rules/mixin.md +++ b/spec/at-rules/mixin.md @@ -33,20 +33,22 @@ To execute a `@mixin` rule `rule`: * If `rule` is outside of any block of statements: - * If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s + * If `name` *doesn't* begin with `-` or `_`, set [the current module]'s mixin `name` to `rule`. > This overrides the previous definition, if one exists. - * Set [the current import context][]'s mixin `name` to `rule`. + * Set [the current import context]'s mixin `name` to `rule`. > This happens regardless of whether or not it begins with `-` or `_`. -* Otherwise, set the innermost block's [scope][]'s mixin `name` to `value`. +* Otherwise, set the [current scope]'s mixin `name` to `rule`. - [scope]: ../variables.md#scope - [the current module]: ../spec.md#current-module - [the current import context]: ../spec.md#current-import-context + > This overrides the previous definition, if one exists. + +[the current module]: ../spec.md#current-module +[the current import context]: ../spec.md#current-import-context +[current scope]: ../spec.md#scope ## `@include` diff --git a/spec/modules.md b/spec/modules.md index 79b25eef..b2b5065b 100644 --- a/spec/modules.md +++ b/spec/modules.md @@ -426,13 +426,12 @@ and returns a member of type `type` or null. * If `name` is a plain `Identifier` or a `Variable` that's not a `NamespacedVariable`: - * Let `scope` be the [scope][] of the innermost block containing the current - statement such that `scope` has a member of type `type` named `name`, or - null if no such scope exists. + * Let `scope` be the [current scope] or its innermost parent such that `scope` + has a member of type `type` named `name`, or null if no such scope exists. * If `scope` is not null, return `scope`'s value of type `type` named `name`. - [scope]: variables.md#scope + [current scope]: spec.md#scope * If `name` is a [`NamespacedIdentifier`](#syntax) of the form `namespace.raw-name` or a [`Variable`][] of the form `namespace.$raw-name`: diff --git a/spec/spec.md b/spec/spec.md index 14a78b3c..b8d8b4f0 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -19,10 +19,14 @@ the language this way. ## Table of Contents * [Definitions](#definitions) + * [Scope](#scope) + * [Global Scope](#global-scope) * [Current Source File](#current-source-file) * [Current Configuration](#current-configuration) * [Current Import Context](#current-import-context) * [Current Module](#current-module) +* [Procedures](#procedures) + * [Running in a New Scope](#running-in-a-new-scope) * [Semantics](#semantics) * [Compiling a Path](#compiling-a-path) * [Compiling a String](#compiling-a-string) @@ -30,6 +34,23 @@ the language this way. ## Definitions +### Scope + +A *scope* is a mutable structure that contains: + +* The scope's *variables*: a mapping from identifiers to SassScript values. +* The scope's *mixins*: a mapping from identifiers to mixins. +* The scope's *functions*: a mapping from identifiers to functions. +* The scope's *parent*: a reference to another scope, which may be unset. + +One scope at a time is designated the *current scope*. By default, this is the +[global scope](#global-scope). + +### Global Scope + +The *global scope* is the scope shared among the top level of all Sass files. It +has no parent. + ### Current Source File The *current source file* is the [source file][] that was passed to the @@ -64,6 +85,19 @@ invocation of [Executing a File](#executing-a-file). > Because a module is only made immutable (other than its variables) when > execution has finished, the current module is always mutable. +## Procedures + +### Running in a New Scope + +To run a set of steps *in a new scope*: + +* Let `parent` be the [current scope]. + + [current scope]: #scope + +* Return the result of running the given steps with the current scope set to an + empty scope with `parent` as its parent. + ## Semantics ### Compiling a Path diff --git a/spec/variables.md b/spec/variables.md index c9fb7e39..c6801eff 100644 --- a/spec/variables.md +++ b/spec/variables.md @@ -3,9 +3,6 @@ ## Table of Contents * [Syntax](#syntax) -* [Definitions](#definitions) - * [Scope](#scope) - * [Global Scope](#global-scope) * [Semantics](#semantics) * [Executing a Variable Declaration](#executing-a-variable-declaration) * [Evaluating a Variable](#evaluating-a-variable) @@ -27,18 +24,6 @@ the `.$` in `NamespacedVariable`. Each of `!global` and `!default` is allowed at most once in `VariableDeclaration`. As with all statements, a `VariableDeclaration` must be separated from other statements with a semicolon. -## Definitions - -### Scope - -A *scope* is a mapping from variable names to values. Every block of statements -delimited by `{` and `}` in SCSS or by indentation in the indented syntax has an -associated scope. - -### Global Scope - -The *global scope* is the scope shared among the top level of all Sass files. - ## Semantics ### Executing a Variable Declaration @@ -107,8 +92,10 @@ To execute a `VariableDeclaration` `declaration`: > This also overrides the previous definition. -* Otherwise, if `resolved` is null, get the innermost block containing - `declaration` and set its scope's variable `name` to `value`. +* Otherwise, if `resolved` is null, set the [current scope]'s variable `name` to + `value`. + + [current scope]: spec.md#scope * Otherwise, set `resolved`'s value to `value`. From f4b191d16f4dfbe3e66700fd1f95cc6e911f5140 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 6 Oct 2023 18:03:51 -0700 Subject: [PATCH 06/51] [First-Class Mixins] Flush to spec Closes #626 --- spec/at-rules/function.md | 28 ++++++++++-- spec/at-rules/mixin.md | 39 ++++++++++------ spec/built-in-modules.md | 38 ++++++++++++++++ spec/built-in-modules/meta.md | 86 +++++++++++++++++++++++++++++++++-- spec/types/functions.md | 62 +++++++++++++++++++++++++ spec/types/mixins.md | 63 +++++++++++++++++++++++++ 6 files changed, 293 insertions(+), 23 deletions(-) create mode 100644 spec/built-in-modules.md create mode 100644 spec/types/functions.md create mode 100644 spec/types/mixins.md diff --git a/spec/at-rules/function.md b/spec/at-rules/function.md index 5d118072..aa3fd5c6 100644 --- a/spec/at-rules/function.md +++ b/spec/at-rules/function.md @@ -28,20 +28,38 @@ To execute a `@function` rule `rule`: [vendor prefix]: ../syntax.md#vendor-prefix +* Let `parent` be the [current scope]. + + [current scope]: ../spec.md#scope + +* Let `function` be a [function] named `name` which does the following when + executed with `args`: + + [function]: ../types/functions.md + + * With the current scope set to an empty [scope] with `parent` as its parent: + + * Evaluate `args` with `rule`'s `ArgumentDeclaration`. + + * Execute each statement in `rule`. + + * Return the value from the `@return` rule if one was executed, or throw an + error if no `@return` rule was executed. + + [scope]: ../spec.md#scope + * If `rule` is outside of any block of statements: * If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s - function `name` to `rule`. + function `name` to `function`. > This overrides the previous definition, if one exists. - * Set [the current import context][]'s function `name` to `rule`. + * Set [the current import context][]'s function `name` to `function`. > This happens regardless of whether or not it begins with `-` or `_`. [the current module]: ../spec.md#current-module [the current import context]: ../spec.md#current-import-context -* Otherwise, set the [current scope]'s function `name` to `rule`. - - [current scope]: ../spec.md#scope +* Otherwise, set the [current scope]'s function `name` to `function`. diff --git a/spec/at-rules/mixin.md b/spec/at-rules/mixin.md index a142f620..d154d199 100644 --- a/spec/at-rules/mixin.md +++ b/spec/at-rules/mixin.md @@ -31,25 +31,41 @@ To execute a `@mixin` rule `rule`: * Let `name` be the value of `rule`'s `Identifier`. +* Let `parent` be the [current scope]. + + [current scope]: ../spec.md#scope + +* Let `mixin` be a [mixin] named `name` which accepts a content block if `rule` + contains a `@content` rule. To execute this mixin with `args`: + + [mixin]: ../types/mixins.md + + * With the current scope set to an empty [scope] with `parent` as its parent: + + * Evaluate `args` with `rule`'s `ArgumentDeclaration`. + + * Execute each statement in `rule`. + + [scope]: ../spec.md#scope + * If `rule` is outside of any block of statements: * If `name` *doesn't* begin with `-` or `_`, set [the current module]'s - mixin `name` to `rule`. + mixin `name` to `mixin`. > This overrides the previous definition, if one exists. - * Set [the current import context]'s mixin `name` to `rule`. + * Set [the current import context]'s mixin `name` to `mixin`. > This happens regardless of whether or not it begins with `-` or `_`. -* Otherwise, set the [current scope]'s mixin `name` to `rule`. + [the current module]: ../spec.md#current-module + [the current import context]: ../spec.md#current-import-context + +* Otherwise, set the [current scope]'s mixin `name` to `mixin`. > This overrides the previous definition, if one exists. -[the current module]: ../spec.md#current-module -[the current import context]: ../spec.md#current-import-context -[current scope]: ../spec.md#scope - ## `@include` ### Syntax @@ -72,15 +88,12 @@ To execute an `@include` rule `rule`: * Let `name` be `rule`'s `NamespacedIdentifier`. -* Let `mixin` be the result of [resolving a mixin][] named `name`. If this - returns null, throw an error. +* Let `mixin` be the result of [resolving a mixin] named `name`. If this returns + null, throw an error. [resolving a mixin]: ../modules.md#resolving-a-member -* Execute `rule`'s `ArgumentInvocation` with `mixin`'s `ArgumentDeclaration` in - `mixin`'s scope. - -* Execute each statement in `mixin`. +* Execute `mixin` with `rule`'s `ArgumentInvocation`. ## `@content` diff --git a/spec/built-in-modules.md b/spec/built-in-modules.md new file mode 100644 index 00000000..0fbe10e2 --- /dev/null +++ b/spec/built-in-modules.md @@ -0,0 +1,38 @@ +# Built-In Modules + +Sass provides a number of built-in [modules] that may be loaded from URLs with +the scheme `sass`. These modules have no extensions, CSS trees, dependencies, or +source files. Their canonical URLs are the same as the URLs used to load them. + +[modules]: modules.md#module + +## Built-In Functions and Mixins + +Each function and mixin defined in a built-in modules is specified with a +signature of the form + +
+[\] ArgumentDeclaration
+
+ +[\]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram + +followed by a procedure. It's available as a member (either function or mixin) +in the module whose name is the value of the ``. When it's executed +with `args`: + +* With an empty scope with no parent as the [current scope]: + + [current scope]: spec.md#scope + + * Evaluate `args` with the signature's `ArgumentDeclaration`. + + * Run the procedure, and return its result if this is a function. + +Built-in mixins don't accept content blocks unless explicitly specified +otherwise. + +By convention, in these procedures `$var` is used as a shorthand for "the value +of the variable `var` in the current scope". + +> In other words, `$var` is the value passed to the argument `$var`. diff --git a/spec/built-in-modules/meta.md b/spec/built-in-modules/meta.md index 06b495cf..57af84c0 100644 --- a/spec/built-in-modules/meta.md +++ b/spec/built-in-modules/meta.md @@ -5,6 +5,7 @@ This built-in module is available from the URL `sass:meta`. ## Table of Contents * [Functions](#functions) + * [`accepts-content()`](#accepts-content) * [`calc-name()`](#calc-name) * [`calc-args()`](#calc-args) * [`call()`](#call) @@ -12,19 +13,36 @@ This built-in module is available from the URL `sass:meta`. * [`feature-exists()`](#feature-exists) * [`function-exists()`](#function-exists) * [`get-function()`](#get-function) + * [`get-mixin()`](#get-mixin) * [`global-variable-exists()`](#global-variable-exists) * [`inspect()`](#inspect) * [`keywords()`](#keywords) * [`mixin-exists()`](#mixin-exists) * [`module-functions()`](#module-functions) + * [`module-mixins()`](#module-mixins) * [`module-variables()`](#module-variables) * [`type-of()`](#type-of) * [`variable-exists()`](#variable-exists) * [Mixins](#mixins) + * [`apply()`](#apply) * [`load-css()`](#load-css) ## Functions +### `accepts-content()` + +This is a new function in the `sass:meta` module. + +``` +accepts-content($mixin) +``` + +* If `$mixin` is not a [mixin], throw an error. + + [mixin]: ../types/mixins.md + +* Return whether `$mixin` accepts a content block as a SassScript boolean. + ### `calc-name()` ``` @@ -144,6 +162,31 @@ This function is also available as a global function named `get-function()`. * Return [`use`'s module][]'s function named `$name`, or throw an error if no such function exists. +### `get-mixin()` + +``` +get-mixin($name, $module: null) +``` + +* If `$name` is not a string, throw an error. + +* If `$module` is null: + + * Return the result of [resolving a mixin] named `$name`. If this returns + null, throw an error. + + [resolving a mixin]: ../modules.md#resolving-a-member + +* Otherwise: + + * If `$module` is not a string, throw an error. + + * Let `use` be the `@use` rule in [the current source file] whose namespace is + equal to `$module`. If no such rule exists, throw an error. + + * Return [`use`'s module]'s mixin named `$name`, or throw an error if no such + mixin exists. + ### `global-variable-exists()` ``` @@ -198,16 +241,14 @@ This function is also available as a global function named `mixin-exists()`. * If `$module` is null: - * Return whether [resolving a mixin][] named `$name` returns null. - - [resolving a mixin]: ../modules.md#resolving-a-member + * Return whether [resolving a mixin] named `$name` returns null. * Otherwise, if `$module` isn't a string, throw an error. -* Otherwise, let `use` be the `@use` rule in [the current source file][] whose +* Otherwise, let `use` be the `@use` rule in [the current source file] whose namespace is equal to `$module`. If no such rule exists, throw an error. -* Return whether [`use`'s module][] contains a mixin named `$name`. +* Return whether [`use`'s module] contains a mixin named `$name`. ### `module-functions()` @@ -225,6 +266,22 @@ This function is also available as a global function named `module-functions()`. * Return a map whose keys are the names of functions in [`use`'s module][] and whose values are the corresponding functions. +### `module-mixins()` + +This is a new function in the `sass:meta` module. + +``` +module-mixins($module) +``` + +* If `$module` is not a string, throw an error. + +* Let `use` be the `@use` rule in [the current source file] whose namespace is + equal to `$module`. If no such rule exists, throw an error. + +* Return a map whose keys are the quoted string names of mixins in + [`use`'s module] and whose values are the corresponding mixins. + ### `module-variables()` ``` @@ -261,6 +318,7 @@ This function is also available as a global function named `type-of()`. | Function | `"function"` | | List | `"list"` | | Map | `"map"` | + | Mixin | `"mixin"` | | Null | `"null"` | | Number | `"number"` | | String | `"string"` | @@ -288,6 +346,24 @@ This function is also available as a global function named `variable-exists()`. ## Mixins +### `apply()` + +``` +apply($mixin, $args...) +``` + +* If `$mixin` is not a [mixin], throw an error. + +* If the current `@include` rule has a `ContentBlock` and `$mixin` doesn't + accept a block, throw an error. + +* Execute `$mixin` with the `ArgumentInvocation` `(...$args)`. Treat the + `@include` rule that invoked `apply` as the `@include` rule that invoked + `$mixin`. + + > This ensures that any `@content` rules in `$mixin` will use `apply()`'s + > `ContentBlock`. + ### `load-css()` ``` diff --git a/spec/types/functions.md b/spec/types/functions.md new file mode 100644 index 00000000..0d6c842e --- /dev/null +++ b/spec/types/functions.md @@ -0,0 +1,62 @@ +# Functions + +## Table of Contents + +* [Types](#types) + * [Operations](#operations) + * [Equality](#equality) + * [Serialization](#serialization) + +## Types + +The value type known as a "function" is a procedure that takes an +`ArgumentInvocation` `args` and returns a SassScript value. Each function has a +string name. + +> The specific details of executing this procedure differ depending on where and +> how the function is defined. + +### Operations + +A function follows the default behavior of all SassScript operations, except +that equality is defined as below. + +#### Equality + +Functions use reference equality: two function values are equal only if they +refer to the exact same instance of the same procedure. + +> If the same file were to be imported multiple times, Sass would create a new +> function value for each `@function` rule each time the file is imported. +> Because a new function value has been created, although the name, body, and +> source span of a given function from the file would be the same between +> imports, the values would not be equal because they refer to different +> instances. Functions pre-defined by the Sass language are instatiated at most +> once during the entire evaluation of a program. +> +> As an example, if we declare two functions: +> +> ```scss +> @function foo() { +> @return red; +> } +> +> $a: meta.get-function(foo); +> +> @mixin foo { +> @return red; +> } +> +> $b: meta.get-mixin(foo); +> ``` +> +> Although every aspect of the two functions is the same, `$a != $b`, because +> they refer to separate function values. + +### Serialization + +To serialize a function value: + +* If the value is not being inspected, throw an error. + +* Otherwise, emit `'get-function("'`, then the function's name, then `'")'`. diff --git a/spec/types/mixins.md b/spec/types/mixins.md new file mode 100644 index 00000000..b390c444 --- /dev/null +++ b/spec/types/mixins.md @@ -0,0 +1,63 @@ +# Mixins + +## Table of Contents + +* [Types](#types) + * [Operations](#operations) + * [Equality](#equality) + * [Serialization](#serialization) + +## Types + +The value type known as a "mixin" is a procedure that takes an +`ArgumentInvocation` `args` and returns nothing. Each mixin has a string name +and a boolean that indicates whether or not it accepts a content block. + +> The specific details of executing this procedure differ depending on where and +> how the mixin is defined. A mixin will typically add nodes to the CSS +> stylesheet. + +### Operations + +A mixin follows the default behavior of all SassScript operations, except that +equality is defined as below. + +#### Equality + +Mixins use reference equality: two mixin values are equal only if they refer to +the exact same instance of the same procedure. + +> If the same file were to be imported multiple times, Sass would create a new +> mixin value for each `@mixin` rule each time the file is imported. Because a +> new mixin value has been created, although the name, body, and source span of +> a given mixin from the file would be the same between imports, the values +> would not be equal because they refer to different instances. Mixins +> pre-defined by the Sass language are instatiated at most once during the +> entire evaluation of a program. +> +> As an example, if we declare two mixins: +> +> ```scss +> @mixin foo { +> color: red; +> } +> +> $a: meta.get-mixin(foo); +> +> @mixin foo { +> color: red; +> } +> +> $b: meta.get-mixin(foo); +> ``` +> +> Although every aspect of the two mixins is the same, `$a != $b`, because they +> refer to separate mixin values. + +### Serialization + +To serialize a mixin value: + +* If the value is not being inspected, throw an error. + +* Otherwise, emit `'get-mixin("'`, then the mixin's name, then `'")'`. From 6a0c61cf2e4ee8844f0d7a79c1d62b53b2e46992 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Sat, 7 Oct 2023 15:22:05 -0400 Subject: [PATCH 07/51] Remove unused ValueObject type --- proposal/color-4-new-spaces-js.d.ts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index a0c50b46..c68e2180 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -50,7 +50,7 @@ proposal]. ## API ```ts -import {List, ValueObject} from 'immutable'; +import {List} from 'immutable'; import {Value} from '../spec/js-api/value'; ``` From ea1ec36c12da579ff99d9871951c6f44785be255 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 10 Oct 2023 14:36:50 -0700 Subject: [PATCH 08/51] Code review --- spec/at-rules/function.md | 2 +- spec/built-in-modules/meta.md | 16 ++++++++++++++++ spec/types/functions.md | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/spec/at-rules/function.md b/spec/at-rules/function.md index aa3fd5c6..c62189ed 100644 --- a/spec/at-rules/function.md +++ b/spec/at-rules/function.md @@ -55,7 +55,7 @@ To execute a `@function` rule `rule`: > This overrides the previous definition, if one exists. - * Set [the current import context][]'s function `name` to `function`. + * Set [the current import context]'s function `name` to `function`. > This happens regardless of whether or not it begins with `-` or `_`. diff --git a/spec/built-in-modules/meta.md b/spec/built-in-modules/meta.md index 57af84c0..4b488b6d 100644 --- a/spec/built-in-modules/meta.md +++ b/spec/built-in-modules/meta.md @@ -108,6 +108,10 @@ This function is also available as a global function named `function-exists()`. * If `$name` is not a string, throw an error. +* If `$name` is not an [``], return false. + + [``]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram + * If `$module` is null: * Return whether [resolving a function][] named `$name` returns null. @@ -135,6 +139,8 @@ This function is also available as a global function named `get-function()`. * If `$name` is not a string, throw an error. +* If `$name` is not an [``], throw an error. + * If `$module` is null: * If `$css` is falsey: @@ -170,6 +176,8 @@ get-mixin($name, $module: null) * If `$name` is not a string, throw an error. +* If `$name` is not an [``], throw an error. + * If `$module` is null: * Return the result of [resolving a mixin] named `$name`. If this returns @@ -197,6 +205,10 @@ This function is also available as a global function named `global-variable-exis * If `$name` is not a string, throw an error. +* If `$name` is not a [`PlainVariable`], return false. + + [`PlainVariable`]: ../variables.md#syntax + * If `$module` is null: * Return whether [resolving a variable][] named `$name`, ignoring local @@ -239,6 +251,8 @@ This function is also available as a global function named `mixin-exists()`. * If `$name` is not a string, throw an error. +* If `$name` is not an [``], return false. + * If `$module` is null: * Return whether [resolving a mixin] named `$name` returns null. @@ -333,6 +347,8 @@ This function is also available as a global function named `variable-exists()`. * If `$name` is not a string, throw an error. +* If `$name` is not a [`PlainVariable`], return false. + * If `$module` is null: * Return whether [resolving a variable][] named `$name` returns null. diff --git a/spec/types/functions.md b/spec/types/functions.md index 0d6c842e..0f0ba467 100644 --- a/spec/types/functions.md +++ b/spec/types/functions.md @@ -43,11 +43,11 @@ refer to the exact same instance of the same procedure. > > $a: meta.get-function(foo); > -> @mixin foo { +> @function foo { > @return red; > } > -> $b: meta.get-mixin(foo); +> $b: meta.get-function(foo); > ``` > > Although every aspect of the two functions is the same, `$a != $b`, because From b7003fd5367611b19bc2fd562514a254fc0908d1 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Tue, 10 Oct 2023 18:11:13 -0400 Subject: [PATCH 09/51] Tweak language around getters and arrays. --- proposal/color-4-new-spaces-js.d.ts.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index c68e2180..a89284f8 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -179,14 +179,14 @@ toGamut(space?: KnownColorSpace): SassColor; #### `channelsOrNull` -Returns an array of channel values (excluding alpha) for [`internal`], with +Returns a list of channel values (excluding alpha) for [`internal`], with [missing channels][missing components] converted to `null`. -* Let `space` be the result of [`this.space`]. +* Let `space` be the value of [`this.space`]. * Let `components` be the list of channels in `space`. -* Let `channels` be an empty array. +* Let `channels` be an empty list. * For each `component` in `components`: @@ -207,12 +207,12 @@ get channelsOrNull(): List; #### `channels` -This algorithm returns an array of channel values (excluding alpha) for +This algorithm returns a list of channel values (excluding alpha) for [`internal`], with [missing channels][missing components] converted to `0`. -* Let `channelsOrNull` be the result of [`this.channelsOrNull`]. +* Let `channelsOrNull` be the value of [`this.channelsOrNull`]. -* Let `channels` be an empty array. +* Let `channels` be an empty list. * For each `channel` in `channelsOrNull`: @@ -230,7 +230,7 @@ get channels(): List; #### `channel` -* Let `initialSpace` be the value of [`this.space()`]. +* Let `initialSpace` be the value of [`this.space`]. * Let `space` be `options.space` if it is defined, and the value of `initialSpace` otherwise. @@ -340,7 +340,7 @@ isChannelPowerless( #### `interpolate` -* Let `space` be the value of [`this.space()`]. +* Let `space` be the value of [`this.space`]. * If `options.method` is set, let `interpolationMethod` be a space separated list containing the value of `space`, a space, and the value of @@ -383,7 +383,7 @@ as the result of changing some of [`internal`]'s components. > If `space` is not a [legacy color space], a channel value of `null` will > result in a [missing component][missing components] value for that channel. -* Let `initialSpace` be the value of [`this.space()`]. +* Let `initialSpace` be the value of [`this.space`]. * Let `spaceSetExplicitly` be `true` if `options.space` is defined, and `false` otherwise. @@ -561,7 +561,6 @@ as the result of changing some of [`internal`]'s components. * Return the result of [`changedColor.toSpace(initialSpace)`]. -[`this.space()`]: #space [`this.toSpace(space)`]: #tospace [`changedColor.toSpace(initialSpace)`]: #tospace [`Changing a Component Value`]: #changing-a-component-value @@ -850,7 +849,7 @@ Create a new SassColor in the `rgb` color space. * If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be the result of [parsing a channel value] with value `options.alpha`. -* Return the result of [`rgb(red green blue / alpha)`]. +* Set [`internal`] to the result of [`rgb(red green blue / alpha)`]. [`rgb(red green blue / alpha)`]: ./color-4-new-spaces.md#rgb-and-rgba From 7d50def4d120731ac3e6e7e8b372828565d36703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Tue, 10 Oct 2023 17:17:28 -0700 Subject: [PATCH 10/51] Clarifying performance expectations about sass and sass-embedded (#3716) --- js-api-doc/compile.d.ts | 23 +++++++++++++++++------ js-api-doc/legacy/render.d.ts | 11 ++++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/js-api-doc/compile.d.ts b/js-api-doc/compile.d.ts index 7dde5b74..e8a8ce65 100644 --- a/js-api-doc/compile.d.ts +++ b/js-api-doc/compile.d.ts @@ -44,6 +44,11 @@ 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. + * * @example * * ```js @@ -66,9 +71,9 @@ export function compile(path: string, options?: Options<'sync'>): CompileResult; * This only allows synchronous or asynchronous {@link Importer}s and * {@link CustomFunction}s. * - * **Heads up!** When using Dart Sass, **{@link compile} is almost twice as fast - * as {@link compileAsync}**, due to the overhead of making the entire - * evaluation process asynchronous. + * **Heads up!** When using the `sass` npm package, **{@link compile} is almost + * twice as fast as {@link compileAsync}**, due to the overhead of making the + * entire evaluation process asynchronous. * * @example * @@ -94,6 +99,12 @@ 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 + * {@link compileString}**, due to the overhead of emulating synchronous + * messaging with worker threads and concurrent compilations being blocked on + * main thread. + * * @example * * ```js @@ -125,9 +136,9 @@ export function compileString( * This only allows synchronous or asynchronous {@link Importer}s and {@link * CustomFunction}s. * - * **Heads up!** When using Dart Sass, **{@link compile} is almost twice as fast - * as {@link compileAsync}**, due to the overhead of making the entire - * evaluation process asynchronous. + * **Heads up!** When using the `sass` npm package, **{@link compileString} is + * almost twice as fast as {@link compileStringAsync}**, due to the overhead + * of making the entire evaluation process asynchronous. * * @example * diff --git a/js-api-doc/legacy/render.d.ts b/js-api-doc/legacy/render.d.ts index b029c882..9f78816f 100644 --- a/js-api-doc/legacy/render.d.ts +++ b/js-api-doc/legacy/render.d.ts @@ -96,6 +96,11 @@ export interface LegacyResult { * This function synchronously compiles a Sass file to CSS. If it succeeds, it * returns the result, and if it fails it throws an error. * + * **Heads up!** When using the `sass-embedded` npm package, **{@link render} + * is almost always faster than {@link renderSync}**, due to the overhead of + * emulating synchronous messaging with worker threads and concurrent + * compilations being blocked on main thread. + * * @example * * ```js @@ -116,9 +121,9 @@ export function renderSync(options: LegacyOptions<'sync'>): LegacyResult; * `callback` with a {@link LegacyResult} if compilation succeeds or {@link * LegacyException} if it fails. * - * **Heads up!** When using Dart Sass, **{@link renderSync} is almost twice as - * fast as {@link render}** by default, due to the overhead of making the entire - * evaluation process asynchronous. + * **Heads up!** When using the `sass` npm package, **{@link renderSync} is + * almost twice as fast as {@link render}** by default, due to the overhead of + * making the entire evaluation process asynchronous. * * ```js * const sass = require('sass'); // or require('node-sass'); From 6998b4429ebc9530a3b414382aadeb948d00e118 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 11 Oct 2023 08:48:16 -0400 Subject: [PATCH 11/51] Rename types using title-case for acronyms longer than two letters in camel-case identifiers. --- proposal/color-4-new-spaces-js.changes.md | 3 + proposal/color-4-new-spaces-js.d.ts.md | 112 +++++++++++----------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/proposal/color-4-new-spaces-js.changes.md b/proposal/color-4-new-spaces-js.changes.md index b7271cfe..3a0f578f 100644 --- a/proposal/color-4-new-spaces-js.changes.md +++ b/proposal/color-4-new-spaces-js.changes.md @@ -1,5 +1,8 @@ ## Draft 1.2 +* Rename types using title-case for acronyms longer than two letters in + camel-case identifiers (e.g. `ColorSpaceHsl` instead of `ColorSpaceHSL`). + * Remove generic `change` overload, and make `space` optional on others. * Return `immutable` types for `channels` and `channelsOrNull`, and remove diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index a89284f8..d9f99113 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -60,23 +60,23 @@ import {Value} from '../spec/js-api/value'; ### Color Space Definitions ```ts -export type ColorSpaceHSL = 'hsl'; +export type ColorSpaceHsl = 'hsl'; -export type ChannelNameHSL = 'hue' | 'saturation' | 'lightness'; +export type ChannelNameHsl = 'hue' | 'saturation' | 'lightness'; -export type ColorSpaceHWB = 'hwb'; +export type ColorSpaceHwb = 'hwb'; -export type ChannelNameHWB = 'hue' | 'whiteness' | 'blackness'; +export type ChannelNameHwb = 'hue' | 'whiteness' | 'blackness'; export type ColorSpaceLab = 'lab' | 'oklab'; export type ChannelNameLab = 'lightness' | 'a' | 'b'; -export type ColorSpaceLCH = 'lch' | 'oklch'; +export type ColorSpaceLch = 'lch' | 'oklch'; -export type ChannelNameLCH = 'lightness' | 'chroma' | 'hue'; +export type ChannelNameLch = 'lightness' | 'chroma' | 'hue'; -export type ColorSpaceRGB = +export type ColorSpaceRgb = | 'a98-rgb' | 'display-p3' | 'prophoto-rgb' @@ -84,29 +84,29 @@ export type ColorSpaceRGB = | 'srgb' | 'srgb-linear'; -export type ChannelNameRGB = 'red' | 'green' | 'blue'; +export type ChannelNameRgb = 'red' | 'green' | 'blue'; -export type ColorSpaceXYZ = 'xyz' | 'xyz-d50' | 'xyz-d65'; +export type ColorSpaceXyz = 'xyz' | 'xyz-d50' | 'xyz-d65'; -export type ChannelNameXYZ = 'x' | 'y' | 'z'; +export type ChannelNameXyz = 'x' | 'y' | 'z'; export type ChannelName = - | ChannelNameHSL - | ChannelNameHWB + | ChannelNameHsl + | ChannelNameHwb | ChannelNameLab - | ChannelNameLCH - | ChannelNameRGB - | ChannelNameXYZ; + | ChannelNameLch + | ChannelNameRgb + | ChannelNameXyz; export type KnownColorSpace = - | ColorSpaceHSL - | ColorSpaceHWB + | ColorSpaceHsl + | ColorSpaceHwb | ColorSpaceLab - | ColorSpaceLCH - | ColorSpaceRGB - | ColorSpaceXYZ; + | ColorSpaceLch + | ColorSpaceRgb + | ColorSpaceXyz; -export type PolarColorSpace = ColorSpaceHSL | ColorSpaceHWB | ColorSpaceLCH; +export type PolarColorSpace = ColorSpaceHsl | ColorSpaceHwb | ColorSpaceLch; export type RectangularColorSpace = Exclude; @@ -248,28 +248,28 @@ get channels(): List; ```ts channel(channel: ChannelName): number; channel( - channel: ChannelNameHSL | 'alpha', - options: {space: ColorSpaceHSL} + channel: ChannelNameHsl | 'alpha', + options: {space: ColorSpaceHsl} ): number; channel( - channel: ChannelNameHWB | 'alpha', - options: {space: ColorSpaceHWB} + channel: ChannelNameHwb | 'alpha', + options: {space: ColorSpaceHwb} ): number; channel( channel: ChannelNameLab | 'alpha', options: {space: ColorSpaceLab} ): number; channel( - channel: ChannelNameLCH | 'alpha', - options: {space: ColorSpaceLCH} + channel: ChannelNameLch | 'alpha', + options: {space: ColorSpaceLch} ): number; channel( - channel: ChannelNameRGB | 'alpha', - options: {space: ColorSpaceRGB} + channel: ChannelNameRgb | 'alpha', + options: {space: ColorSpaceRgb} ): number; channel( - channel: ChannelNameXYZ | 'alpha', - options: {space: ColorSpaceXYZ} + channel: ChannelNameXyz | 'alpha', + options: {space: ColorSpaceXyz} ): number; ``` @@ -313,28 +313,28 @@ JavaScript boolean. ```ts isChannelPowerless(channel: ChannelName): boolean; isChannelPowerless( - channel: ChannelNameHSL, - options?: {space: ColorSpaceHSL} + channel: ChannelNameHsl, + options?: {space: ColorSpaceHsl} ): boolean; isChannelPowerless( - channel: ChannelNameHWB, - options?: {space: ColorSpaceHWB} + channel: ChannelNameHwb, + options?: {space: ColorSpaceHwb} ): boolean; isChannelPowerless( channel: ChannelNameLab, options?: {space: ColorSpaceLab} ): boolean; isChannelPowerless( - channel: ChannelNameLCH, - options?: {space: ColorSpaceLCH} + channel: ChannelNameLch, + options?: {space: ColorSpaceLch} ): boolean; isChannelPowerless( - channel: ChannelNameRGB, - options?: {space: ColorSpaceRGB} + channel: ChannelNameRgb, + options?: {space: ColorSpaceRgb} ): boolean; isChannelPowerless( - channel: ChannelNameXYZ, - options?: {space: ColorSpaceXYZ} + channel: ChannelNameXyz, + options?: {space: ColorSpaceXyz} ): boolean; ``` @@ -568,19 +568,19 @@ as the result of changing some of [`internal`]'s components. ```ts change( options: { - [key in ChannelNameHSL]?: number | null; + [key in ChannelNameHsl]?: number | null; } & { alpha?: number; - space?: ColorSpaceHSL; + space?: ColorSpaceHsl; } ): SassColor; change( options: { - [key in ChannelNameHWB]?: number | null; + [key in ChannelNameHwb]?: number | null; } & { alpha?: number; - space?: ColorSpaceHWB; + space?: ColorSpaceHwb; } ): SassColor; @@ -595,28 +595,28 @@ change( change( options: { - [key in ChannelNameLCH]?: number | null; + [key in ChannelNameLch]?: number | null; } & { alpha?: number | null; - space?: ColorSpaceLCH; + space?: ColorSpaceLch; } ): SassColor; change( options: { - [key in ChannelNameRGB]?: number | null; + [key in ChannelNameRgb]?: number | null; } & { alpha?: number | null; - space?: ColorSpaceRGB; + space?: ColorSpaceRgb; } ): SassColor; change( options: { - [key in ChannelNameXYZ]?: number | null; + [key in ChannelNameXyz]?: number | null; } & { alpha?: number | null; - space?: ColorSpaceXYZ; + space?: ColorSpaceXyz; } ): SassColor; ``` @@ -693,7 +693,7 @@ constructor(options: { chroma: number | null; hue: number | null; alpha?: number | null; - space: ColorSpaceLCH; + space: ColorSpaceLch; }); ``` @@ -727,7 +727,7 @@ constructor(options: { green: number | null; blue: number | null; alpha?: number | null; - space: Exclude; + space: Exclude; }); ``` @@ -757,7 +757,7 @@ constructor(options: { y: number | null; z: number | null; alpha?: number | null; - space: ColorSpaceXYZ; + space: ColorSpaceXyz; }); ``` @@ -795,7 +795,7 @@ constructor(options: { saturation: number | null; lightness: number | null; alpha?: number | null; - space?: ColorSpaceHSL; + space?: ColorSpaceHsl; }); ``` @@ -827,7 +827,7 @@ constructor(options: { whiteness: number | null; blackness: number | null; alpha?: number | null; - space?: ColorSpaceHWB; + space?: ColorSpaceHwb; }); ``` From 219a1b5f4564780e26e3d1782a037ae82b32566b Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 11 Oct 2023 10:26:37 -0400 Subject: [PATCH 12/51] Remove `isAlphaMissing` and add "alpha" to channel name types. --- proposal/color-4-new-spaces-js.changes.md | 4 ++ proposal/color-4-new-spaces-js.d.ts.md | 60 +++++------------------ 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/proposal/color-4-new-spaces-js.changes.md b/proposal/color-4-new-spaces-js.changes.md index 3a0f578f..193f1cd5 100644 --- a/proposal/color-4-new-spaces-js.changes.md +++ b/proposal/color-4-new-spaces-js.changes.md @@ -1,5 +1,9 @@ ## Draft 1.2 +* Add "alpha" to all channel name types. + +* Remove `isAlphaMissing` in favor of `isChannelMissing("alpha")`. + * Rename types using title-case for acronyms longer than two letters in camel-case identifiers (e.g. `ColorSpaceHsl` instead of `ColorSpaceHSL`). diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index d9f99113..a0f4ec59 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -24,7 +24,6 @@ proposal]. * [`channel`](#channel) * [`alpha`](#alpha) * [`isChannelMissing`](#ischannelmissing) - * [`isAlphaMissing`](#isalphamissing) * [`isChannelPowerless`](#ischannelpowerless) * [`interpolate`](#interpolate) * [Updated Color Functions](#updated-color-functions) @@ -62,19 +61,19 @@ import {Value} from '../spec/js-api/value'; ```ts export type ColorSpaceHsl = 'hsl'; -export type ChannelNameHsl = 'hue' | 'saturation' | 'lightness'; +export type ChannelNameHsl = 'hue' | 'saturation' | 'lightness' | 'alpha'; export type ColorSpaceHwb = 'hwb'; -export type ChannelNameHwb = 'hue' | 'whiteness' | 'blackness'; +export type ChannelNameHwb = 'hue' | 'whiteness' | 'blackness' | 'alpha'; export type ColorSpaceLab = 'lab' | 'oklab'; -export type ChannelNameLab = 'lightness' | 'a' | 'b'; +export type ChannelNameLab = 'lightness' | 'a' | 'b' | 'alpha'; export type ColorSpaceLch = 'lch' | 'oklch'; -export type ChannelNameLch = 'lightness' | 'chroma' | 'hue'; +export type ChannelNameLch = 'lightness' | 'chroma' | 'hue' | 'alpha'; export type ColorSpaceRgb = | 'a98-rgb' @@ -84,11 +83,11 @@ export type ColorSpaceRgb = | 'srgb' | 'srgb-linear'; -export type ChannelNameRgb = 'red' | 'green' | 'blue'; +export type ChannelNameRgb = 'red' | 'green' | 'blue' | 'alpha'; export type ColorSpaceXyz = 'xyz' | 'xyz-d50' | 'xyz-d65'; -export type ChannelNameXyz = 'x' | 'y' | 'z'; +export type ChannelNameXyz = 'x' | 'y' | 'z' | 'alpha'; export type ChannelName = | ChannelNameHsl @@ -247,30 +246,12 @@ get channels(): List; ```ts channel(channel: ChannelName): number; -channel( - channel: ChannelNameHsl | 'alpha', - options: {space: ColorSpaceHsl} -): number; -channel( - channel: ChannelNameHwb | 'alpha', - options: {space: ColorSpaceHwb} -): number; -channel( - channel: ChannelNameLab | 'alpha', - options: {space: ColorSpaceLab} -): number; -channel( - channel: ChannelNameLch | 'alpha', - options: {space: ColorSpaceLch} -): number; -channel( - channel: ChannelNameRgb | 'alpha', - options: {space: ColorSpaceRgb} -): number; -channel( - channel: ChannelNameXyz | 'alpha', - options: {space: ColorSpaceXyz} -): number; +channel(channel: ChannelNameHsl, options: {space: ColorSpaceHsl}): number; +channel(channel: ChannelNameHwb, options: {space: ColorSpaceHwb}): number; +channel(channel: ChannelNameLab, options: {space: ColorSpaceLab}): number; +channel(channel: ChannelNameLch, options: {space: ColorSpaceLch}): number; +channel(channel: ChannelNameRgb, options: {space: ColorSpaceRgb}): number; +channel(channel: ChannelNameXyz, options: {space: ColorSpaceXyz}): number; ``` #### `alpha` @@ -289,20 +270,11 @@ Returns the result of [`color.is-missing(internal, channel)`][color.is-missing()] as a JavaScript boolean. ```ts -isChannelMissing(channel: ChannelName | 'alpha'): boolean; +isChannelMissing(channel: ChannelName): boolean; ``` [color.is-missing()]: ./color-4-new-spaces.md#coloris-missing-1 -#### `isAlphaMissing` - -Returns the result of [`color.is-missing(internal, -'alpha')`][color.is-missing()] as a JavaScript boolean. - -```ts -get isAlphaMissing(): boolean; -``` - #### `isChannelPowerless` Returns the result of [`color.is-powerless(internal, channel, space)`] as a @@ -570,7 +542,6 @@ change( options: { [key in ChannelNameHsl]?: number | null; } & { - alpha?: number; space?: ColorSpaceHsl; } ): SassColor; @@ -579,7 +550,6 @@ change( options: { [key in ChannelNameHwb]?: number | null; } & { - alpha?: number; space?: ColorSpaceHwb; } ): SassColor; @@ -588,7 +558,6 @@ change( options: { [key in ChannelNameLab]?: number | null; } & { - alpha?: number | null; space?: ColorSpaceLab; } ): SassColor; @@ -597,7 +566,6 @@ change( options: { [key in ChannelNameLch]?: number | null; } & { - alpha?: number | null; space?: ColorSpaceLch; } ): SassColor; @@ -606,7 +574,6 @@ change( options: { [key in ChannelNameRgb]?: number | null; } & { - alpha?: number | null; space?: ColorSpaceRgb; } ): SassColor; @@ -615,7 +582,6 @@ change( options: { [key in ChannelNameXyz]?: number | null; } & { - alpha?: number | null; space?: ColorSpaceXyz; } ): SassColor; From 3eb7f2c5e1febf5ddd30ccef204ccc7b1aca1191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:52:15 -0700 Subject: [PATCH 13/51] Bump tj-actions/changed-files from 39.2.0 to 39.2.2 (#3720) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.0 to 39.2.2. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/8238a4103220c636f2dad328ead8a7c8dbe316a3...408093d9ff9c134c33b974e0722ce06b9d6e8263) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 262618b0..082f2f1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,7 +138,7 @@ jobs: - name: Find changed files in js-api-doc id: changed-files - uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3 + uses: tj-actions/changed-files@408093d9ff9c134c33b974e0722ce06b9d6e8263 with: {files: js-api-doc} - name: Deploy From 44b060f66de4c01ad2304d2e28691f7160fa285d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:52:35 -0700 Subject: [PATCH 14/51] Bump bufbuild/buf-setup-action from 1.26.1 to 1.27.0 (#3713) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.26.1 to 1.27.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.26.1...v1.27.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 082f2f1b..f7bc8c1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.26.1 + - uses: bufbuild/buf-setup-action@v1.27.0 with: {github_token: "${{ github.token }}"} - name: Generate protobuf code run: buf generate From 75ced5c7196e374ba4e9ec76ad36028c6f6a1040 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Tue, 17 Oct 2023 18:45:42 -0400 Subject: [PATCH 15/51] [Color 4] Draft 1.3 changes for Color spaces JS API (#3721) --- proposal/color-4-new-spaces-js.changes.md | 8 +++++ proposal/color-4-new-spaces-js.d.ts.md | 44 ++++++++++++----------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/proposal/color-4-new-spaces-js.changes.md b/proposal/color-4-new-spaces-js.changes.md index 193f1cd5..17d9ccc8 100644 --- a/proposal/color-4-new-spaces-js.changes.md +++ b/proposal/color-4-new-spaces-js.changes.md @@ -1,3 +1,11 @@ +## Draft 1.3 + +* Rename new Embedded Protocol message from `SassColor` to `Color`. + +* Make `color2` a positional parameter of `interpolate`, not an option. + +* Add `rec2020` color space. + ## Draft 1.2 * Add "alpha" to all channel name types. diff --git a/proposal/color-4-new-spaces-js.d.ts.md b/proposal/color-4-new-spaces-js.d.ts.md index a0f4ec59..999f82e2 100644 --- a/proposal/color-4-new-spaces-js.d.ts.md +++ b/proposal/color-4-new-spaces-js.d.ts.md @@ -1,4 +1,4 @@ -# CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.1 +# CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.3 *([Issue](https://github.com/sass/sass/issues/2831), [Changelog](color-4-new-spaces-js.changes.md))* @@ -43,7 +43,7 @@ proposal]. * [Changing a Component Value](#changing-a-component-value) * [Determining Construction Space](#determining-construction-space) * [Embedded Protocol](#embedded-protocol) - * [SassColor](#sasscolor) + * [Color](#color) * [Removed SassScript values](#removed-sassscript-values) ## API @@ -79,6 +79,7 @@ export type ColorSpaceRgb = | 'a98-rgb' | 'display-p3' | 'prophoto-rgb' + | 'rec2020' | 'rgb' | 'srgb' | 'srgb-linear'; @@ -324,16 +325,18 @@ isChannelPowerless( * Otherwise, let `interpolationMethod` be a space separated list containing the value of `space`, a space, and the string "shorter". -* Return the result of [`color.mix(internal, options.color2, options.weight, interpolationMethod)`][`color.mix()`]. +* Return the result of [`color.mix(internal, color2, options.weight, interpolationMethod)`][`color.mix()`]. ```ts -interpolate(options: {color2: SassColor; weight?: number}): SassColor; +interpolate(color2: SassColor, options: {weight?: number}): SassColor; -interpolate(options: { - color2: SassColor; - weight?: number; - method?: HueInterpolationMethod; -}): SassColor; +interpolate( + color2: SassColor, + options: { + weight?: number; + method?: HueInterpolationMethod; + } +): SassColor; ``` [`color.mix()`]: ./color-4-new-spaces.md#colormix-1 @@ -505,8 +508,8 @@ as the result of changing some of [`internal`]'s components. }) ``` -* If `space` equals `a98-rgb`, `display-p3`, `prophoto-rgb`, `srgb`, or - `srgb-linear`, let `changedColor` be the result of: +* If `space` equals `a98-rgb`, `display-p3`, `prophoto-rgb`, `rec2020`, `srgb`, + or `srgb-linear`, let `changedColor` be the result of: ```js new SassColor({ @@ -598,7 +601,7 @@ change( #### Lab Channel Constructor -Create a new SassColor in a color space with Lab channels -- `lab` and `oklab`. +Create a new SassColor in a color space with Lab channels—`lab` and `oklab`. * Let `lightness` be the result of [parsing a channel value] with value `options.lightness`. @@ -632,7 +635,7 @@ constructor(options: { #### LCH Channel Constructor -Create a new SassColor in a color space with LCH channels -- `lch` and `oklch`. +Create a new SassColor in a color space with LCH channels—`lch` and `oklch`. * Let `lightness` be the result of [parsing a channel value] with value `options.lightness`. @@ -665,8 +668,8 @@ constructor(options: { #### Predefined RGB Channel Constructor -Create a new SassColor in a color space with RGB channels -- `srgb`, -`srgb-linear`, `display-p3`, `a98-rgb`, and `prophoto-rgb`. `rgb` is supported +Create a new SassColor in a color space with RGB channels—`srgb`, `srgb-linear`, +`display-p3`, `a98-rgb`, `prophoto-rgb`, and `rec2020`. `rgb` is supported through the modified [RGB Constructor]. * Let `red` be the result of [parsing a channel value] with value `options.red`. @@ -699,8 +702,8 @@ constructor(options: { #### XYZ Channel Constructor -Create a new SassColor in a color space with XYZ channels -- `xyz`, `xyz-d50`, -and `xyz-d65`. +Create a new SassColor in a color space with XYZ channels—`xyz`, `xyz-d50`, and +`xyz-d65`. * Let `x` be the result of [parsing a channel value] with value `options.x`. @@ -894,10 +897,10 @@ space for construction. This introduces a breaking change in the Embedded Protocol, as it removes the legacy SassScript values. -### SassColor +### Color ```proto -message SassColor { +message Color { // The name of a known color space. string space = 1; @@ -910,8 +913,7 @@ message SassColor { // The value of the third channel associated with `space`. double channel3 = 4; - // The color's alpha channel. Mandatory. Must be between 0 and 1, - // inclusive. + // The color's alpha channel. Mandatory. Must be between 0 and 1, inclusive. double alpha = 5; } ``` From 09f8a3a66d196e589e6a40f5557bfadefa2d4f6d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 18 Oct 2023 17:56:59 -0400 Subject: [PATCH 16/51] Proposal: Compiler interface for shared resources (#3719) --- proposal/shared-resources.d.ts.md | 228 ++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 proposal/shared-resources.d.ts.md diff --git a/proposal/shared-resources.d.ts.md b/proposal/shared-resources.d.ts.md new file mode 100644 index 00000000..b12cccc6 --- /dev/null +++ b/proposal/shared-resources.d.ts.md @@ -0,0 +1,228 @@ +# Shared Resources in JavaScript API + +*([Issue](https://github.com/sass/sass/issues/3296))* + +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](#summary) + * [Design Decisions](#design-decisions) + * [Splitting Sync and Async Compilers](#splitting-sync-and-async-compilers) + * [Limited API interface](#limited-api-interface) + * [Flexibility for interfaces on process management](#flexibility-for-interfaces-on-process-management) + * [No shared state](#no-shared-state) + * [Example](#example) + * [Sync Compiler](#sync-compiler) + * [Async Compiler](#async-compiler) +* [API](#api) + * [Types](#types) + * [initCompiler()](#initcompiler) + * [initAsyncCompiler()](#initasynccompiler) + * [Compiler](#compiler) + * [`compile()` and `compileString()`](#compile-and-compilestring) + * [dispose()](#dispose) + * [Async Compiler](#async-compiler-1) + * [`compileAsync()` and `compileStringAsync()`](#compileasync-and-compilestringasync) + * [dispose()](#dispose-1) + +## 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 `