Skip to content

Commit dd56d28

Browse files
authoredNov 3, 2023
Write implementation documentation (#2042)
Also rename Dispatcher to CompilationDispatcher for clarity
1 parent 54caf19 commit dd56d28

21 files changed

+595
-20
lines changed
 

‎CONTRIBUTING.md

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Want to contribute? Great! First, read this page.
1010
* [Synchronizing](#synchronizing)
1111
* [File Headers](#file-headers)
1212
* [Release Process](#release-process)
13+
* [Package Structure](#package-structure)
1314

1415
## Before You Contribute
1516

@@ -245,3 +246,13 @@ few things to do before pushing that tag:
245246

246247
You *don't* need to create tags for packages in `pkg`; that will be handled
247248
automatically by GitHub actions.
249+
250+
## Package Structure
251+
252+
The structure of the Sass package is documented in README.md files in most
253+
directories under `lib/`. This documentation is intended to help contributors
254+
quickly build a basic understanding of the structure of the compiler and how its
255+
various pieces fit together. [`lib/src/README.md`] is a good starting point to get
256+
an overview of the compiler as a whole.
257+
258+
[`lib/src/README.md`]: lib/src/README.md

‎lib/src/README.md

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# The Sass Compiler
2+
3+
* [Life of a Compilation](#life-of-a-compilation)
4+
* [Late Parsing](#late-parsing)
5+
* [Early Serialization](#early-serialization)
6+
* [JS Support](#js-support)
7+
* [APIs](#apis)
8+
* [Importers](#importers)
9+
* [Custom Functions](#custom-functions)
10+
* [Loggers](#loggers)
11+
* [Built-In Functions](#built-in-functions)
12+
* [`@extend`](#extend)
13+
14+
This is the root directory of Dart Sass's private implementation libraries. This
15+
contains essentially all the business logic defining how Sass is actually
16+
compiled, as well as the APIs that users use to interact with Sass. There are
17+
two exceptions:
18+
19+
* [`../../bin/sass.dart`] is the entrypoint for the Dart Sass CLI (on all
20+
platforms). While most of the logic it runs exists in this directory, it does
21+
contain some logic to drive the basic compilation logic and handle errors. All
22+
the most complex parts of the CLI, such as option parsing and the `--watch`
23+
command, are handled in the [`executable`] directory. Even Embedded Sass runs
24+
through this entrypoint, although it gets immediately gets handed off to [the
25+
embedded compiler].
26+
27+
[`../../bin/sass.dart`]: ../../bin/sass.dart
28+
[`executable`]: executable
29+
[the embedded compiler]: embedded/README.md
30+
31+
* [`../sass.dart`] is the entrypoint for the public Dart API. This is what's
32+
loaded when a Dart package imports Sass. It just contains the basic
33+
compilation functions, and exports the rest of the public APIs from this
34+
directory.
35+
36+
[`../sass.dart`]: ../sass.dart
37+
38+
Everything else is contained here, and each file and some subdirectories have
39+
their own documentation. But before you dive into those, let's take a look at
40+
the general lifecycle of a Sass compilation.
41+
42+
## Life of a Compilation
43+
44+
Whether it's invoked through the Dart API, the JS API, the CLI, or the embedded
45+
host, the basic process of a Sass compilation is the same. Sass is implemented
46+
as an AST-walking [interpreter] that operates in roughly three passes:
47+
48+
[interpreter]: https://en.wikipedia.org/wiki/Interpreter_(computing)
49+
50+
1. **Parsing**. The first step of a Sass compilation is always to parse the
51+
source file, whether it's SCSS, the indented syntax, or CSS. The parsing
52+
logic lives in the [`parse`] directory, while the abstract syntax tree that
53+
represents the parsed file lives in [`ast/sass`].
54+
55+
[`parse`]: parse/README.md
56+
[`ast/sass`]: ast/sass/README.md
57+
58+
2. **Evaluation**. Once a Sass file is parsed, it's evaluated by
59+
[`visitor/async_evaluate.dart`]. (Why is there both an async and a sync
60+
version of this file? See [Synchronizing] for details!) The evaluator handles
61+
all the Sass-specific logic: it resolves variables, includes mixins, executes
62+
control flow, and so on. As it goes, it builds up a new AST that represents
63+
the plain CSS that is the compilation result, which is defined in
64+
[`ast/css`].
65+
66+
[`visitor/async_evaluate.dart`]: visitor/async_evaluate.dart
67+
[Synchronizing]: ../../CONTRIBUTING.md#synchronizing
68+
[`ast/css`]: ast/css/README.md
69+
70+
Sass evaluation is almost entirely linear: it begins at the first statement
71+
of the file, evaluates it (which may involve evaluating its nested children),
72+
adds its result to the CSS AST, and then moves on to the second statement. On
73+
it goes until it reaches the end of the file, at which point it's done. The
74+
only exception is module resolution: every Sass module has its own compiled
75+
CSS AST, and once the entrypoint file is done compiling the evaluator will go
76+
back through these modules, resolve `@extend`s across them as necessary, and
77+
stitch them together into the final stylesheet.
78+
79+
SassScript, the expression-level syntax, is handled by the same evaluator.
80+
The main difference between SassScript and statement-level evaluation is that
81+
the same SassScript values are used during evaluation _and_ as part of the
82+
CSS AST. This means that it's possible to end up with a Sass-specific value,
83+
such as a map or a first-class function, as the value of a CSS declaration.
84+
If that happens, the Serialization phase will signal an error when it
85+
encounters the invalid value.
86+
87+
3. **Serialization**. Once we have the CSS AST that represents the compiled
88+
stylesheet, we need to convert it into actual CSS text. This is done by
89+
[`visitor/serialize.dart`], which walks the AST and builds up a big buffer of
90+
the resulting CSS. It uses [a special string buffer] that tracks source and
91+
destination locations in order to generate [source maps] as well.
92+
93+
[`visitor/serialize.dart`]: visitor/serialize.dart
94+
[a special string buffer]: util/source_map_buffer.dart
95+
[source maps]: https://web.dev/source-maps/
96+
97+
There's actually one slight complication here: the first and second pass aren't
98+
as separate as they appear. When one Sass stylesheet loads another with `@use`,
99+
`@forward`, or `@import`, that rule is handled by the evaluator and _only at
100+
that point_ is the loaded file parsed. So in practice, compilation actually
101+
switches between parsing and evaluation, although each individual stylesheet
102+
naturally has to be parsed before it can be evaluated.
103+
104+
### Late Parsing
105+
106+
Some syntax within a stylesheet is only parsed _during_ evaluation. This allows
107+
authors to use `#{}` interpolation to inject Sass variables and other dynamic
108+
values into various locations, such as selectors, while still allowing Sass to
109+
parse them to support features like nesting and `@extend`. The following
110+
syntaxes are parsed during evaluation:
111+
112+
* [Selectors](parse/selector.dart)
113+
* [`@keyframes` frames](parse/keyframe_selector.dart)
114+
* [Media queries](parse/media_query.dart) (for historical reasons, these are
115+
parsed before evaluation and then _reparsed_ after they've been fully
116+
evaluated)
117+
118+
### Early Serialization
119+
120+
There are also some cases where the evaluator can serialize values before the
121+
main serialization pass. For example, if you inject a variable into a selector
122+
using `#{}`, that variable's value has to be converted to a string during
123+
evaluation so that the evaluator can then parse and handle the newly-generated
124+
selector. The evaluator does this by invoking the serializer _just_ for that
125+
specific value. As a rule of thumb, this happens anywhere interpolation is used
126+
in the original stylesheet, although there are a few other circumstances as
127+
well.
128+
129+
## JS Support
130+
131+
One of the main benefits of Dart as an implementation language is that it allows
132+
us to distribute Dart Sass both as an extremely efficient stand-alone executable
133+
_and_ an easy-to-install pure-JavaScript package, using the dart2js compilation
134+
tool. However, properly supporting JS isn't seamless. There are two major places
135+
where we need to think about JS support:
136+
137+
1. When interfacing with the filesystem. None of Dart's IO APIs are natively
138+
supported on JS, so for anything that needs to work on both the Dart VM _and_
139+
Node.js we define a shim in the [`io`] directory that will be implemented in
140+
terms of `dart:io` if we're running on the Dart VM or the `fs` or `process`
141+
modules if we're running on Node. (We don't support IO at all on the browser
142+
except to print messages to the console.)
143+
144+
[`io`]: io/README.md
145+
146+
2. When exposing an API. Dart's JS interop is geared towards _consuming_ JS
147+
libraries from Dart, not producing a JS library written in Dart, so we have
148+
to jump through some hoops to make it work. This is all handled in the [`js`]
149+
directory.
150+
151+
[`js`]: js/README.md
152+
153+
## APIs
154+
155+
One of Sass's core features is its APIs, which not only compile stylesheets but
156+
also allow users to provide plugins that can be invoked from within Sass. In
157+
both the JS API, the Dart API, and the embedded compiler, Sass provides three
158+
types of plugins: importers, custom functions, and loggers.
159+
160+
### Importers
161+
162+
Importers control how Sass loads stylesheets through `@use`, `@forward`, and
163+
`@import`. Internally, _all_ stylesheet loads are modeled as importers. When a
164+
user passes a load path to an API or compiles a stylesheet through the CLI, we
165+
just use the built-in [`FilesystemImporter`] which implements the same interface
166+
that we make available to users.
167+
168+
[`FilesystemImporter`]: importer/filesystem.dart
169+
170+
In the Dart API, the importer root class is [`importer/async_importer.dart`].
171+
The JS API and the embedded compiler wrap the Dart importer API in
172+
[`importer/node_to_dart`] and [`embedded/importer`] respectively.
173+
174+
[`importer/async_importer.dart`]: importer/async_importer.dart
175+
[`importer/node_to_dart`]: importer/node_to_dart
176+
[`embedded/importer`]: embedded/importer
177+
178+
### Custom Functions
179+
180+
Custom functions are defined by users of the Sass API but invoked by Sass
181+
stylesheets. To a Sass stylesheet, they look like any other built-in function:
182+
users pass SassScript values to them and get SassScript values back. In fact,
183+
all the core Sass functions are implemented using the Dart custom function API.
184+
185+
Because custom functions take and return SassScript values, that means we need
186+
to make _all_ values available to the various APIs. For Dart, this is
187+
straightforward: we need to have objects to represent those values anyway, so we
188+
just expose those objects publicly (with a few `@internal` annotations here and
189+
there to hide APIs we don't want users relying on). These value types live in
190+
the [`value`] directory.
191+
192+
[`value`]: value/README.md
193+
194+
Exposing values is a bit more complex for other platforms. For the JS API, we do
195+
a bit of metaprogramming in [`js/value`] so that we can return the
196+
same Dart values we use internally while still having them expose a JS API that
197+
feels native to that language. For the embedded host, we convert them to and
198+
from a protocol buffer representation in [`embedded/protofier.dart`].
199+
200+
[`js/value`]: js/value/README.md
201+
[`embedded/value.dart`]: embedded/value.dart
202+
203+
### Loggers
204+
205+
Loggers are the simplest of the plugins. They're just callbacks that are invoked
206+
any time Dart Sass would emit a warning (from the language or from `@warn`) or a
207+
debug message from `@debug`. They're defined in:
208+
209+
* [`logger.dart`](logger.dart) for Dart
210+
* [`js/logger.dart`](js/logger.dart) for Node
211+
* [`embedded/logger.dart`](embedded/logger.dart) for the embedded compiler
212+
213+
## Built-In Functions
214+
215+
All of Sass's built-in functions are defined in the [`functions`] directory,
216+
including both global functions and functions defined in core modules like
217+
`sass:math`. As mentioned before, these are defined using the standard custom
218+
function API, although in a few cases they use additional private features like
219+
the ability to define multiple overloads of the same function name.
220+
221+
[`functions`]: functions/README.md
222+
223+
## `@extend`
224+
225+
The logic for Sass's `@extend` rule is particularly complex, since it requires
226+
Sass to not only parse selectors but to understand how to combine them and when
227+
they can be safely optimized away. Most of the logic for this is contained
228+
within the [`extend`] directory.
229+
230+
[`extend`]: extend/README.md

‎lib/src/ast/css/README.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# CSS Abstract Syntax Tree
2+
3+
This directory contains the abstract syntax tree that represents a plain CSS
4+
file generated by Sass compilation. It differs from other Sass ASTs in two major
5+
ways:
6+
7+
1. Instead of being created by [a parser], it's created by [the evaluator] as it
8+
traverses the [Sass AST].
9+
10+
[a parser]: ../../parse/README.md
11+
[the evaluator]: ../../visitor/async_evaluate.dart
12+
[Sass AST]: ../sass/README.md
13+
14+
2. Because of various Sass features like `@extend` and at-rule hoisting, the CSS
15+
AST is mutable even though all other ASTs are immutable.
16+
17+
**Note:** the CSS AST doesn't have its own representation of declaration values.
18+
Instead, declaration values are represented as [`Value`] objects. This does mean
19+
that a CSS AST can be in a state where some of its values aren't representable
20+
in plain CSS (such as maps)—in this case, [the serializer] will emit an error.
21+
22+
[`Value`]: ../../value/README.md
23+
[the serializer]: ../../visitor/serialize.dart
24+
25+
## Mutable and Immutable Views
26+
27+
Internally, the CSS AST is mutable to allow for operations like hoisting rules
28+
to the root of the AST and updating existing selectors when `@extend` rules are
29+
encountered. However, because mutability poses a high risk for "spooky [action
30+
at a distance]", we limit access to mutating APIs exclusively to the evaluator.
31+
32+
[action at a distance]: https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)
33+
34+
We do this by having an _unmodifiable_ interface (written in this directory) for
35+
each CSS AST node which only exposes members that don't modify the node in
36+
question. The implementations of those interfaces, which _do_ have modifying
37+
methods, live in the [`modifiable`] directory. We then universally refer to the
38+
immutable node interfaces except specifically in the evaluator, and the type
39+
system automatically ensures we don't accidentally mutate anything we don't
40+
intend to.
41+
42+
[`modifiable`]: modifiable
43+
44+
(Of course, it's always possible to cast an immutable node type to a mutable
45+
one, but that's a very clear code smell that a reviewer can easily identify.)
46+
47+
## CSS Source Files
48+
49+
A lesser-known fact about Sass is that it actually supports _three_ syntaxes for
50+
its source files: SCSS, the indented syntax, and plain CSS. But even when it
51+
parses plain CSS, it uses the Sass AST rather than the CSS AST to represent it
52+
so that parsing logic can easily be shared with the other stylesheet parsers.

‎lib/src/ast/sass/README.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Sass Abstract Syntax Tree
2+
3+
This directory contains the abstract syntax tree that represents a Sass source
4+
file, regardless of which syntax it was written in (SCSS, the indented syntax,
5+
or plain CSS). The AST is constructed recursively by [a parser] from the leaf
6+
nodes in towards the root, which allows it to be fully immutable.
7+
8+
[a parser]: ../../parse/README.md
9+
10+
The Sass AST is broken up into three categories:
11+
12+
1. The [statement AST], which represents statement-level constructs like
13+
variable assignments, style rules, and at-rules.
14+
15+
[statement AST]: statement
16+
17+
2. The [expression AST], which represents SassScript expressions like function
18+
calls, operations, and value literals.
19+
20+
[expression AST]: exprssion
21+
22+
3. Miscellaneous AST nodes that are used by both statements and expressions or
23+
don't fit cleanly into either category that live directly in this directory.
24+
25+
The Sass AST nodes are processed (usually from the root [`Stylesheet`]) by [the
26+
evaluator], which runs the logic they encode and builds up a [CSS AST] that
27+
represents the compiled stylesheet. They can also be transformed back into Sass
28+
source using the `toString()` method. Since this is only ever used for debugging
29+
and doesn't need configuration or full-featured indentation tracking, it doesn't
30+
use a full visitor.
31+
32+
[`Stylesheet`]: statement/stylesheet.dart
33+
[the evaluator]: ../../visitor/async_evaluate.dart
34+
[CSS AST]: ../css/README.md

‎lib/src/ast/selector/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Selector Abstract Syntax Tree
2+
3+
This directory contains the abstract syntax tree that represents a parsed CSS
4+
selector. This AST is constructed recursively by [the selector parser]. It's
5+
fully immutable.
6+
7+
[the selector parser]: ../../parse/selector.dart
8+
9+
Unlike the [Sass AST], which is parsed from a raw source string before being
10+
evaluated, the selector AST is parsed _during evaluation_. This is necessary to
11+
ensure that there's a chance to resolve interpolation before fully parsing the
12+
selectors in question.
13+
14+
[Sass AST]: ../sass/README.md
15+
16+
Although this AST doesn't include any SassScript, it _does_ include a few
17+
Sass-specific constructs: the [parent selector] `&` and [placeholder selectors].
18+
Parent selectors are resolved by [the evaluator] before it hands the AST off to
19+
[the serializer], while placeholders are omitted in the serializer itself.
20+
21+
[parent selector]: parent.dart
22+
[placeholder selectors]: placeholder.dart
23+
[the evaluator]: ../../visitor/async_evaluate.dart
24+
[the serializer]: ../../visitor/serialize.dart

‎lib/src/embedded/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Embedded Sass Compiler
2+
3+
This directory contains the Dart Sass embedded compiler. This is a special mode
4+
of the Dart Sass command-line executable, only supported on the Dart VM, in
5+
which it uses stdin and stdout to communicate with another endpoint, the
6+
"embedded host", using a protocol buffer-based protocol. See [the embedded
7+
protocol specification] for details.
8+
9+
[the embedded protocol specification]: https://github.com/sass/sass/blob/main/spec/embedded-protocol.md
10+
11+
The embedded compiler has two different levels of dispatchers for handling
12+
incoming messages from the embedded host:
13+
14+
1. The [`IsolateDispatcher`] is the first recipient of each packet. It decodes
15+
the packets _just enough_ to determine which compilation they belong to, and
16+
forwards them to the appropriate compilation dispatcher. It also parses and
17+
handles messages that aren't compilation specific, such as `VersionRequest`.
18+
19+
[`IsolateDispatcher`]: isolate_dispatcher.dart
20+
21+
2. The [`CompilationDispatcher`] fully parses and handles messages for a single
22+
compilation. Each `CompilationDispatcher` runs in a separate isolate so that
23+
the embedded compiler can run multiple compilations in parallel.
24+
25+
[`CompilationDispatcher`]: compilation_dispatcher.dart
26+
27+
Otherwise, most of the code in this directory just wraps Dart APIs to
28+
communicate with their protocol buffer equivalents.

‎lib/src/embedded/dispatcher.dart ‎lib/src/embedded/compilation_dispatcher.dart

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ final _outboundRequestId = 0;
3131

3232
/// A class that dispatches messages to and from the host for a single
3333
/// compilation.
34-
final class Dispatcher {
34+
final class CompilationDispatcher {
3535
/// The mailbox for receiving messages from the host.
3636
final Mailbox _mailbox;
3737

@@ -54,9 +54,9 @@ final class Dispatcher {
5454
/// it'll just be a wrapper around the error.
5555
var _requestError = false;
5656

57-
/// Creates a [Dispatcher] that receives encoded protocol buffers through
58-
/// [_mailbox] and sends them through [_sendPort].
59-
Dispatcher(this._mailbox, this._sendPort);
57+
/// Creates a [CompilationDispatcher] that receives encoded protocol buffers
58+
/// through [_mailbox] and sends them through [_sendPort].
59+
CompilationDispatcher(this._mailbox, this._sendPort);
6060

6161
/// Listens for incoming `CompileRequests` and runs their compilations.
6262
void listen() {

‎lib/src/embedded/host_callable.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import '../callable.dart';
66
import '../exception.dart';
77
import '../value/function.dart';
88
import '../value/mixin.dart';
9-
import 'dispatcher.dart';
9+
import 'compilation_dispatcher.dart';
1010
import 'embedded_sass.pb.dart';
1111
import 'opaque_registry.dart';
1212
import 'protofier.dart';
@@ -21,7 +21,7 @@ import 'utils.dart';
2121
///
2222
/// Throws a [SassException] if [signature] is invalid.
2323
Callable hostCallable(
24-
Dispatcher dispatcher,
24+
CompilationDispatcher dispatcher,
2525
OpaqueRegistry<SassFunction> functions,
2626
OpaqueRegistry<SassMixin> mixins,
2727
String signature,

‎lib/src/embedded/importer/base.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
import 'package:meta/meta.dart';
66

77
import '../../importer.dart';
8-
import '../dispatcher.dart';
8+
import '../compilation_dispatcher.dart';
99

1010
/// An abstract base class for importers that communicate with the host in some
1111
/// way.
1212
abstract base class ImporterBase extends Importer {
13-
/// The [Dispatcher] to which to send requests.
13+
/// The [CompilationDispatcher] to which to send requests.
1414
@protected
15-
final Dispatcher dispatcher;
15+
final CompilationDispatcher dispatcher;
1616

1717
ImporterBase(this.dispatcher);
1818

‎lib/src/embedded/importer/file.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// https://opensource.org/licenses/MIT.
44

55
import '../../importer.dart';
6-
import '../dispatcher.dart';
6+
import '../compilation_dispatcher.dart';
77
import '../embedded_sass.pb.dart' hide SourceSpan;
88
import 'base.dart';
99

@@ -19,7 +19,8 @@ final class FileImporter extends ImporterBase {
1919
/// The host-provided ID of the importer to invoke.
2020
final int _importerId;
2121

22-
FileImporter(Dispatcher dispatcher, this._importerId) : super(dispatcher);
22+
FileImporter(CompilationDispatcher dispatcher, this._importerId)
23+
: super(dispatcher);
2324

2425
Uri? canonicalize(Uri url) {
2526
if (url.scheme == 'file') return _filesystemImporter.canonicalize(url);

‎lib/src/embedded/importer/host.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import '../../exception.dart';
66
import '../../importer.dart';
77
import '../../importer/utils.dart';
88
import '../../util/span.dart';
9-
import '../dispatcher.dart';
9+
import '../compilation_dispatcher.dart';
1010
import '../embedded_sass.pb.dart' hide SourceSpan;
1111
import '../utils.dart';
1212
import 'base.dart';
@@ -20,7 +20,7 @@ final class HostImporter extends ImporterBase {
2020
/// [canonicalize].
2121
final Set<String> _nonCanonicalSchemes;
2222

23-
HostImporter(Dispatcher dispatcher, this._importerId,
23+
HostImporter(CompilationDispatcher dispatcher, this._importerId,
2424
Iterable<String> nonCanonicalSchemes)
2525
: _nonCanonicalSchemes = Set.unmodifiable(nonCanonicalSchemes),
2626
super(dispatcher) {

‎lib/src/embedded/isolate_dispatcher.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'package:pool/pool.dart';
1212
import 'package:protobuf/protobuf.dart';
1313
import 'package:stream_channel/stream_channel.dart';
1414

15-
import 'dispatcher.dart';
15+
import 'compilation_dispatcher.dart';
1616
import 'embedded_sass.pb.dart';
1717
import 'reusable_isolate.dart';
1818
import 'util/proto_extensions.dart';
@@ -161,5 +161,5 @@ class IsolateDispatcher {
161161
}
162162

163163
void _isolateMain(Mailbox mailbox, SendPort sendPort) {
164-
Dispatcher(mailbox, sendPort).listen();
164+
CompilationDispatcher(mailbox, sendPort).listen();
165165
}

‎lib/src/embedded/logger.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import 'package:stack_trace/stack_trace.dart';
99
import '../logger.dart';
1010
import '../util/nullable.dart';
1111
import '../utils.dart';
12-
import 'dispatcher.dart';
12+
import 'compilation_dispatcher.dart';
1313
import 'embedded_sass.pb.dart' hide SourceSpan;
1414
import 'utils.dart';
1515

1616
/// A Sass logger that sends log messages as `LogEvent`s.
1717
final class EmbeddedLogger implements Logger {
18-
/// The [Dispatcher] to which to send events.
19-
final Dispatcher _dispatcher;
18+
/// The [CompilationDispatcher] to which to send events.
19+
final CompilationDispatcher _dispatcher;
2020

2121
/// Whether the formatted message should contain terminal colors.
2222
final bool _color;

‎lib/src/embedded/protofier.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import '../util/map.dart';
66
import '../util/nullable.dart';
77
import '../value.dart';
8-
import 'dispatcher.dart';
8+
import 'compilation_dispatcher.dart';
99
import 'embedded_sass.pb.dart' as proto;
1010
import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator;
1111
import 'host_callable.dart';
@@ -18,7 +18,7 @@ import 'utils.dart';
1818
/// custom function call.
1919
final class Protofier {
2020
/// The dispatcher, for invoking deprotofied [Value_HostFunction]s.
21-
final Dispatcher _dispatcher;
21+
final CompilationDispatcher _dispatcher;
2222

2323
/// The IDs of first-class functions.
2424
final OpaqueRegistry<SassFunction> _functions;

‎lib/src/extend/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# `@extend` Logic
2+
3+
This directory contains most of the logic for running Sass's `@extend` rule.
4+
This rule is probably the most complex corner of the Sass language, since it
5+
involves both understanding the semantics of selectors _and_ being able to
6+
combine them.
7+
8+
The high-level lifecycle of extensions is as follows:
9+
10+
1. When [the evaluator] encounters a style rule, it registers its selector in
11+
the [`ExtensionStore`] for the current module. This applies any extensions
12+
that have already been registered, then returns a _mutable_
13+
`Box<SelectorList>` that will get updated as extensions are applied.
14+
15+
[the evaluator]: ../visitor/async_evaluate.dart
16+
[`ExtensionStore`]: extension_store.dart
17+
18+
2. When the evaluator encounters an `@extend`, it registers that in the current
19+
module's `ExtensionStore` as well. This updates any selectors that have
20+
already been registered with that extension, _and_ updates the extension's
21+
own extender (the selector that gets injected when the extension is applied,
22+
which is stored along with the extension). Note that the extender has to be
23+
extended separately from the selector in the style rule, because the latter
24+
gets redundant selectors trimmed eagerly and the former does not.
25+
26+
3. When the entrypoint stylesheet has been fully executed, the evaluator
27+
determines which extensions are visible from which modules and adds
28+
extensions from one store to one another accordingly using
29+
`ExtensionStore.addExtensions()`.
30+
31+
Otherwise, the process of [extending a selector] as described in the Sass spec
32+
matches the logic here fairly closely. See `ExtensionStore._extendList()` for
33+
the primary entrypoint for that logic.
34+
35+
[extending a selector]: https://github.com/sass/sass/blob/main/spec/at-rules/extend.md#extending-a-selector

‎lib/src/functions/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Built-In Functions
2+
3+
This directory contains the standard functions that are built into Sass itself,
4+
both those that are available globally and those that are available only through
5+
built-in modules. Each of the files here exports a corresponding
6+
[`BuiltInModule`], and most define a list of global functions as well.
7+
8+
[`BuiltInModule`]: ../module/built_in.dart
9+
10+
There are a few functions that Sass supports that aren't defined here:
11+
12+
* The `if()` function is defined directly in the [`functions.dart`] file,
13+
although in most cases this is actually parsed as an [`IfExpression`] and
14+
handled directly by [the evaluator] since it has special behavior about when
15+
its arguments are evaluated. The function itself only exists for edge cases
16+
like `if(...$args)` or `meta.get-function("if")`.
17+
18+
[`functions.dart`]: ../functions.dart
19+
[`IfExpression`]: ../ast/sass/expression/if.dart
20+
[the evaluator]: ../visitor/async_evaluate.dart
21+
22+
* Certain functions in the `sass:meta` module require runtime information that's
23+
only available to the evaluator. These functions are defined in the evaluator
24+
itself so that they have access to its private variables.

‎lib/src/io/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Input/Output Shim
2+
3+
This directory contains an API shim for doing various forms of IO across
4+
different platforms. Dart chooses at compile time which of the three files to
5+
use:
6+
7+
* `interface.dart` is used by the Dart Analyzer for static checking. It defines
8+
the "expected" interface of the other two files, although there aren't strong
9+
checks that their interfaces are exactly the same.
10+
11+
* `vm.dart` is used by the Dart VM, and defines IO operations in terms of the
12+
`dart:io` library.
13+
14+
* `js.dart` is used by JS platforms. On Node.js, it will use Node's `fs` and
15+
`process` APIs for IO operations. On other JS platforms, most IO operations
16+
won't work at all, although messages will still be emitted with
17+
`console.log()` and `console.error()`.

‎lib/src/js/README.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# JavaScript API
2+
3+
This directory contains Dart Sass's implementation of the Sass JS API. Dart's JS
4+
interop support is primarily intended for _consuming_ JS libraries from Dart, so
5+
we have to jump through some hoops in order to effectively _produce_ a JS
6+
library with the desired API.
7+
8+
JS support has its own dedicated entrypoint in [`../js.dart`]. The [`cli_pkg`
9+
package] ensures that when users load Dart Sass _as a library_, this entrypoint
10+
is run instead of the CLI entrypoint, but otherwise it's up to us to set up the
11+
library appropriately. To do so, we use JS interop to define an [`Exports`]
12+
class that is in practice implemented by a CommonJS-like[^1] `exports` object,
13+
and then assign various values to this object.
14+
15+
[`../js.dart`]: ../js.dart
16+
[`cli_pkg` package]: https://github.com/google/dart_cli_pkg
17+
[`Exports`]: exports.dart
18+
19+
[^1]: It's not _literally_ CommonJS because it needs to run directly on browsers
20+
as well, but it's still an object named `exports` that we can hang names
21+
off of.
22+
23+
## Value Types
24+
25+
The JS API value types pose a particular challenge from Dart. Although every
26+
Dart class is represented by a JavaScript class when compiled to JS, Dart has no
27+
way of specifying what the JS API of those classes should be. What's more, in
28+
order to make the JS API as efficient as possible, we want to be able to pass
29+
the existing Dart [`Value`] objects as-is to custom functions rather than
30+
wrapping them with JS-only wrappers.
31+
32+
[`Value`]: ../value.dart
33+
34+
To solve the first problem, in [`reflection.dart`] we use JS interop to wrap the
35+
manual method of defining a JavaScript class. We use this to create a
36+
JS-specific class for each value type, with all the JS-specific methods and
37+
properties defined by Sass's JS API spec. However, while normal JS constructors
38+
just set some properties on `this`, our constructors for these classes return
39+
Dart `Value` objects instead.
40+
41+
[`reflection.dart`]: reflection.dart
42+
43+
"But wait," I hear you say, "those `Value` objects aren't instances of the new
44+
JS class you've created!" This is where the deep magic comes in. Once we've
45+
defined our class with its phony constructor, we create a single Dart object of
46+
the given `Value` subclass and _edit its JavaScript prototype chain_ to include
47+
the new class we just created. Once that's done, all the Dart value types will
48+
have exactly the right JS API (including responding correctly to `instanceof`!)
49+
and the constructor will now correctly return an instance of the JS class.
50+
51+
## Legacy API
52+
53+
Dart Sass also supports the legacy JS API in the [`legacy`] directory. This hews
54+
as close as possible to the API of the old `node-sass` package which wrapped the
55+
old LibSass implementation. It's no longer being actively updated, but we still
56+
need to support it at least until the next major version release of Dart Sass.
57+
58+
[`legacy`]: legacy

‎lib/src/parse/README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Sass Parser
2+
3+
This directory contains various parsers used by Sass. The two most relevant
4+
classes are:
5+
6+
* [`Parser`]: The base class of all other parsers, which includes basic
7+
infrastructure, utilities, and methods for parsing common CSS constructs that
8+
appear across multiple different specific parsers.
9+
10+
[`Parser`]: parser.dart
11+
12+
* [`StylesheetParser`]: The base class specifically for the initial stylesheet
13+
parse. Almost all of the logic for parsing Sass files, both statement- and
14+
expression-level, lives here. Only places where individual syntaxes differ
15+
from one another are left abstract or overridden by subclasses.
16+
17+
[`StylesheetParser`]: stylesheet.dart
18+
19+
All Sass parsing is done by hand using the [`string_scanner`] package, which we
20+
use to read the source [code-unit]-by-code-unit while also tracking source span
21+
information which we can then use to report errors and generate source maps. We
22+
don't use any kind of parser generator, partly because Sass's grammar requires
23+
arbitrary backtracking in various places and partly because handwritten code is
24+
often easier to read and debug.
25+
26+
[`string_scanner`]: https://pub.dev/packages/string_scanner
27+
[code-unit]: https://developer.mozilla.org/en-US/docs/Glossary/Code_unit
28+
29+
The parser is simple recursive descent. There's usually a method for each
30+
logical production that either consumes text and returns its corresponding AST
31+
node or throws an exception; in some cases, a method (conventionally beginning
32+
with `try`) will instead consume text and return a node if it matches and return
33+
null without consuming anything if it doesn't.

‎lib/src/value/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Value Types
2+
3+
This directory contains definitions for all the SassScript value types. These
4+
definitions are used both to represent SassScript values internally and in the
5+
public Dart API. They are usually produced by [the evaluator] as it evaluates
6+
the expression-level [Sass AST].
7+
8+
[the evaluator]: ../visitor/async_evaluate.dart
9+
[Sass AST]: ../ast/sass/README.md
10+
11+
Sass values are always immutable, even internally. Any changes to them must be
12+
done by creating a new value. In some cases, it's easiest to make a mutable
13+
copy, edit it, and then create a new immutable value from the result.

‎lib/src/visitor/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Visitors
2+
3+
This directory contains various types that implement the [visitor pattern] for
4+
[various ASTs]. A few of these, such as [the evaluator] and [the serializer],
5+
implement critical business logic for the Sass compiler. Most of the rest are
6+
either small utilities or base classes for small utilities that need to run over
7+
an AST to determine some kind of information about it. Some are even entirely
8+
unused within Sass itself, and exist only to support users of the [`sass_api`]
9+
package.
10+
11+
[visitor pattern]: https://en.wikipedia.org/wiki/Visitor_pattern
12+
[various ASTs]: ../ast
13+
[the evaluator]: async_evaluate.dart
14+
[the serializer]: serialize.dart
15+
[`sass_api`]: https://pub.dev/packages/sass_api

0 commit comments

Comments
 (0)
Please sign in to comment.