diff --git a/proposal/new-js-api.changes.md b/proposal/new-js-api.changes.md index 0630cf7b..ef01235e 100644 --- a/proposal/new-js-api.changes.md +++ b/proposal/new-js-api.changes.md @@ -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 diff --git a/proposal/new-js-api.d.ts b/proposal/new-js-api.d.ts index e68e2579..58493eaa 100644 --- a/proposal/new-js-api.d.ts +++ b/proposal/new-js-api.d.ts @@ -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'. diff --git a/spec/modules.md b/spec/modules.md index 0508d18c..52784b97 100644 --- a/spec/modules.md +++ b/spec/modules.md @@ -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`: diff --git a/spec/spec.md b/spec/spec.md index b42638d4..63c6f3ff 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -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 diff --git a/spec/syntax.md b/spec/syntax.md index 095a9056..590b02ac 100644 --- a/spec/syntax.md +++ b/spec/syntax.md @@ -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.