accepted/first-class-mixins.d.ts.md
(Issue)
This proposal promotes mixins to first-class values and adds members to the
sass:meta module for manipulating them.
This section is non-normative.
Sass today has two callables: the function and the mixin. Functions in Sass are a first class SassScript value type; they can be assigned to variables and invoked dynamically.
Mixins, however, are not first class values. This is a stumbling block many users (sass/sass#626, sass/sass#673, sass/sass#3328, sass/sass#3439, among others) of Sass run into.
Promoting mixins to be first class values would resolve a number of commonly requested features while also rounding out the language and adding feature parity between Sass's two callables.
This section is non-normative.
This proposal promotes mixins to first-class values, giving Sass composability for both of its kinds of callables.
Additionally, it provides ways to interact with mixin values similarly to how one would with function values:
meta.get-function() => meta.get-mixin()
meta.module-functions() => meta.module-mixins()
meta.call() => meta.apply()
Mixins differ from functions in that the result of their execution is a Sass AST node, and not a SassScript value. Sass today does not expose ways to create or manipulate AST nodes through the JavaScript API, nor does it intend to do so in the future.
For this reason, it is not meaningful -- or even possible -- to construct or execute a mixin through the JavaScript API. A mixin object shall be opaque, and the only operation available shall be to return the object as-is.
This proposal promotes the mixin value to a Sass value type.
The only operation permitted for mixin objects is checking for equality. All other operations throw an error.
When the Sass interpreter encounters an @mixin rule in Sass source code, it
constructs a mixin object in memory. Additionally, some mixin objects are
pre-defined by the Sass language and accessible though the builtin modules.
Mixin objects, like function objects, use pointer equality.
If the same file were to be imported multiple times, the Sass interpreter would
create a new mixin object for each @mixin rule each time the file is imported.
Because a new mixin object has been created, although the name, body, and source
span of a given mixin from the file would be the same between imports, the
objects would not be equal because they refer to different objects in memory.
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 mixin1 { color: red; } $a: meta.get-mixin(mixin1); @mixin mixin1 { color: red; } $b: meta.get-mixin(mixin1);Although every aspect of the two mixins is the same,
$a != $b, because they refer to separate mixin declarations/objects.
To serialize a Mixin:
If the value is not being inspected, throw an error.
Otherwise:
Emit "get-mixin(".
Emit a double quote ("), then the name of the mixin, then another double
quote.
Emit ")".
meta.type-of()Add the following clause to the meta.type-of() function and the top-level
type-of() function:
$value is a mixin, return an unquoted string with value "mixin".meta.get-mixin()This is a new function in the sass:meta module.
meta.get-mixin($name, $module: null)
If $name is not a string, throw an error.
If $module is null:
$name. If this returns
null, throw an error.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.
meta.module-mixins()This is a new function in the sass:meta module.
meta.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.
meta.accepts-content()This is a new function in the sass:meta module.
meta.accepts-content($mixin)
If $mixin is not a mixin, throw an error.
Return a boolean which is true if the body of $mixin has an @content rule.
meta.apply()meta.apply($mixin, $args...)
If $mixin is not a mixin, throw an error.
If the current @include rule has a ContentBlock and $mixin's body does not
contain an @content rule, throw an error.
Execute the ArgumentInvocation (...$args) with $mixin's
ArgumentDeclaration in $mixin's scope. Treat the @include rule that
invoked meta.apply as the @include rule that invoked $mixin.
This ensures that any
@contentrules in$mixinwill usemeta.apply()'sContentBlock.
$mixin.import {Value} from '../spec/js-api/value';
SassMixinThe JS API representation of a Sass mixin.
export class SassMixin extends Value {
assertMixinReturns this if it's a SassMixin and throws an error otherwise.
The
nameparameter may be used for error reporting.
assertMixin(name?: string): SassMixin;
internalThe private internal field refers to a Sass mixin.
Throws an error.
constructor();
} // SassMixin
This document proposes adding a new value:
// 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;
}
The protocol allows first-class mixins defined in the compiler to be passed
to the host and vice-versa as Value.CompilerMixins.
Two first-class mixins are equal if they have the same ID.