Specify the way importers integrate into loading source files (#3080)

Paves the way for #2509

Co-authored-by: Awjin Ahn <awjin@google.com>
This commit is contained in:
Natalie Weizenbaum 2021-06-16 16:24:05 -07:00 committed by GitHub
parent 03ee6aa95b
commit 3b3e27756b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 177 additions and 64 deletions

View File

@ -1,3 +1,7 @@
## Draft 2.1
* Minor adjustments to link up with updates in the main spec.
## Draft 2
* Rename `CompileResult.includedUrls` to `CompileResult.loadedUrls`. This is

View File

@ -1,5 +1,5 @@
/**
* # New JavaScript API: Draft 2
* # New JavaScript API: Draft 2.1
*
* *([Issue](https://github.com/sass/sass/issues/3056))*
*
@ -204,8 +204,8 @@ export interface CompileResult {
* Compiles the Sass file at `path`.
*
* To perform the compilation, the compiler executes the [compiling a path]
* procedure on `path`, with `options.loadPaths` as the import paths. The
* compiler must respect the configuration specified by the `options` object.
* procedure on `path`, with `options.loadPaths` as `load-paths`. The compiler
* must respect the configuration specified by the `options` object.
*
* [compiling a path]: ../spec/spec.md#compiling-a-path
*
@ -247,7 +247,7 @@ export function compileAsync(
*
* To perform the compilation, the compiler executes the [compiling a string]
* procedure as follows:
* - Use `options.loadPaths` as the import paths.
* - Use `options.loadPaths` as `load-paths`.
* - Use `options.source` as `string`.
* - Use `options.syntax` as `syntax`.
* - If `options.syntax` == 'sass', use 'indented'.

View File

@ -10,6 +10,9 @@
* [Module Graph](#module-graph)
* [Import Context](#import-context)
* [Built-In Module](#built-in-module)
* [Importer](#importer)
* [Filesystem Importer](#filesystem-importer)
* [Global Importer List](#global-importer-list)
* [Basename](#basename)
* [Dirname](#dirname)
* [Syntax](#syntax)
@ -17,6 +20,7 @@
* [Loading a Module](#loading-a-module)
* [Loading a Source File](#loading-a-source-file)
* [Resolving a `file:` URL](#resolving-a-file-url)
* [Resolving a `file:` URL for Extensions](#resolving-a-file-url-for-extensions)
* [Resolving a `file:` URL for Partials](#resolving-a-file-url-for-partials)
* [Resolving a Member](#resolving-a-member)
@ -147,6 +151,69 @@ outside the Sass compilation may not use the scheme `sass:`.
Built-in modules may contain mixins, variables, or functions, but they may never
contain CSS or extensions.
### Importer
An *importer* is a function that takes a string that may be either a relative or
absolute URL and returns three values: a string (the text of a stylesheet), a
syntax ("indented", "scss", or "css"), and an absolute URL (that
stylesheet's canonical URL). It may also return null to indicate that the
importer doesn't recognize the URL in question or cannot find a corresponding
stylesheet. If the URL is recognized but invalid, it should throw an error
rather than returning null. What constitutes "recognized" or "invalid" is left
up to the importer.
The details of an importer's behavior is typically defined by the end user in an
implementation-specific way. However, all importers must adhere to the following
contract:
* When the URL returned by an importer is passed back to that importer, it must
return the same result.
* The importer must return the same result for all URLs that refer to the same
file, although what specifically constitutes "the same file" is left up to the
importer.
> Importers are represented as a single function in the spec to simplify the
> writing of algorithms, but implementations are encouraged to have users
> instead define two separate functions: a `canonicalize()` function that
> converts an input string into a canonical URL, and a `load()` function that
> loads the contents of a canonical URL. This allows implementations to avoid
> the overhead of reloading the same file over and over.
### Filesystem Importer
A *filesystem importer* is an [importer](#importer) with an associated absolute
`file:` URL named `base`. When a filesystem importer is invoked with a string
named `string`:
* Let `url` be the result of [parsing `string` as a URL][parsing a URL] with
`base` as the base URL. If this returns a failure, throw that failure.
* If `url`'s scheme is not `file`, return null.
* Let `resolved` be the result of [resolving `url`](#resolving-a-file-url).
* If `resolved` is null, return null.
* Let `text` be the contents of the file at `resolved`.
* Let `syntax` be:
* "scss" if `url` ends in `.scss`.
* "indented" if `url` ends in `.sass`.
* "css" if `url` ends in `.css`.
> The algorithm for [resolving a `file:` URL](#resolving-a-file-url)
> guarantees that `url` will have one of these extensions.
* Return `text`, `syntax`, and `resolved`.
[parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser
### Global Importer List
The *global importer list* is a list of importers that's set for the entire
duration of a Sass compilation.
### Basename
The *basename* of a URL is the final component of that URL's path.
@ -213,66 +280,64 @@ This algorithm takes a string `argument` and [configuration](#configuration)
### Loading a Source File
This algorithm takes a string, `argument`, and returns either a [source file][]
or null.
This algorithm takes a string, `argument`, and returns either a [source file] or
null.
* If the scheme of the [current source file][]'s canonical URL is `file`:
* If `argument` is a relative URL:
[current source file]: spec.md#current-source-file
* Let `resolved` be the result of [parsing `argument` as a URL][parsing a URL]
with the [current source file]'s canonical URL as the base URL.
* Let `root` be the current source file's canonical URL.
* Let `result` be the result of passing `resolved` to the current source
file's [importer](#importer).
* Otherwise, let `root` be `null`.
* If `result` is not null:
* Let `bases` be a list beginning with `root` if it's non-null, followed by the
absolute `file:` URLs of all import paths.
* Let `ast` be the result of [parsing] `result`'s text as `result`'s syntax.
* For each `base` in `bases`:
* Return a source file with `ast` as its abstract syntax tree, `result`'s
URL as its canonical URL, and the current source file's importer as its
importer.
* Let `url` be the result of [parsing `argument` as a URL][] with `base` as
the base URL.
* For each `importer` in the [global importer list](#global-importer-list):
If this returns a failure, throw that failure.
* Let `result` be the result of passing `argument` to `importer`.
* If `url`'s scheme is not `file`, return null.
* If `result` is not null:
* Let `resolved` be the result of [resolving `url`](#resolving-a-file-url).
* Let `ast` be the result of [parsing] `result`'s text as `result`'s syntax.
* If `resolved` is null:
* Let `index` be [`dirname(url)`](#dirname) + `"index/"` +
[`basename(url)`](#basename).
* Set `resolved` to the result of [resolving
`index`](#resolving-a-file-url).
* If `resolved` is still null, continue to the next loop.
* Let `text` be the contents of the file at `resolved`.
* Let `ast` be:
* The result of parsing `text` as SCSS if `resolved` ends in `.scss`.
* The result of parsing `text` as the indented syntax if `resolved` ends in
`.sass`.
* The result of [parsing `text` as CSS][] if `resolved` ends in `.css`.
> The algorithm for [resolving a `file:` URL](#resolving-a-file-url)
> guarantees that `resolved` will have one of these extensions.
* Return a source file with `ast` as its abstract syntax tree and `resolved`
as its canonical URL.
[parsing `argument` as a URL]: https://url.spec.whatwg.org/#concept-url-parser
[parsing `text` as CSS]: syntax.md#parsing-text-as-css
* Return a source file with `ast` as its abstract syntax tree, `result`'s
URL as its canonical URL, and `importer` as its importer.
* Return null.
[current source file]: spec.md#current-source-file
[parsing]: syntax.md#parsing-text
### Resolving a `file:` URL
This algorithm takes a URL, `url`, whose scheme must be `file` and returns
either another URL that's guaranteed to point to a file on disk or null.
* Let `resolved` be the result of [resolving `url` for extensions][resolving for
extensions].
* If `resolved` is not null, return it. Otherwise:
* Let `index` be [`dirname(url)`](#dirname) + `"index/"` +
[`basename(url)`](#basename).
* Return the result of [resolving `index` for extensions][resolving for
extensions].
[resolving for extensions]: #resolving-a-file-url-for-extensions
### Resolving a `file:` URL for Extensions
This algorithm takes a URL, `url`, whose scheme must be `file` and returns
either another URL that's guaranteed to point to a file on disk or null.
* If `url` ends in `.scss`, `.sass`, or `.css`:
* If this algorithm is being run for an `@import`:

View File

@ -71,7 +71,9 @@ invocation of [Executing a File](#executing-a-file).
> This an entrypoint to the specification; it's up to each implementation how it
> exposes this to the user.
This algorithm takes a local filesystem path `path` and returns a string.
This algorithm takes a local filesystem path `path`, an optional list of
[importers] `importers`, and an optional list of paths `load-paths`. It returns
a string.
* Let `text` be the result of decoding the binary contents of the file at
`path`.
@ -84,35 +86,57 @@ This algorithm takes a local filesystem path `path` and returns a string.
* Let `url` be the absolute `file:` URL corresponding to `path`.
* Return the result of [compiling](#compiling-a-string) `text` with `syntax` and
`url`.
* Let `importer` be a [filesystem importer] with an arbitrary `base`.
> This importer will only ever be passed absolute URLs, so its base won't
> matter.
* Return the result of [compiling](#compiling-a-string) `text` with `syntax`,
`url`, `importer`, `importers`, and `load-paths`.
[importers]: modules.md#importer
### Compiling a String
> This an entrypoint to the specification; it's up to each implementation how it
> exposes this to the user.
This algorithm takes a string `string`, a syntax `syntax` ("indented", "scss",
or "css"), and an optional URL `url`,
This algorithm takes:
* a string `string`,
* a syntax `syntax` ("indented", "scss", or "css"),
* an optional URL `url`,
* an optional [importer] `importer`,
* an optional list of importers `importers`,
* and an optional list of paths `load-paths`.
* Let `ast` be:
[importer]: modules.md#importer
* The result of parsing `text` as the indented syntax if `syntax` is
"indented".
It runs as follows:
* The result of [parsing `text` as CSS][] if `syntax` is "css".
* Set the [global importer list] to `importers`.
* The result of parsing `text` as SCSS otherwise.
* For each `path` in `load-paths`:
[parsing `text` as CSS]: syntax.md#parsing-text-as-css
* Let `base` be the absolute `file:` URL that refers to `path`.
* If `url` is null, set it to a unique value.
* Add a [filesystem importer] with base `base` to the global importer list.
> This ensures that all source files have a valid URL. When displaying this
> value, implementations should help users understand the source of the string
> if possible.
* Let `ast` be the result of [parsing] `text` as `syntax`.
* Let `file` be the [source file][] with `ast` and canonical URL `url`.
* If `url` is null:
* If `importer` is not null, throw an error.
* Set `url` to a unique value.
> This ensures that all source files have a valid URL. When displaying this
> value, implementations should help users understand the source of the string
> if possible.
* If `importer` is null, set it to a function that always returns null.
* Let `file` be the [source file][] with `ast`, canonical URL `url`, and
importer `importer`.
[source file]: syntax.md#source-file
@ -124,6 +148,10 @@ or "css"), and an optional URL `url`,
* Return the result of converting `css` to a CSS string.
[filesystem importer]: modules.md#filesystem-importer
[parsing]: syntax.md#parsing-text
[global importer list]: modules.md#global-importer-list
### Executing a File
This algorithm takes a [source file][] `file`, a [configuration][] `config`, an

View File

@ -12,6 +12,7 @@
* [`MinMaxExpression`](#minmaxexpression)
* [`PseudoSelector`](#pseudoselector)
* [Procedures](#procedures)
* [Parsing Text](#parsing-text)
* [Parsing Text as CSS](#parsing-text-as-css)
* [Consuming an Identifier](#consuming-an-identifier)
* [Consuming an Interpolated Identifier](#consuming-an-interpolated-identifier)
@ -24,9 +25,11 @@
### Source File
A *source file* is a Sass abstract syntax tree along with an absolute URL, known
as that file's *canonical URL*. A given canonical URL cannot be associated with
more than one source file.
A *source file* is a Sass abstract syntax tree along with; an absolute URL, known as
that file's *canonical URL*; and an [importer]. A given canonical URL cannot be
associated with more than one source file.
[importer]: modules.md#importer
### Vendor Prefix
@ -128,6 +131,19 @@ parentheses.
## Procedures
### Parsing Text
This algorithm takes a string `text` and a syntax `syntax` ("indented", "scss",
or "sass"), and returns a Sass abstract syntax tree.
* If `syntax` is "indented", return the result of parsing `text` as the indented
syntax.
* If `syntax` is "css", return the result of [parsing `text` as
CSS](#parsing-text-as-css).
* If `syntax` is "scss", return the result of parsing `text` as SCSS.
### Parsing Text as CSS
This algorithm takes a string, `text`, and returns a Sass abstract syntax tree.