6.6 KiB
Reconfigurable Modules: Draft 1.1
Table of Contents
Background
This section is non-normative.
In the existing module system each module can only be cofigured once, the first time it is used. That works well for direct use of modules, but doesn't allow for "middleware" modules to forward pre-configured, and re-configurable modules. It is often useful for complex libraries to provide a "core" module with unopinionated defaults, and then specialized wrapper modules with more opinionated configurations and additional helpers. That wrapper package needs to:
- Set some or all origin-package configurations
- Allow the user to also set some or all origin-package configurations, along with new middleware configurations
- Use the configured origin-package to provide additional members based on the fully-configured origin module
Part 3 should be possible in the existing system by writing the @forward
rules before the @use
rules, but parts 1 and 2 are not currently possible
in combination.
This proposal provides a syntax for middleware modules to add configuration of the root module, without removing that option for end-users.
Summary
This section is non-normative.
Sass will add a with
clause to @forward
. The @forward ... with
syntax is
based on the @use ... with
syntax, but allows the addition of !default
flags
similar to a variable declaration. Unlike @use ... with
, unconfigured
origin variables, and variables configured with a !default
flag, will remain
configurable by any file importing the combined module. For example:
// _origin.scss
$hue: 0 !default;
$saturation: 50% !default;
// _middleware.scss
@forward "origin" with (
$hue: 330 !default, // Can be overridden by importing users.
$saturation: 70% // Cannot be overridden by importing users.
);
// entrypoint.scss
@use "middleware" with (
$hue: 120 // override both the origin & middleware !default values
);
// middleware.$hue == 120
// middleware.$saturation == 70%
Keyword arguments in the configuration must reference variable names as
defined in the forwarded module, regardless of any concurent as
clause:
// _origin.scss
$hue: 0 !default;
$color-hex: #ccc !default;
// _middleware.scss
@forward "origin" as color-* with (
$hue: 330, // the color-* prefix is not referenced in configuration
$color-hex: #966
);
// entrypoint.scss
@use "middleware" as m;
// m.$color-hue == 330
// m.$color-hex == #966
A @forward
rule configuration is applied to the source module even if the
forwarding module acts as an entrypoint:
// _origin.scss
$hue: 0 !default;
// entrypoint.scss
@forward "origin" with (
$hue: 330 !default
);
@use "origin"; // origin.$hue == 330
Multiple configurations can be chained in a single cascading "thread" that
contains zero or more @forward
rules, and zero or one terminal @use
rule.
Variables remain open to configuration in the chain as long as every mention
includes the !default
flag. Multiple threads configuring a single module will
cause an error, even if they originate in the same file.
Syntax
The new WithClause
extends @forward
to the follow grammar:
ForwardRule ::= '@forward' QuotedString AsClause? (ShowClause | HideClause)? WithClause? WithClause ::= 'with' '(' KeywordArgument (',' KeywordArgument)* ','? ')' ForwardWithArgument ::= '$' Identifier ':' Expression '!default'?
Semantics
The @forward ... with
semantics builds on the existing proposal for
Executing Files, and should be understood as modifying and expanding upon
the existing execution process rather than being a comprehensive replacement.
Given a source file file
, a configuration config
, and an import context
import
:
-
Let
module
be an empty module with the same URL asfile
. -
Let
uses
be an empty map from@use
rules to modules. -
When a
@use
rulerule
is encountered:-
If
rule
has a namespace that's the same as another@use
rule's namespace infile
, throw an error. -
Let
rule-config
be the empty configuration. -
If
rule
has aWithClause
:-
For each
KeywordArgument
argument
in this clause:-
Let
value
be the result of evaluatingargument
's expression. -
Add a variable to
rule-config
with the same name asargument
's identifier and withvalue
as its value.
-
-
-
Let
module
be the result of loading the module withrule
's URL andrule-config
. -
If
rule
has aWithClause
that contains any variables that aren't part ofmodule
's public API or that weren't declared with a!default
flag inmodule
, throw an error. -
Associate
rule
withmodule
inuses
.
-
-
When a
@forward
rulerule
is encountered:-
If
rule
has anAsClause
with identifierprefix
:-
Let
rule-config
be an empty configuration. -
For each variable
variable
inconfig
:-
If
variable
's name begins withprefix
:-
Let
suffix
be the portion ofvariable
's name afterprefix
. -
Add a variable to
rule-config
with the namesuffix
and with the same value asvariable
.
-
-
-
-
Otherwise, let
rule-config
beconfig
. -
If
rule
has aWithClause
:-
For each
ForwardWithArgument
argument
in this clause:-
If
argument
has a!default
flag and a variable exists inrule-config
with the same name asargument
's identifier, do nothing. -
Otherwise, let
value
be the result of evaluatingargument
's expression. -
Add a variable to
rule-config
with the same name asargument
's identifier, and withvalue
as its value.
-
-
-
Let
forwarded
be the result of loading the module withrule
's URL andrule-config
. -
If
rule
has aWithClause
that contains any variables that aren't part offorwarded
's public API or that weren't declared with a!default
flag inforwarded
, throw an error. -
Forward
forwarded
withfile
throughmodule
.
-
From this point on, the logic remains unchanged.