Skip to content

Commit 29f232a

Browse files
authored
Convert the module system proposal to stable specifications (sass#2777)
1 parent 2c08156 commit 29f232a

22 files changed

+2575
-424
lines changed

spec/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spec.md

spec/at-rules/extend.md

Lines changed: 190 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,24 @@ many interacting layers and a lot of intricate case analysis.
66

77
## Table of Contents
88

9-
* [Definition](#definition)
9+
* [Definitions](#definitions)
10+
* [Extender](#extender)
11+
* [Target](#target)
12+
* [Extension](#extension)
13+
* [Extendee](#extendee)
14+
* [The `extend()` Function](#the-extend-function)
15+
* [Semantics](#semantics)
16+
* [Executing an `@extend` Rule](#executing-an-extend-rule)
17+
* [Resolving a Module's Extensions](#resolving-a-modules-extensions)
1018
* [Limitations](#limitations)
1119
* [Specificity](#specificity)
1220
* [The First Law](#the-first-law)
1321
* [The Second Law](#the-second-law)
1422

15-
## Definition
23+
## Definitions
1624

17-
First, let's give names to the various selectors involved with a given use of
18-
`@extend`:
25+
These definitions provide names to the various selectors involved with a given
26+
use of `@extend`:
1927

2028
```scss
2129
.extender {
@@ -28,16 +36,185 @@ First, let's give names to the various selectors involved with a given use of
2836
}
2937
```
3038

31-
The **extender** is the selector list for the style rule in which the `@extend`
32-
rule appears. The **target** is the simple selector that's used as an argument
33-
to `@extend`. The **extendee** is the selector list elsewhere in the stylesheet
34-
that contains the target and is modified to include the extender as well. We
35-
also use the function `extend(extendee, target, extender)` to refer to the
36-
result of extending `extendee` with `extender {@extend target}` (much like the
37-
Sass function `selector-extend()`).
39+
### Extender
3840

39-
Given these definitions, the `@extend` rule means that **all elements matching
40-
the extender should be styled as though they match the target as well**.
41+
An `@extend` rule's *extender* is the [selector list][] for the style rule in
42+
which the `@extend` rule appears.
43+
44+
[selector list]: https://drafts.csswg.org/selectors-4/#selector-list
45+
46+
### Target
47+
48+
An `@extend` rule's *target* is the [simple selector][] that's used as an
49+
argument to `@extend`.
50+
51+
[simple selector]: https://drafts.csswg.org/selectors-4/#simple
52+
53+
### Extension
54+
55+
An *extension* is a collection of various properties.
56+
57+
> An extension is a more abstract representation of the information inherent in
58+
> an `@extend` rule. As such, all `@extend` rules define extensions, but not all
59+
> extensions directly correspond to `@extend` rules.
60+
61+
* The *extender*, a [selector list][].
62+
* The *target*, a [simple selector][].
63+
64+
### Extendee
65+
66+
An *extendee* is a selector list being modified by an [extension](#extension).
67+
It's only defined within the scope of a single application of a given extension.
68+
69+
> If an extendee contains that extensions's target, it will usually be modified
70+
> to include the extension's extender as well.
71+
72+
### The `extend()` Function
73+
74+
As a shorthand, we use the function notation `extend(extendee, target,
75+
extender)` to refer to the result of extending `extendee` with `extender
76+
{@extend target}` (much like the Sass function `selector-extend()`). We further
77+
use `extend(extendee, extension)` as a shorthand for `extend(extendee,
78+
extension.target, extension.extender)`.
79+
80+
## Semantics
81+
82+
The `@extend` rule means that all elements matching the [extender](#extender)
83+
should be styled as though they match the [target](#target) as well. The
84+
`@extend` rule only applies to CSS in the module in which it's defined and
85+
that module's transitive dependencies.
86+
87+
> Because Sass can't directly affect how the browser applies styles to elements,
88+
> these semantics are approximated by duplicating each [extendee](#extendee)
89+
> with the target replaced by the extender. Rather than being a naïve textual
90+
> replacement, the extender is integrated intelligently into the extendee to
91+
> match the semantics as best as possible.
92+
93+
### Executing an `@extend` Rule
94+
95+
To execute an `@extend` rule `rule`:
96+
97+
* If there is no [current style rule][], throw an error.
98+
99+
[current style rule]: ../style-rules.md#current-style-rule
100+
101+
* Let `selector` be the result of evaluating all interpolation in `rule`'s
102+
selector and parsing the result as a list of simple selectors.
103+
104+
* If `selector` contains any parent selectors, throw an error.
105+
106+
* Let `extension` be an [extension](#extension) whose extender is the current
107+
style rule's selector and whose target is `selector`.
108+
109+
* Add `extension` to [the current module][]'s extensions.
110+
111+
[the current module]: ../spec.md#the-current-module
112+
113+
> Note that this adds the extension to the module being evaluated, not the
114+
> module in which the `@extend` lexically appears. This means that `@extend`s
115+
> are effectively dynamically scoped, not lexically scoped.
116+
117+
### Resolving a Module's Extensions
118+
119+
This algorithm takes a [module][] `starting-module` and returns a [CSS tree][]
120+
that includes CSS for *all* modules transitively used or forwarded by
121+
`starting-module`.
122+
123+
[module]: ../modules.md#module
124+
[CSS tree]: ../modules.md#css-tree
125+
126+
* Let `new-selectors` be an empty map from style rules to selectors. For the
127+
purposes of this map, style rules are compared using *reference equality*,
128+
meaning that style rules at different points in the CSS tree are always
129+
considered different even if their contents are the same.
130+
131+
* Let `new-extensions` be an empty map from modules to sets of
132+
[extensions](#extension).
133+
134+
* Let `extended` be the subgraph of the [module graph][] containing
135+
modules that are transitively reachable from `starting-module`.
136+
137+
[module graph]: ../modules.md#module-graph
138+
139+
* For each module `domestic` in `extended`, in reverse [topological][] order:
140+
141+
[topological]: https://en.wikipedia.org/wiki/Topological_sorting
142+
143+
* Let `downstream` be the set of modules in `extended` whose dependencies
144+
include `domestic`.
145+
146+
* For each style rule `rule` in `domestic`'s CSS:
147+
148+
* Let `selector` be the result of applying `domestic`'s extensions to
149+
`rule`'s selector.
150+
151+
* Let `selector-lists` be an empty set of selector lists.
152+
153+
* For each module `foreign` in `downstream`:
154+
155+
* Let `extended-selector` be `extend(selector, new-extensions[foreign])`.
156+
157+
> `new-extensions[foreign]` is guaranteed to be populated at this point
158+
> because `extended` is traversed in reverse topological order, which
159+
> means that `foreign`'s own extensions will already have been resolved
160+
> by the time we start working on modules upstream of it.
161+
162+
* Add `selector` to `selector-lists`.
163+
164+
* Set `new-selectors[rule]` to a selector that matches the union of all
165+
elements matched by selectors in `selector-lists`. This selector must obey
166+
[the specificity laws](#specificity) relative to the selectors from which
167+
it was generated. For the purposes of [the first law](#the-first-law),
168+
"the original extendee" is considered only to refer to selectors that
169+
appear in `domestic`'s CSS, *not* selectors that were added by other
170+
modules' extensions.
171+
172+
> Implementations are expected to trim redundant selectors from
173+
> `selector-lists` as much as possible. For the purposes of the first law
174+
> of extend, "the original extendee" is *only* the selectors in `rule`'s
175+
> selector. The new complex selectors in `selector` generated from
176+
> `domestic`'s extensions don't count as "original", and may be optimized
177+
> away.
178+
179+
* For every extension `extension` whose extender appears in `rule`'s
180+
selector:
181+
182+
* For every complex selector `complex` in `new-selectors[rule]`:
183+
184+
* Add a copy of `extension` with its extender replaced by `complex` to
185+
`new-extensions[domestic]`.
186+
187+
* Let `css` be an empty CSS tree.
188+
189+
* Define a mutating recursive procedure, *traversing*, which takes a module
190+
`domestic`:
191+
192+
* If `domestic` has already been traversed, do nothing.
193+
194+
* Otherwise, traverse every module in `domestic`'s dependencies.
195+
196+
> Because this traverses modules depth-first, it emits CSS in reverse
197+
> topological order.
198+
199+
* Let `initial-imports` be the longest initial subsequence of top-level
200+
statements in `domestic`'s CSS tree that contains only comments and
201+
`@import` rules *and* that ends with an `@import` rule.
202+
203+
* Insert a copy of `initial-imports` in `css` after the last `@import` rule, or
204+
at the beginning of `css` if it doesn't contain any `@import` rules.
205+
206+
* For each top-level statement `statement` in `domestic`'s CSS tree after
207+
`initial-imports`:
208+
209+
* If `statement` is an `@import` rule, insert a copy of `statement` in `css`
210+
after the last `@import` rule, or at the beginning of `css` if it doesn't
211+
contain any `@import` rules.
212+
213+
* Otherwise, add a copy of `statement` to the end of `css`, with any style
214+
rules' selectors replaced with the corresponding selectors in
215+
`new-selectors`.
216+
217+
* Return `css`.
41218

42219
### Limitations
43220

@@ -76,7 +253,6 @@ required to meet the full definition.
76253
When modifying the extendee during extension, the implementation must provide
77254
two guarantees about the result. These are known as the "laws of extend".
78255

79-
80256
#### The First Law
81257

82258
The first law of `@extend` says that the specificity of the first generated

spec/at-rules/forward.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# `@forward`
2+
3+
The `@forward` rule loads a [module][] from a URL and adds its members to the
4+
public API of the current module without making them available to use within the
5+
current stylesheet.
6+
7+
[module]: ../modules.md#module
8+
9+
## Table of Contents
10+
11+
* [Syntax](#syntax)
12+
* [Semantics](#semantics)
13+
14+
## Syntax
15+
16+
The grammar for the `@forward` rule is as follows:
17+
18+
<x><pre>
19+
**ForwardRule** ::= '@forward' QuotedString AsClause? (ShowClause | HideClause)?
20+
**AsClause** ::= 'as' Identifier '*'
21+
**ShowClause** ::= 'show' MemberName (',' MemberName)*
22+
**HideClause** ::= 'hide' MemberName (',' MemberName)*
23+
**MemberName** ::= '$'? Identifier
24+
</pre></x>
25+
26+
`@forward` rules must be at the top level of the document, and must come before
27+
any rules other than `@charset` or `@use`. The `QuotedString`'s contents, known
28+
as the rule's *URL*, must be a [valid URL string][] (for non-[special][] base
29+
URL). No whitespace is allowed after `$` in `MemberName`, or before `*` in
30+
`AsClause`.
31+
32+
[valid URL string]: https://url.spec.whatwg.org/#valid-url-string
33+
[special]: https://url.spec.whatwg.org/#special-scheme
34+
35+
## Semantics
36+
37+
> Note that `@forward` *does not* make any APIs available to the current module;
38+
> that is purely the domain of `@use`. It *does* include the forwarded module's
39+
> CSS tree, but it's not visible to `@extend` without also using the module.
40+
41+
To execute a `@forward` rule `rule`:
42+
43+
* If `rule` has an `AsClause` with identifier `prefix`:
44+
45+
* Let `rule-config` be an empty [configuration][].
46+
47+
[configuration]: ../modules.md#configuration
48+
49+
* For each variable `variable` in [the current configuration][]:
50+
51+
[the current configuration]: ../spec.md#current-configuration
52+
53+
* If `variable`'s name begins with `prefix`:
54+
55+
* Let `suffix` be the portion of `variable`'s name after `prefix`.
56+
57+
* Add a variable to `rule-config` with the name `suffix` and with the
58+
same value as `variable`.
59+
60+
* Otherwise, let `rule-config` be the current configuration.
61+
62+
* Let `forwarded` be the result of [loading the module][] with `rule`'s URL
63+
string and `rule-config`.
64+
65+
[loading the module]: ../modules.md#loading-a-module
66+
67+
* For every member `member` in `forwarded`:
68+
69+
* Let `name` be `member`'s name.
70+
71+
* If `rule` has an `AsClause` `as`, prepend `as`'s identifier to `name` (after
72+
the `$` if `member` is a variable).
73+
74+
* If there's a member defined at the top level of [the current source file][]
75+
named `name` with the same type as `member`, do nothing.
76+
77+
[the current source file]: ../spec.md#current-source-file
78+
79+
* Otherwise, if `rule` has a `show` clause that doesn't include `name`
80+
(including `$` for variables), do nothing.
81+
82+
> It's not possible to show/hide a mixin without showing/hiding the
83+
> equivalent function, or to do the reverse.
84+
85+
* Otherwise, if `rule` has a `hide` clause that does include `name` (including
86+
`$` for variables), do nothing.
87+
88+
* Otherwise, if another `@forward` rule's module has a member named `name`
89+
with the same type as `member`, throw an error.
90+
91+
* Otherwise, add `member` to [the current module][] with the name `name`.
92+
93+
[the current module]: ../spec.md#current-module
94+
95+
> It's possible for the same member to be added to a given module multiple
96+
> times if it's forwarded with different prefixes. All of these names refer
97+
> to the same logical member, so for example if a variable gets set that
98+
> change will appear for all of its names.
99+
>
100+
> It's also possible for a module's members to have multiple prefixes added,
101+
> if they're forwarded with prefixes multiple times.

spec/at-rules/function.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# `@function`
2+
3+
## Table of Contents
4+
5+
* [Syntax](#syntax)
6+
* [Semantics](#semantics)
7+
8+
## Syntax
9+
10+
<x><pre>
11+
**FunctionRule** ::= '@function' Identifier ArgumentDeclaration '{' Statements '}'
12+
</pre></x>
13+
14+
No whitespace is allowed between the `Identifier` and the `ArgumentDeclaration`
15+
in `FunctionRule`.
16+
17+
## Semantics
18+
19+
To execute a `@function` rule `rule`:
20+
21+
* Let `name` be the value of `rule`'s `Identifier`.
22+
23+
* If `rule` is outside of any block of statements:
24+
25+
* If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s
26+
function `name` to `rule`.
27+
28+
[the current module]: ../spec.md#current-module
29+
30+
> This overrides the previous definition, if one exists.
31+
32+
* Set [the current import context][]'s function `name` to `rule`.
33+
34+
[the current import context]: ../spec.md#current-import-context
35+
36+
> This happens regardless of whether or not it begins with `-` or `_`.
37+
38+
* Otherwise, set the innermost block's [scope][]'s function `name` to `value`.
39+
40+
[scope]: ../variables.md#scope

0 commit comments

Comments
 (0)