mirror of https://github.com/flutter/samples.git
deploy: 2f370275ad
parent
d25ae63c92
commit
3300a647bf
File diff suppressed because one or more lines are too long
@ -0,0 +1,230 @@
|
||||
# The Sass Compiler
|
||||
|
||||
* [Life of a Compilation](#life-of-a-compilation)
|
||||
* [Late Parsing](#late-parsing)
|
||||
* [Early Serialization](#early-serialization)
|
||||
* [JS Support](#js-support)
|
||||
* [APIs](#apis)
|
||||
* [Importers](#importers)
|
||||
* [Custom Functions](#custom-functions)
|
||||
* [Loggers](#loggers)
|
||||
* [Built-In Functions](#built-in-functions)
|
||||
* [`@extend`](#extend)
|
||||
|
||||
This is the root directory of Dart Sass's private implementation libraries. This
|
||||
contains essentially all the business logic defining how Sass is actually
|
||||
compiled, as well as the APIs that users use to interact with Sass. There are
|
||||
two exceptions:
|
||||
|
||||
* [`../../bin/sass.dart`] is the entrypoint for the Dart Sass CLI (on all
|
||||
platforms). While most of the logic it runs exists in this directory, it does
|
||||
contain some logic to drive the basic compilation logic and handle errors. All
|
||||
the most complex parts of the CLI, such as option parsing and the `--watch`
|
||||
command, are handled in the [`executable`] directory. Even Embedded Sass runs
|
||||
through this entrypoint, although it gets immediately gets handed off to [the
|
||||
embedded compiler].
|
||||
|
||||
[`../../bin/sass.dart`]: ../../bin/sass.dart
|
||||
[`executable`]: executable
|
||||
[the embedded compiler]: embedded/README.md
|
||||
|
||||
* [`../sass.dart`] is the entrypoint for the public Dart API. This is what's
|
||||
loaded when a Dart package imports Sass. It just contains the basic
|
||||
compilation functions, and exports the rest of the public APIs from this
|
||||
directory.
|
||||
|
||||
[`../sass.dart`]: ../sass.dart
|
||||
|
||||
Everything else is contained here, and each file and some subdirectories have
|
||||
their own documentation. But before you dive into those, let's take a look at
|
||||
the general lifecycle of a Sass compilation.
|
||||
|
||||
## Life of a Compilation
|
||||
|
||||
Whether it's invoked through the Dart API, the JS API, the CLI, or the embedded
|
||||
host, the basic process of a Sass compilation is the same. Sass is implemented
|
||||
as an AST-walking [interpreter] that operates in roughly three passes:
|
||||
|
||||
[interpreter]: https://en.wikipedia.org/wiki/Interpreter_(computing)
|
||||
|
||||
1. **Parsing**. The first step of a Sass compilation is always to parse the
|
||||
source file, whether it's SCSS, the indented syntax, or CSS. The parsing
|
||||
logic lives in the [`parse`] directory, while the abstract syntax tree that
|
||||
represents the parsed file lives in [`ast/sass`].
|
||||
|
||||
[`parse`]: parse/README.md
|
||||
[`ast/sass`]: ast/sass/README.md
|
||||
|
||||
2. **Evaluation**. Once a Sass file is parsed, it's evaluated by
|
||||
[`visitor/async_evaluate.dart`]. (Why is there both an async and a sync
|
||||
version of this file? See [Synchronizing] for details!) The evaluator handles
|
||||
all the Sass-specific logic: it resolves variables, includes mixins, executes
|
||||
control flow, and so on. As it goes, it builds up a new AST that represents
|
||||
the plain CSS that is the compilation result, which is defined in
|
||||
[`ast/css`].
|
||||
|
||||
[`visitor/async_evaluate.dart`]: visitor/async_evaluate.dart
|
||||
[Synchronizing]: ../../CONTRIBUTING.md#synchronizing
|
||||
[`ast/css`]: ast/css/README.md
|
||||
|
||||
Sass evaluation is almost entirely linear: it begins at the first statement
|
||||
of the file, evaluates it (which may involve evaluating its nested children),
|
||||
adds its result to the CSS AST, and then moves on to the second statement. On
|
||||
it goes until it reaches the end of the file, at which point it's done. The
|
||||
only exception is module resolution: every Sass module has its own compiled
|
||||
CSS AST, and once the entrypoint file is done compiling the evaluator will go
|
||||
back through these modules, resolve `@extend`s across them as necessary, and
|
||||
stitch them together into the final stylesheet.
|
||||
|
||||
SassScript, the expression-level syntax, is handled by the same evaluator.
|
||||
The main difference between SassScript and statement-level evaluation is that
|
||||
the same SassScript values are used during evaluation _and_ as part of the
|
||||
CSS AST. This means that it's possible to end up with a Sass-specific value,
|
||||
such as a map or a first-class function, as the value of a CSS declaration.
|
||||
If that happens, the Serialization phase will signal an error when it
|
||||
encounters the invalid value.
|
||||
|
||||
3. **Serialization**. Once we have the CSS AST that represents the compiled
|
||||
stylesheet, we need to convert it into actual CSS text. This is done by
|
||||
[`visitor/serialize.dart`], which walks the AST and builds up a big buffer of
|
||||
the resulting CSS. It uses [a special string buffer] that tracks source and
|
||||
destination locations in order to generate [source maps] as well.
|
||||
|
||||
[`visitor/serialize.dart`]: visitor/serialize.dart
|
||||
[a special string buffer]: util/source_map_buffer.dart
|
||||
[source maps]: https://web.dev/source-maps/
|
||||
|
||||
There's actually one slight complication here: the first and second pass aren't
|
||||
as separate as they appear. When one Sass stylesheet loads another with `@use`,
|
||||
`@forward`, or `@import`, that rule is handled by the evaluator and _only at
|
||||
that point_ is the loaded file parsed. So in practice, compilation actually
|
||||
switches between parsing and evaluation, although each individual stylesheet
|
||||
naturally has to be parsed before it can be evaluated.
|
||||
|
||||
### Late Parsing
|
||||
|
||||
Some syntax within a stylesheet is only parsed _during_ evaluation. This allows
|
||||
authors to use `#{}` interpolation to inject Sass variables and other dynamic
|
||||
values into various locations, such as selectors, while still allowing Sass to
|
||||
parse them to support features like nesting and `@extend`. The following
|
||||
syntaxes are parsed during evaluation:
|
||||
|
||||
* [Selectors](parse/selector.dart)
|
||||
* [`@keyframes` frames](parse/keyframe_selector.dart)
|
||||
* [Media queries](parse/media_query.dart) (for historical reasons, these are
|
||||
parsed before evaluation and then _reparsed_ after they've been fully
|
||||
evaluated)
|
||||
|
||||
### Early Serialization
|
||||
|
||||
There are also some cases where the evaluator can serialize values before the
|
||||
main serialization pass. For example, if you inject a variable into a selector
|
||||
using `#{}`, that variable's value has to be converted to a string during
|
||||
evaluation so that the evaluator can then parse and handle the newly-generated
|
||||
selector. The evaluator does this by invoking the serializer _just_ for that
|
||||
specific value. As a rule of thumb, this happens anywhere interpolation is used
|
||||
in the original stylesheet, although there are a few other circumstances as
|
||||
well.
|
||||
|
||||
## JS Support
|
||||
|
||||
One of the main benefits of Dart as an implementation language is that it allows
|
||||
us to distribute Dart Sass both as an extremely efficient stand-alone executable
|
||||
_and_ an easy-to-install pure-JavaScript package, using the dart2js compilation
|
||||
tool. However, properly supporting JS isn't seamless. There are two major places
|
||||
where we need to think about JS support:
|
||||
|
||||
1. When interfacing with the filesystem. None of Dart's IO APIs are natively
|
||||
supported on JS, so for anything that needs to work on both the Dart VM _and_
|
||||
Node.js we define a shim in the [`io`] directory that will be implemented in
|
||||
terms of `dart:io` if we're running on the Dart VM or the `fs` or `process`
|
||||
modules if we're running on Node. (We don't support IO at all on the browser
|
||||
except to print messages to the console.)
|
||||
|
||||
[`io`]: io/README.md
|
||||
|
||||
2. When exposing an API. Dart's JS interop is geared towards _consuming_ JS
|
||||
libraries from Dart, not producing a JS library written in Dart, so we have
|
||||
to jump through some hoops to make it work. This is all handled in the [`js`]
|
||||
directory.
|
||||
|
||||
[`js`]: js/README.md
|
||||
|
||||
## APIs
|
||||
|
||||
One of Sass's core features is its APIs, which not only compile stylesheets but
|
||||
also allow users to provide plugins that can be invoked from within Sass. In
|
||||
both the JS API, the Dart API, and the embedded compiler, Sass provides three
|
||||
types of plugins: importers, custom functions, and loggers.
|
||||
|
||||
### Importers
|
||||
|
||||
Importers control how Sass loads stylesheets through `@use`, `@forward`, and
|
||||
`@import`. Internally, _all_ stylesheet loads are modeled as importers. When a
|
||||
user passes a load path to an API or compiles a stylesheet through the CLI, we
|
||||
just use the built-in [`FilesystemImporter`] which implements the same interface
|
||||
that we make available to users.
|
||||
|
||||
[`FilesystemImporter`]: importer/filesystem.dart
|
||||
|
||||
In the Dart API, the importer root class is [`importer/async_importer.dart`].
|
||||
The JS API and the embedded compiler wrap the Dart importer API in
|
||||
[`importer/node_to_dart`] and [`embedded/importer`] respectively.
|
||||
|
||||
[`importer/async_importer.dart`]: importer/async_importer.dart
|
||||
[`importer/node_to_dart`]: importer/node_to_dart
|
||||
[`embedded/importer`]: embedded/importer
|
||||
|
||||
### Custom Functions
|
||||
|
||||
Custom functions are defined by users of the Sass API but invoked by Sass
|
||||
stylesheets. To a Sass stylesheet, they look like any other built-in function:
|
||||
users pass SassScript values to them and get SassScript values back. In fact,
|
||||
all the core Sass functions are implemented using the Dart custom function API.
|
||||
|
||||
Because custom functions take and return SassScript values, that means we need
|
||||
to make _all_ values available to the various APIs. For Dart, this is
|
||||
straightforward: we need to have objects to represent those values anyway, so we
|
||||
just expose those objects publicly (with a few `@internal` annotations here and
|
||||
there to hide APIs we don't want users relying on). These value types live in
|
||||
the [`value`] directory.
|
||||
|
||||
[`value`]: value/README.md
|
||||
|
||||
Exposing values is a bit more complex for other platforms. For the JS API, we do
|
||||
a bit of metaprogramming in [`js/value`] so that we can return the
|
||||
same Dart values we use internally while still having them expose a JS API that
|
||||
feels native to that language. For the embedded host, we convert them to and
|
||||
from a protocol buffer representation in [`embedded/protofier.dart`].
|
||||
|
||||
[`js/value`]: js/value/README.md
|
||||
[`embedded/value.dart`]: embedded/value.dart
|
||||
|
||||
### Loggers
|
||||
|
||||
Loggers are the simplest of the plugins. They're just callbacks that are invoked
|
||||
any time Dart Sass would emit a warning (from the language or from `@warn`) or a
|
||||
debug message from `@debug`. They're defined in:
|
||||
|
||||
* [`logger.dart`](logger.dart) for Dart
|
||||
* [`js/logger.dart`](js/logger.dart) for Node
|
||||
* [`embedded/logger.dart`](embedded/logger.dart) for the embedded compiler
|
||||
|
||||
## Built-In Functions
|
||||
|
||||
All of Sass's built-in functions are defined in the [`functions`] directory,
|
||||
including both global functions and functions defined in core modules like
|
||||
`sass:math`. As mentioned before, these are defined using the standard custom
|
||||
function API, although in a few cases they use additional private features like
|
||||
the ability to define multiple overloads of the same function name.
|
||||
|
||||
[`functions`]: functions/README.md
|
||||
|
||||
## `@extend`
|
||||
|
||||
The logic for Sass's `@extend` rule is particularly complex, since it requires
|
||||
Sass to not only parse selectors but to understand how to combine them and when
|
||||
they can be safely optimized away. Most of the logic for this is contained
|
||||
within the [`extend`] directory.
|
||||
|
||||
[`extend`]: extend/README.md
|
@ -0,0 +1,52 @@
|
||||
# CSS Abstract Syntax Tree
|
||||
|
||||
This directory contains the abstract syntax tree that represents a plain CSS
|
||||
file generated by Sass compilation. It differs from other Sass ASTs in two major
|
||||
ways:
|
||||
|
||||
1. Instead of being created by [a parser], it's created by [the evaluator] as it
|
||||
traverses the [Sass AST].
|
||||
|
||||
[a parser]: ../../parse/README.md
|
||||
[the evaluator]: ../../visitor/async_evaluate.dart
|
||||
[Sass AST]: ../sass/README.md
|
||||
|
||||
2. Because of various Sass features like `@extend` and at-rule hoisting, the CSS
|
||||
AST is mutable even though all other ASTs are immutable.
|
||||
|
||||
**Note:** the CSS AST doesn't have its own representation of declaration values.
|
||||
Instead, declaration values are represented as [`Value`] objects. This does mean
|
||||
that a CSS AST can be in a state where some of its values aren't representable
|
||||
in plain CSS (such as maps)—in this case, [the serializer] will emit an error.
|
||||
|
||||
[`Value`]: ../../value/README.md
|
||||
[the serializer]: ../../visitor/serialize.dart
|
||||
|
||||
## Mutable and Immutable Views
|
||||
|
||||
Internally, the CSS AST is mutable to allow for operations like hoisting rules
|
||||
to the root of the AST and updating existing selectors when `@extend` rules are
|
||||
encountered. However, because mutability poses a high risk for "spooky [action
|
||||
at a distance]", we limit access to mutating APIs exclusively to the evaluator.
|
||||
|
||||
[action at a distance]: https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)
|
||||
|
||||
We do this by having an _unmodifiable_ interface (written in this directory) for
|
||||
each CSS AST node which only exposes members that don't modify the node in
|
||||
question. The implementations of those interfaces, which _do_ have modifying
|
||||
methods, live in the [`modifiable`] directory. We then universally refer to the
|
||||
immutable node interfaces except specifically in the evaluator, and the type
|
||||
system automatically ensures we don't accidentally mutate anything we don't
|
||||
intend to.
|
||||
|
||||
[`modifiable`]: modifiable
|
||||
|
||||
(Of course, it's always possible to cast an immutable node type to a mutable
|
||||
one, but that's a very clear code smell that a reviewer can easily identify.)
|
||||
|
||||
## CSS Source Files
|
||||
|
||||
A lesser-known fact about Sass is that it actually supports _three_ syntaxes for
|
||||
its source files: SCSS, the indented syntax, and plain CSS. But even when it
|
||||
parses plain CSS, it uses the Sass AST rather than the CSS AST to represent it
|
||||
so that parsing logic can easily be shared with the other stylesheet parsers.
|
@ -0,0 +1,34 @@
|
||||
# Sass Abstract Syntax Tree
|
||||
|
||||
This directory contains the abstract syntax tree that represents a Sass source
|
||||
file, regardless of which syntax it was written in (SCSS, the indented syntax,
|
||||
or plain CSS). The AST is constructed recursively by [a parser] from the leaf
|
||||
nodes in towards the root, which allows it to be fully immutable.
|
||||
|
||||
[a parser]: ../../parse/README.md
|
||||
|
||||
The Sass AST is broken up into three categories:
|
||||
|
||||
1. The [statement AST], which represents statement-level constructs like
|
||||
variable assignments, style rules, and at-rules.
|
||||
|
||||
[statement AST]: statement
|
||||
|
||||
2. The [expression AST], which represents SassScript expressions like function
|
||||
calls, operations, and value literals.
|
||||
|
||||
[expression AST]: exprssion
|
||||
|
||||
3. Miscellaneous AST nodes that are used by both statements and expressions or
|
||||
don't fit cleanly into either category that live directly in this directory.
|
||||
|
||||
The Sass AST nodes are processed (usually from the root [`Stylesheet`]) by [the
|
||||
evaluator], which runs the logic they encode and builds up a [CSS AST] that
|
||||
represents the compiled stylesheet. They can also be transformed back into Sass
|
||||
source using the `toString()` method. Since this is only ever used for debugging
|
||||
and doesn't need configuration or full-featured indentation tracking, it doesn't
|
||||
use a full visitor.
|
||||
|
||||
[`Stylesheet`]: statement/stylesheet.dart
|
||||
[the evaluator]: ../../visitor/async_evaluate.dart
|
||||
[CSS AST]: ../css/README.md
|
@ -0,0 +1,24 @@
|
||||
# Selector Abstract Syntax Tree
|
||||
|
||||
This directory contains the abstract syntax tree that represents a parsed CSS
|
||||
selector. This AST is constructed recursively by [the selector parser]. It's
|
||||
fully immutable.
|
||||
|
||||
[the selector parser]: ../../parse/selector.dart
|
||||
|
||||
Unlike the [Sass AST], which is parsed from a raw source string before being
|
||||
evaluated, the selector AST is parsed _during evaluation_. This is necessary to
|
||||
ensure that there's a chance to resolve interpolation before fully parsing the
|
||||
selectors in question.
|
||||
|
||||
[Sass AST]: ../sass/README.md
|
||||
|
||||
Although this AST doesn't include any SassScript, it _does_ include a few
|
||||
Sass-specific constructs: the [parent selector] `&` and [placeholder selectors].
|
||||
Parent selectors are resolved by [the evaluator] before it hands the AST off to
|
||||
[the serializer], while placeholders are omitted in the serializer itself.
|
||||
|
||||
[parent selector]: parent.dart
|
||||
[placeholder selectors]: placeholder.dart
|
||||
[the evaluator]: ../../visitor/async_evaluate.dart
|
||||
[the serializer]: ../../visitor/serialize.dart
|
@ -0,0 +1,28 @@
|
||||
# Embedded Sass Compiler
|
||||
|
||||
This directory contains the Dart Sass embedded compiler. This is a special mode
|
||||
of the Dart Sass command-line executable, only supported on the Dart VM, in
|
||||
which it uses stdin and stdout to communicate with another endpoint, the
|
||||
"embedded host", using a protocol buffer-based protocol. See [the embedded
|
||||
protocol specification] for details.
|
||||
|
||||
[the embedded protocol specification]: https://github.com/sass/sass/blob/main/spec/embedded-protocol.md
|
||||
|
||||
The embedded compiler has two different levels of dispatchers for handling
|
||||
incoming messages from the embedded host:
|
||||
|
||||
1. The [`IsolateDispatcher`] is the first recipient of each packet. It decodes
|
||||
the packets _just enough_ to determine which compilation they belong to, and
|
||||
forwards them to the appropriate compilation dispatcher. It also parses and
|
||||
handles messages that aren't compilation specific, such as `VersionRequest`.
|
||||
|
||||
[`IsolateDispatcher`]: isolate_dispatcher.dart
|
||||
|
||||
2. The [`CompilationDispatcher`] fully parses and handles messages for a single
|
||||
compilation. Each `CompilationDispatcher` runs in a separate isolate so that
|
||||
the embedded compiler can run multiple compilations in parallel.
|
||||
|
||||
[`CompilationDispatcher`]: compilation_dispatcher.dart
|
||||
|
||||
Otherwise, most of the code in this directory just wraps Dart APIs to
|
||||
communicate with their protocol buffer equivalents.
|
@ -0,0 +1,35 @@
|
||||
# `@extend` Logic
|
||||
|
||||
This directory contains most of the logic for running Sass's `@extend` rule.
|
||||
This rule is probably the most complex corner of the Sass language, since it
|
||||
involves both understanding the semantics of selectors _and_ being able to
|
||||
combine them.
|
||||
|
||||
The high-level lifecycle of extensions is as follows:
|
||||
|
||||
1. When [the evaluator] encounters a style rule, it registers its selector in
|
||||
the [`ExtensionStore`] for the current module. This applies any extensions
|
||||
that have already been registered, then returns a _mutable_
|
||||
`Box<SelectorList>` that will get updated as extensions are applied.
|
||||
|
||||
[the evaluator]: ../visitor/async_evaluate.dart
|
||||
[`ExtensionStore`]: extension_store.dart
|
||||
|
||||
2. When the evaluator encounters an `@extend`, it registers that in the current
|
||||
module's `ExtensionStore` as well. This updates any selectors that have
|
||||
already been registered with that extension, _and_ updates the extension's
|
||||
own extender (the selector that gets injected when the extension is applied,
|
||||
which is stored along with the extension). Note that the extender has to be
|
||||
extended separately from the selector in the style rule, because the latter
|
||||
gets redundant selectors trimmed eagerly and the former does not.
|
||||
|
||||
3. When the entrypoint stylesheet has been fully executed, the evaluator
|
||||
determines which extensions are visible from which modules and adds
|
||||
extensions from one store to one another accordingly using
|
||||
`ExtensionStore.addExtensions()`.
|
||||
|
||||
Otherwise, the process of [extending a selector] as described in the Sass spec
|
||||
matches the logic here fairly closely. See `ExtensionStore._extendList()` for
|
||||
the primary entrypoint for that logic.
|
||||
|
||||
[extending a selector]: https://github.com/sass/sass/blob/main/spec/at-rules/extend.md#extending-a-selector
|
@ -0,0 +1,24 @@
|
||||
# Built-In Functions
|
||||
|
||||
This directory contains the standard functions that are built into Sass itself,
|
||||
both those that are available globally and those that are available only through
|
||||
built-in modules. Each of the files here exports a corresponding
|
||||
[`BuiltInModule`], and most define a list of global functions as well.
|
||||
|
||||
[`BuiltInModule`]: ../module/built_in.dart
|
||||
|
||||
There are a few functions that Sass supports that aren't defined here:
|
||||
|
||||
* The `if()` function is defined directly in the [`functions.dart`] file,
|
||||
although in most cases this is actually parsed as an [`IfExpression`] and
|
||||
handled directly by [the evaluator] since it has special behavior about when
|
||||
its arguments are evaluated. The function itself only exists for edge cases
|
||||
like `if(...$args)` or `meta.get-function("if")`.
|
||||
|
||||
[`functions.dart`]: ../functions.dart
|
||||
[`IfExpression`]: ../ast/sass/expression/if.dart
|
||||
[the evaluator]: ../visitor/async_evaluate.dart
|
||||
|
||||
* Certain functions in the `sass:meta` module require runtime information that's
|
||||
only available to the evaluator. These functions are defined in the evaluator
|
||||
itself so that they have access to its private variables.
|
@ -0,0 +1,17 @@
|
||||
# Input/Output Shim
|
||||
|
||||
This directory contains an API shim for doing various forms of IO across
|
||||
different platforms. Dart chooses at compile time which of the three files to
|
||||
use:
|
||||
|
||||
* `interface.dart` is used by the Dart Analyzer for static checking. It defines
|
||||
the "expected" interface of the other two files, although there aren't strong
|
||||
checks that their interfaces are exactly the same.
|
||||
|
||||
* `vm.dart` is used by the Dart VM, and defines IO operations in terms of the
|
||||
`dart:io` library.
|
||||
|
||||
* `js.dart` is used by JS platforms. On Node.js, it will use Node's `fs` and
|
||||
`process` APIs for IO operations. On other JS platforms, most IO operations
|
||||
won't work at all, although messages will still be emitted with
|
||||
`console.log()` and `console.error()`.
|
@ -0,0 +1,58 @@
|
||||
# JavaScript API
|
||||
|
||||
This directory contains Dart Sass's implementation of the Sass JS API. Dart's JS
|
||||
interop support is primarily intended for _consuming_ JS libraries from Dart, so
|
||||
we have to jump through some hoops in order to effectively _produce_ a JS
|
||||
library with the desired API.
|
||||
|
||||
JS support has its own dedicated entrypoint in [`../js.dart`]. The [`cli_pkg`
|
||||
package] ensures that when users load Dart Sass _as a library_, this entrypoint
|
||||
is run instead of the CLI entrypoint, but otherwise it's up to us to set up the
|
||||
library appropriately. To do so, we use JS interop to define an [`Exports`]
|
||||
class that is in practice implemented by a CommonJS-like[^1] `exports` object,
|
||||
and then assign various values to this object.
|
||||
|
||||
[`../js.dart`]: ../js.dart
|
||||
[`cli_pkg` package]: https://github.com/google/dart_cli_pkg
|
||||
[`Exports`]: exports.dart
|
||||
|
||||
[^1]: It's not _literally_ CommonJS because it needs to run directly on browsers
|
||||
as well, but it's still an object named `exports` that we can hang names
|
||||
off of.
|
||||
|
||||
## Value Types
|
||||
|
||||
The JS API value types pose a particular challenge from Dart. Although every
|
||||
Dart class is represented by a JavaScript class when compiled to JS, Dart has no
|
||||
way of specifying what the JS API of those classes should be. What's more, in
|
||||
order to make the JS API as efficient as possible, we want to be able to pass
|
||||
the existing Dart [`Value`] objects as-is to custom functions rather than
|
||||
wrapping them with JS-only wrappers.
|
||||
|
||||
[`Value`]: ../value.dart
|
||||
|
||||
To solve the first problem, in [`reflection.dart`] we use JS interop to wrap the
|
||||
manual method of defining a JavaScript class. We use this to create a
|
||||
JS-specific class for each value type, with all the JS-specific methods and
|
||||
properties defined by Sass's JS API spec. However, while normal JS constructors
|
||||
just set some properties on `this`, our constructors for these classes return
|
||||
Dart `Value` objects instead.
|
||||
|
||||
[`reflection.dart`]: reflection.dart
|
||||
|
||||
"But wait," I hear you say, "those `Value` objects aren't instances of the new
|
||||
JS class you've created!" This is where the deep magic comes in. Once we've
|
||||
defined our class with its phony constructor, we create a single Dart object of
|
||||
the given `Value` subclass and _edit its JavaScript prototype chain_ to include
|
||||
the new class we just created. Once that's done, all the Dart value types will
|
||||
have exactly the right JS API (including responding correctly to `instanceof`!)
|
||||
and the constructor will now correctly return an instance of the JS class.
|
||||
|
||||
## Legacy API
|
||||
|
||||
Dart Sass also supports the legacy JS API in the [`legacy`] directory. This hews
|
||||
as close as possible to the API of the old `node-sass` package which wrapped the
|
||||
old LibSass implementation. It's no longer being actively updated, but we still
|
||||
need to support it at least until the next major version release of Dart Sass.
|
||||
|
||||
[`legacy`]: legacy
|
@ -0,0 +1,33 @@
|
||||
# Sass Parser
|
||||
|
||||
This directory contains various parsers used by Sass. The two most relevant
|
||||
classes are:
|
||||
|
||||
* [`Parser`]: The base class of all other parsers, which includes basic
|
||||
infrastructure, utilities, and methods for parsing common CSS constructs that
|
||||
appear across multiple different specific parsers.
|
||||
|
||||
[`Parser`]: parser.dart
|
||||
|
||||
* [`StylesheetParser`]: The base class specifically for the initial stylesheet
|
||||
parse. Almost all of the logic for parsing Sass files, both statement- and
|
||||
expression-level, lives here. Only places where individual syntaxes differ
|
||||
from one another are left abstract or overridden by subclasses.
|
||||
|
||||
[`StylesheetParser`]: stylesheet.dart
|
||||
|
||||
All Sass parsing is done by hand using the [`string_scanner`] package, which we
|
||||
use to read the source [code-unit]-by-code-unit while also tracking source span
|
||||
information which we can then use to report errors and generate source maps. We
|
||||
don't use any kind of parser generator, partly because Sass's grammar requires
|
||||
arbitrary backtracking in various places and partly because handwritten code is
|
||||
often easier to read and debug.
|
||||
|
||||
[`string_scanner`]: https://pub.dev/packages/string_scanner
|
||||
[code-unit]: https://developer.mozilla.org/en-US/docs/Glossary/Code_unit
|
||||
|
||||
The parser is simple recursive descent. There's usually a method for each
|
||||
logical production that either consumes text and returns its corresponding AST
|
||||
node or throws an exception; in some cases, a method (conventionally beginning
|
||||
with `try`) will instead consume text and return a node if it matches and return
|
||||
null without consuming anything if it doesn't.
|
@ -0,0 +1,13 @@
|
||||
# Value Types
|
||||
|
||||
This directory contains definitions for all the SassScript value types. These
|
||||
definitions are used both to represent SassScript values internally and in the
|
||||
public Dart API. They are usually produced by [the evaluator] as it evaluates
|
||||
the expression-level [Sass AST].
|
||||
|
||||
[the evaluator]: ../visitor/async_evaluate.dart
|
||||
[Sass AST]: ../ast/sass/README.md
|
||||
|
||||
Sass values are always immutable, even internally. Any changes to them must be
|
||||
done by creating a new value. In some cases, it's easiest to make a mutable
|
||||
copy, edit it, and then create a new immutable value from the result.
|
@ -0,0 +1,15 @@
|
||||
# Visitors
|
||||
|
||||
This directory contains various types that implement the [visitor pattern] for
|
||||
[various ASTs]. A few of these, such as [the evaluator] and [the serializer],
|
||||
implement critical business logic for the Sass compiler. Most of the rest are
|
||||
either small utilities or base classes for small utilities that need to run over
|
||||
an AST to determine some kind of information about it. Some are even entirely
|
||||
unused within Sass itself, and exist only to support users of the [`sass_api`]
|
||||
package.
|
||||
|
||||
[visitor pattern]: https://en.wikipedia.org/wiki/Visitor_pattern
|
||||
[various ASTs]: ../ast
|
||||
[the evaluator]: async_evaluate.dart
|
||||
[the serializer]: serialize.dart
|
||||
[`sass_api`]: https://pub.dev/packages/sass_api
|
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue