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.
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 == #966A @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 == 330Multiple 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.
The new WithClause extends @forward to the follow grammar:
ForwardRule ::= '@forward' QuotedString AsClause? (ShowClause | HideClause)? WithClause?
WithClause ::= 'with' '('
KeywordArgument (',' KeywordArgument)* ','?
')'
ForwardWithArgument ::= '$' Identifier ':' Expression '!default'?
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
modulebe an empty module with the same URL asfile. -
Let
usesbe an empty map from@userules to modules. -
When a
@useruleruleis encountered:-
If
rulehas a namespace that's the same as another@userule's namespace infile, throw an error. -
Let
rule-configbe the empty configuration. -
If
rulehas aWithClause:-
For each
KeywordArgumentargumentin this clause:-
Let
valuebe the result of evaluatingargument's expression. -
Add a variable to
rule-configwith the same name asargument's identifier and withvalueas its value.
-
-
-
Let
modulebe the result of loading the module withrule's URL andrule-config. -
If
rulehas aWithClausethat contains any variables that aren't part ofmodule's public API or that weren't declared with a!defaultflag inmodule, throw an error. -
Associate
rulewithmoduleinuses.
-
-
When a
@forwardruleruleis encountered:-
If
rulehas anAsClausewith identifierprefix:-
Let
rule-configbe an empty configuration. -
For each variable
variableinconfig:-
If
variable's name begins withprefix:-
Let
suffixbe the portion ofvariable's name afterprefix. -
Add a variable to
rule-configwith the namesuffixand with the same value asvariable.
-
-
-
-
Otherwise, let
rule-configbeconfig. -
If
rulehas aWithClause:-
For each
ForwardWithArgumentargumentin this clause:-
If
argumenthas a!defaultflag and a variable exists inrule-configwith the same name asargument's identifier, do nothing. -
Otherwise, let
valuebe the result of evaluatingargument's expression. -
Add a variable to
rule-configwith the same name asargument's identifier, and withvalueas its value.
-
-
-
Let
forwardedbe the result of loading the module withrule's URL andrule-config. -
If
rulehas aWithClausethat contains any variables that aren't part offorwarded's public API or that weren't declared with a!defaultflag inforwarded, throw an error. -
Forward
forwardedwithfilethroughmodule.
-
From this point on, the logic remains unchanged.