Skip to content

Commit

Permalink
Clarify that base importer resolution DOESN'T require a URL
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Apr 9, 2024
1 parent b558815 commit 5d254fe
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 132 deletions.
8 changes: 0 additions & 8 deletions EMBEDDED_PROTOCOL_CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
## 2.6.1

* Clarify that passing an `importer` without a `url` to
`CompileRequest.StringInput` is an error. This was previously only implicit
from the rest of the spec, and was not enforced by the Dart Sass embedded
compiler. For compatibility, this will produce a deprecation named
`importer-without-url` until Dart Sass 2.0.0.

## 2.6.0

* Add `fatal_deprecation`, `silence_deprecation`, and `future_deprecation`
Expand Down
8 changes: 0 additions & 8 deletions js-api-doc/deprecations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ export interface Deprecations {
*/
'fs-importer-cwd': Deprecation<'fs-importer-cwd'>;

/**
* Deprecation for passing an `importer` argument without a corresponding
* `url` to `compileString()` or `compileStringAsync()`.
*
* This deprecation became active in Dart Sass 1.75.0.
*/
'importer-without-url': Deprecation<'importer-without-url'>;

/**
* Deprecation for `@import` rules.
*
Expand Down
4 changes: 2 additions & 2 deletions js-api-doc/importer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface CanonicalizeContext {
* Like all importers, this implements custom Sass loading logic for [`@use`
* rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
* rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
* to {@link Options.importers} or {@link StringOptionsWithImporter.importer}.
* to {@link Options.importers} or {@link StringOptions.importer}.
*
* @typeParam sync - A `FileImporter<'sync'>`'s {@link findFileUrl} must return
* synchronously, but in return it can be passed to {@link compile} and {@link
Expand Down Expand Up @@ -122,7 +122,7 @@ export interface FileImporter<
* An object that implements custom Sass loading logic for [`@use`
* rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
* rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
* to {@link Options.importers} or {@link StringOptionsWithImporter.importer}.
* to {@link Options.importers} or {@link StringOptions.importer}.
*
* Importers that simply redirect to files on disk are encouraged to use the
* {@link FileImporter} interface instead.
Expand Down
88 changes: 32 additions & 56 deletions js-api-doc/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@ export interface Options<sync extends 'sync' | 'async'> {
* so that they can get fixed as soon as possible!
*
* **Heads up!** If {@link compileString} or {@link compileStringAsync} is
* called without {@link StringOptionsWithoutImporter.url}, <em>all</em>
* stylesheets it loads will be considered dependencies. Since it doesn’t have
* a path of its own, everything it loads is coming from a load path rather
* than a relative import.
* called without {@link StringOptions.url}, <em>all</em> stylesheets it loads
* will be considered dependencies. Since it doesn’t have a path of its own,
* everything it loads is coming from a load path rather than a relative
* import.
*
* @defaultValue `false`
* @category Messages
Expand Down Expand Up @@ -388,17 +388,18 @@ export interface Options<sync extends 'sync' | 'async'> {
* Options that can be passed to {@link compileString} or {@link
* compileStringAsync}.
*
* If the {@link StringOptionsWithImporter.importer} field isn't passed, the
* entrypoint file can load files relative to itself if a `file://` URL is
* passed to the {@link url} field.
* If the {@link StringOptions.importer} field isn't passed, the entrypoint file
* can load files relative to itself if a `file://` URL is passed to the {@link
* url} field. If it is passed, the entrypoint file uses it to load files
* relative to itself.
*
* @typeParam sync - This lets the TypeScript checker verify that asynchronous
* {@link Importer}s, {@link FileImporter}s, and {@link CustomFunction}s aren't
* passed to {@link compile} or {@link compileString}.
*
* @category Options
*/
export interface StringOptionsWithoutImporter<sync extends 'sync' | 'async'>
export interface StringOptions<sync extends 'sync' | 'async'>
extends Options<sync> {
/**
* The {@link Syntax} to use to parse the entrypoint stylesheet.
Expand All @@ -409,6 +410,19 @@ export interface StringOptionsWithoutImporter<sync extends 'sync' | 'async'>
*/
syntax?: Syntax;

/**
* The importer to use to handle loads that are relative to the entrypoint
* stylesheet.
*
* A relative load's URL is first resolved relative to {@link url}, then
* passed to {@link importer}. (It's passed as-is if {@link url} isn't
* passed.) If the importer doesn't recognize it, it's then passed to {@link
* importers} and {@link loadPaths}.
*
* @category Input
*/
importer: Importer<sync> | FileImporter<sync>;

/**
* The canonical URL of the entrypoint stylesheet.
*
Expand All @@ -418,62 +432,24 @@ export interface StringOptionsWithoutImporter<sync extends 'sync' | 'async'>
* loadPaths}.
*
* @category Input
* @compatibility feature: "Undefined URL with importer", dart: "1.75.0", node: false
*
* Earlier versions of Dart Sass required {@link url} to be defined when
* passing {@link StringOptions.importer}.
*/
url?: URL;
}

/**
* Options that can be passed to {@link compileString} or {@link
* compileStringAsync}.
*
* If the {@link StringOptionsWithImporter.importer} field is passed, the
* entrypoint file uses it to load files relative to itself and the {@link url}
* field is mandatory.
*
* @typeParam sync - This lets the TypeScript checker verify that asynchronous
* {@link Importer}s, {@link FileImporter}s, and {@link CustomFunction}s aren't
* passed to {@link compile} or {@link compileString}.
*
* @category Options
* @deprecated Use {@link StringOptions} instead.
*/
export interface StringOptionsWithImporter<sync extends 'sync' | 'async'>
extends StringOptionsWithoutImporter<sync> {
/**
* The importer to use to handle loads that are relative to the entrypoint
* stylesheet.
*
* A relative load's URL is first resolved relative to {@link url}, then
* passed to {@link importer}. If the importer doesn't recognize it, it's then
* passed to {@link importers} and {@link loadPaths}.
*
* @category Input
*/
importer: Importer<sync> | FileImporter<sync>;

/**
* The canonical URL of the entrypoint stylesheet. If this is passed along
* with {@link importer}, it's used to resolve relative loads in the
* entrypoint stylesheet.
*
* @category Input
*/
url: URL;
}
type StringOptionsWithoutImporter<sync extends 'sync' | 'async'> =
StringOptions<sync>;

/**
* Options that can be passed to {@link compileString} or {@link
* compileStringAsync}.
*
* This is a {@link StringOptionsWithImporter} if it has a {@link
* StringOptionsWithImporter.importer} field, and a {@link
* StringOptionsWithoutImporter} otherwise.
*
* @typeParam sync - This lets the TypeScript checker verify that asynchronous
* {@link Importer}s, {@link FileImporter}s, and {@link CustomFunction}s aren't
* passed to {@link compile} or {@link compileString}.
*
* @category Options
* @deprecated Use {@link StringOptions} instead.
*/
export type StringOptions<sync extends 'sync' | 'async'> =
| StringOptionsWithImporter<sync>
| StringOptionsWithoutImporter<sync>;
type StringOptionsWithImporter<sync extends 'sync' | 'async'> =
StringOptions<sync>;
2 changes: 1 addition & 1 deletion spec/EMBEDDED_PROTOCOL_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.1
2.6.0
7 changes: 2 additions & 5 deletions spec/embedded_sass.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,13 @@ message InboundMessage {
// The location from which `source` was loaded. If this is empty, it
// indicates that the URL is unknown.
//
// This must be a canonical URL. If `importer` is passed, this must be a
// canonical URL recognized by `importer` specifically.
// This must be a canonical URL recognized by `importer`, if it's passed.
string url = 2;

// The syntax to use to parse `source`.
Syntax syntax = 3;

// The importer to use to resolve imports relative to `url`. If this is
// passed, `url` must be passed as well, and must be a canonical URL
// recognized by this importer.
// The importer to use to resolve imports relative to `url`.
Importer importer = 4;
}

Expand Down
1 change: 0 additions & 1 deletion spec/js-api/deprecations.d.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export interface Deprecations {
'null-alpha': Deprecation<'null-alpha'>;
'abs-percent': Deprecation<'abs-percent'>;
'fs-importer-cwd': Deprecation<'fs-importer-cwd'>;
'importer-without-url': Deprecation<'importer-without-url'>;
import: Deprecation<'import'>;
'user-authored': Deprecation<'user-authored', 'user'>;
}
Expand Down
82 changes: 40 additions & 42 deletions spec/js-api/options.d.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ import {PromiseOr} from './util/promise_or';
* [`sourceMapIncludeSources`](#sourcemapincludesources)
* [`style`](#style)
* [`verbose`](#verbose)
* [`StringOptionsWithoutImporter`](#stringoptionswithoutimporter)
* [`StringOptions`](#stringoptions)
* [`syntax`](#syntax)
* [`importer`](#importer)
* [`url`](#url)
* [`StringOptionsWithoutImporter`](#stringoptionswithoutimporter)
* [`StringOptionsWithImporter`](#stringoptionswithimporter)
* [`importer`](#importer)
* [`url`](#url-1)
* [`StringOptions`](#stringoptions)

## Types

Expand Down Expand Up @@ -338,17 +337,16 @@ verbose?: boolean;
} // Options
```

### `StringOptionsWithoutImporter`
### `StringOptions`

> This interface is used for calls to [`compileString()`] and
> [`compileStringAsync()`] that don't pass the `importer` parameter, and so
> don't support relative imports.
> [`compileStringAsync()`].
>
> [`compileString()`]: compile.d.ts.md#compilestring
> [`compileStringAsync()`]: compile.d.ts.md#compilestringasync
```ts
export interface StringOptionsWithoutImporter<sync extends 'sync' | 'async'>
export interface StringOptions<sync extends 'sync' | 'async'>
extends Options<sync> {
```
Expand All @@ -360,61 +358,61 @@ The compiler must parse `source` using this syntax. Defaults to `'scss'`.
syntax?: Syntax;
```
#### `url`
#### `importer`
The URL of the stylesheet being parsed.
The [importer] to use to resolve relative imports in the entrypoint.
> When `importer` isn't passed, this is purely advisory and only used for error
> reporting.
[importer]: importer.d.ts.md
```ts
url?: URL;
```
> Relative entrypoint imports are resolved differently depending on whether the
> `url` parameter is also passed:
>
> * If `url` *is* passed, relative URLs are first resolved relative to that URL
> and then passed to `importer`. This is the same behavior that's used when
> resolving relative URLs in any other Sass file.
>
> * If `url` *is not* passed, relative URLs are passed directly to `importer`
> without being resolved. This is a bit of a hack that relies on the fact that
> some importers work like "load paths", resolving relative URLs based on some
> implicit base URL. It's useful for situations like evaluating a stylesheet
> passed via stdin and giving it access to relative loads in the working
> directory without *also* giving it a fake canonical URL that would show up
> in error messages and source maps.
```ts
} // StringOptionsWithoutImporter
importer?: Importer<sync> | FileImporter<sync>;
```
### `StringOptionsWithImporter`
#### `url`
> This interface is used for calls to [`compileString()`] and
> [`compileStringAsync()`] that *do* pass the `importer` parameter, and so *do*
> support relative imports.
The canonical URL of the stylesheet being parsed.
> When `importer` isn't passed, this is purely advisory and only used for error
> reporting. When it is, it's used as the base URL of the relative imports
> within the entrypoint.
```ts
export interface StringOptionsWithImporter<sync extends 'sync' | 'async'>
extends StringOptionsWithoutImporter<sync> {
url?: URL;
```
#### `importer`
The [importer] to use to resolve relative imports in the entrypoint.
[importer]: importer.d.ts.md
```ts
importer: Importer<sync> | FileImporter<sync>;
} // StringOptions
```

#### `url`
The canonical URL of the entrypoint.
### `StringOptionsWithoutImporter`

> This *must* be passed when `importer` is passed, since otherwise there's
> nothing to resolve relative URLs relative to.
> This alias exists for backwards-compatibility only.
```ts
url: URL;
type StringOptionsWithoutImporter<sync extends 'sync' | 'async'> =
StringOptions<sync>;
```

```ts
} // StringOptionsWithImporter
```
### `StringOptionsWithImporter`

### `StringOptions`
> This alias exists for backwards-compatibility only.
```ts
export type StringOptions<sync extends 'sync' | 'async'> =
| StringOptionsWithImporter<sync>
| StringOptionsWithoutImporter<sync>;
type StringOptionsWithImporter<sync extends 'sync' | 'async'> =
StringOptions<sync>;
```
20 changes: 11 additions & 9 deletions spec/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,18 +377,19 @@ This algorithm takes a string `argument` and [configuration](#configuration)
This algorithm takes a string, `argument`, and returns either a [source file] or
null.

* If `argument` is a relative URL and the [current source file] has an importer
`importer`:
* If `argument` is a relative URL and the [current source file] has an
[importer] `importer`:

* 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.
* If the current source file has a canonical URL `canonical`, let `url` be the
result of [parsing `argument` as a URL][parsing a URL] with `canonical`
the base URL. Otherwise, let `url` be `argument`.

> This will produce an error if the current source file has no base URL,
> since the WHATWG URL-parsing algorithm requires either an absolute URL or
> a base URL.
> Allowing `url` to remain relative here in the absence of a canonical URL
> is a bit of a hack, but it's useful in that many importers (including
> [filesystem importer]s) act as "load paths", resolving URLs relative to
> some implicit base URL rather than expecting all inputs to be absolute.
* Let `result` be the result of passing `resolved` to the current source
file's [importer](#importer).
* Let `result` be the result of passing `url` to `base`.

* If `result` is not null:

Expand All @@ -411,6 +412,7 @@ null.

* Return null.

[filesystem importer]: #filesystem-importer
[parsing]: syntax.md#parsing-text

### Resolving a `file:` URL
Expand Down

0 comments on commit 5d254fe

Please sign in to comment.