mirror of
https://github.com/sass/sass.git
synced 2024-09-21 02:27:30 +00:00
[Package Importer] Add types and specs (#3728)
Co-authored-by: Jonny Gerig Meyer <jonny@oddbird.net>
This commit is contained in:
parent
804c6a6707
commit
336bfd69b2
@ -1,3 +1,8 @@
|
||||
## 2.5.0
|
||||
|
||||
* Add `NodePackageImporter` as a built-in Package Importer, resolving `pkg:`
|
||||
URLs using the standards and conventions of the Node ecosystem.
|
||||
|
||||
## 2.4.0
|
||||
|
||||
* Add `CompileRequest.silent` option that suppresses all `LogEvent`s.
|
||||
|
@ -1,3 +1,10 @@
|
||||
## Draft 1.6
|
||||
|
||||
* Change from an entry point path to entry point directory.
|
||||
|
||||
* Add a private readonly symbol key to `NodePackageImporter` type to
|
||||
distinguish it from arbitrary objects.
|
||||
|
||||
## Draft 1.5
|
||||
|
||||
* Specify that Package Importers must not reject URL patterns that other Package
|
@ -1,11 +1,10 @@
|
||||
# Package Importer: Draft 1.5
|
||||
# Package Importer: Draft 1.6
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/2739))*
|
||||
|
||||
This proposal introduces the semantics for a Package Importer and defines the
|
||||
`pkg:` URL scheme to indicate Sass package imports in an implementation-agnostic
|
||||
format. It also defines the semantics for a new built-in Node Package
|
||||
Importer.
|
||||
format. It also defines the semantics for a new built-in Node Package Importer.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@ -123,9 +122,9 @@ the reader where to find the file.
|
||||
|
||||
While the Dart Sass implementation allows for the use of the `package:` URL
|
||||
scheme, a similar standard doesn't exist in Node. We chose the `pkg:` URL scheme
|
||||
as it clearly communicates to both the user and compiler that the specified files
|
||||
are from a dependency. The `pkg:` URL scheme also does not have known conflicts
|
||||
in the ecosystem.
|
||||
as it clearly communicates to both the user and compiler that the specified
|
||||
files are from a dependency. The `pkg:` URL scheme also does not have known
|
||||
conflicts in the ecosystem.
|
||||
|
||||
#### No built-in `pkg:` resolver for browsers
|
||||
|
||||
@ -188,13 +187,14 @@ use the CSS exports exposed through `"style"`. While in practice, `"style"`
|
||||
tends to be used solely for `css` files, we will support `scss`, `sass` and
|
||||
`css` files for either `"sass"` or `"style"`.
|
||||
|
||||
While conditional exports allows package authors to define specific aliases to internal
|
||||
files, we will still use the Sass conventions for resolving file paths with
|
||||
partials, extensions and indices to discover the intended export alias. However,
|
||||
we will not apply that logic to the destination, and will expect library authors
|
||||
to map the export to the correct place. In other words, given a `package.json`
|
||||
with `exports` as below, The Node package importer will resolve a
|
||||
`@use "pkg:pkgName/variables";` to the destination of the `_variables.scss` export.
|
||||
While conditional exports allows package authors to define specific aliases to
|
||||
internal files, we will still use the Sass conventions for resolving file paths
|
||||
with partials, extensions and indices to discover the intended export alias.
|
||||
However, we will not apply that logic to the destination, and will expect
|
||||
library authors to map the export to the correct place. In other words, given a
|
||||
`package.json` with `exports` as below, The Node package importer will resolve a
|
||||
`@use "pkg:pkgName/variables";` to the destination of the `_variables.scss`
|
||||
export.
|
||||
|
||||
```json
|
||||
{
|
||||
@ -223,7 +223,8 @@ when compiling a string like `compileString('@use "pkg:bootstrap";')`, we don't
|
||||
know where to start looking for the Bootstrap module. We considered
|
||||
`require.main.filename` and the current working directory, but found that
|
||||
neither would allow for all use cases. We decided to allow users to specify an
|
||||
entry point path, defaulting to `require.main.filename`.
|
||||
entry point directory, defaulting to the parent directory of
|
||||
`require.main.filename`.
|
||||
|
||||
## Types
|
||||
|
||||
@ -234,8 +235,13 @@ import {FileImporter, Importer} from '../spec/js-api/importer';
|
||||
### `NodePackageImporter`
|
||||
|
||||
```ts
|
||||
declare const nodePackageImporterKey: unique symbol;
|
||||
|
||||
export class NodePackageImporter {
|
||||
constructor(entryPointPath?: string);
|
||||
/** Used to distinguish this type from any arbitrary object. */
|
||||
private readonly [nodePackageImporterKey]: true;
|
||||
|
||||
constructor(entryPointDirectory?: string);
|
||||
}
|
||||
```
|
||||
|
||||
@ -269,13 +275,13 @@ Javascript Compile API, insert:
|
||||
> This primarily refers to a browser environment, but applies to other
|
||||
> sandboxed JavaScript environments as well.
|
||||
|
||||
* Let `entryPointPath` be the class's `entryPointPath` value if set, resolved
|
||||
relative to the current working directory, and otherwise the value of
|
||||
`require.main.filename`. If `entryPointPath` is not passed and
|
||||
`require.main.filename` is not available, throw an error.
|
||||
|
||||
* Let `entryPointDirectory` be the class's `entryPointDirectory` value if set,
|
||||
resolved relative to the current working directory, and otherwise the parent
|
||||
directory of `require.main.filename`. If `entryPointDirectory` is not passed
|
||||
and `require.main.filename` is not available, throw an error.
|
||||
|
||||
* Let `pkgImporter` be a [Node Package Importer] with an associated
|
||||
`entryPointURL` of the absolute file URL for`entryPointPath`.
|
||||
`entryPointURL` of the absolute file URL for `entryPointDirectory`.
|
||||
|
||||
* Replace the item with `pkgImporter` in a copy of `options.importers`.
|
||||
|
||||
@ -290,19 +296,22 @@ any URL with the `pkg:` scheme. This step will be inserted immediately before
|
||||
the existing legacy importer logic, and if the package importer returns `null`,
|
||||
the legacy importer logic will be invoked.
|
||||
|
||||
Currently, the only available package importer is `node`, which follows Node
|
||||
resolution logic to locate Sass files.
|
||||
Currently, the only available package importer is `NodePackageImporter`, which
|
||||
follows Node resolution logic to locate Sass files.
|
||||
|
||||
An optional `entryPointPath` path can be passed to provide a starting
|
||||
`parentURL` for the Node package resolution algorithm. If not set, the default
|
||||
value is `require.main.filename`.
|
||||
An optional `entryPointDirectory` path can be passed to the
|
||||
`NodePackageImporter` to provide a starting `parentURL` for the Node package
|
||||
resolution algorithm. If not set, the default value is the parent directory of
|
||||
`require.main.filename`.
|
||||
|
||||
Defaults to undefined.
|
||||
|
||||
```ts
|
||||
import {NodePackageImporter as BaseNodePackageImporter} from '../spec/js-api/importer';
|
||||
|
||||
declare module '../spec/js-api/legacy/options' {
|
||||
export interface LegacySharedOptions<sync extends 'sync' | 'async'> {
|
||||
pkgImporter?: NodePackageImporter;
|
||||
pkgImporter?: BaseNodePackageImporter;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -313,7 +322,8 @@ declare module '../spec/js-api/legacy/options' {
|
||||
|
||||
This proposal defines the requirements for Package Importers written by users or
|
||||
provided by implementations. It is a type of [Importer] and, in addition to the
|
||||
standard requirements for importers, it must handle only non-canonical URLs that:
|
||||
standard requirements for importers, it must handle only non-canonical URLs
|
||||
that:
|
||||
|
||||
* have the scheme `pkg`, and
|
||||
* whose path begins with a package name, and
|
||||
@ -365,8 +375,8 @@ When the Node Package Importer is invoked with a string named `string`:
|
||||
|
||||
* Otherwise, let `baseURL` be `entryPointURL`.
|
||||
|
||||
* Let `resolved` be the result of [resolving a `pkg:` URL as Node] with `url` and
|
||||
`baseURL`.
|
||||
* Let `resolved` be the result of [resolving a `pkg:` URL as Node] with `url`
|
||||
and `baseURL`.
|
||||
|
||||
* If `resolved` is null, return null.
|
||||
|
||||
@ -436,8 +446,8 @@ It returns a canonical `file:` URL or null.
|
||||
This algorithm takes a string, `path`, and returns the portion that identifies
|
||||
the Node package.
|
||||
|
||||
* If `path` starts with `@`, it is a scoped package. Return the first 2 [URL path
|
||||
segments], including the separating `/`.
|
||||
* If `path` starts with `@`, it is a scoped package. Return the first 2 [URL
|
||||
path segments], including the separating `/`.
|
||||
|
||||
* Otherwise, return the first URL path segment.
|
||||
|
||||
@ -447,9 +457,13 @@ This algorithm takes a string, `packageName`, and an absolute URL `baseURL`, and
|
||||
returns an absolute URL to the root directory for the most proximate installed
|
||||
`packageName`.
|
||||
|
||||
* Return the result of `PACKAGE_RESOLVE(packageName, baseURL)` as defined in
|
||||
the [Node resolution algorithm specification].
|
||||
* Let `baseDirectory` be `baseURL` appended with a [single-dot URL path
|
||||
segment].
|
||||
|
||||
* Return the result of `PACKAGE_RESOLVE(packageName, baseDirectory)` as defined
|
||||
in the [Node resolution algorithm specification].
|
||||
|
||||
[single-dot URL path segment]: https://url.spec.whatwg.org/#single-dot-path-segment
|
||||
[Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification
|
||||
|
||||
### Resolving package exports
|
||||
@ -481,7 +495,8 @@ This algorithm takes a package.json value `packageManifest`, a directory URL
|
||||
|
||||
* Let `subpathIndex` be `subpath` + `"/index"`.
|
||||
|
||||
* Let `subpathIndexVariants` be the result of [Export load paths] with `subpathIndex`.
|
||||
* Let `subpathIndexVariants` be the result of [Export load paths] with
|
||||
`subpathIndex`.
|
||||
|
||||
* Let `resolvedIndexPaths` be a list of the results of calling
|
||||
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",
|
||||
@ -552,12 +567,12 @@ potential subpaths, resolving for partials and file extensions.
|
||||
## Embedded Protocol
|
||||
|
||||
An Importer that resolves `pkg:` URLs using the [Node resolution algorithm]. It
|
||||
is instantiated with an associated `entry_point_path`, which is either absolute
|
||||
or will be resolved relative to the current working directory.
|
||||
is instantiated with an associated `entry_point_directory`, which is either
|
||||
absolute or will be resolved relative to the current working directory.
|
||||
|
||||
```proto
|
||||
message NodePackageImporter {
|
||||
string entry_point_path = 1;
|
||||
string entry_point_directory = 1;
|
||||
}
|
||||
```
|
||||
|
133
js-api-doc/importer.d.ts
vendored
133
js-api-doc/importer.d.ts
vendored
@ -308,6 +308,139 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
nonCanonicalScheme?: string | string[];
|
||||
}
|
||||
|
||||
declare const nodePackageImporterKey: unique symbol;
|
||||
|
||||
/**
|
||||
* The built-in Node.js package importer. This loads pkg: URLs from node_modules
|
||||
* according to the standard Node.js resolution algorithm.
|
||||
*
|
||||
* A Node.js package importer is exposed as a class that can be added to the
|
||||
* `importers` option.
|
||||
*
|
||||
*```js
|
||||
* const sass = require('sass');
|
||||
* sass.compileString('@use "pkg:vuetify', {
|
||||
* importers: [new sass.NodePackageImporter()]
|
||||
* });
|
||||
*```
|
||||
*
|
||||
* ## Writing Sass packages
|
||||
*
|
||||
* Package authors can control what is exposed to their users through their
|
||||
* `package.json` manifest. The recommended method is to add a `sass`
|
||||
* conditional export to `package.json`.
|
||||
*
|
||||
* ```json
|
||||
* // node_modules/uicomponents/package.json
|
||||
* {
|
||||
* "exports": {
|
||||
* ".": {
|
||||
* "sass": "./src/scss/index.scss",
|
||||
* "import": "./dist/js/index.mjs",
|
||||
* "default": "./dist/js/index.js"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This allows a package user to write `@use "pkg:uicomponents"` to load the
|
||||
* file at `node_modules/uicomponents/src/scss/index.scss`.
|
||||
*
|
||||
* The Node.js package importer supports the variety of formats supported by
|
||||
* Node.js [package entry points], allowing authors to expose multiple subpaths.
|
||||
*
|
||||
* [package entry points]:
|
||||
* https://nodejs.org/api/packages.html#package-entry-points
|
||||
*
|
||||
* ```json
|
||||
* // node_modules/uicomponents/package.json
|
||||
* {
|
||||
* "exports": {
|
||||
* ".": {
|
||||
* "sass": "./src/scss/index.scss",
|
||||
* },
|
||||
* "./colors": {
|
||||
* "sass": "./src/scss/_colors.scss",
|
||||
* },
|
||||
* "./theme/*": {
|
||||
* "sass": "./src/scss/theme/*.scss",
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This allows a package user to write:
|
||||
*
|
||||
* - `@use "pkg:uicomponents";` to import the root export.
|
||||
* - `@use "pkg:uicomponents/colors";` to import the colors partial.
|
||||
* - `@use "pkg:uicomponents/theme/purple";` to import a purple theme.
|
||||
*
|
||||
* Note that while library users can rely on the importer to resolve
|
||||
* [partials](https://sass-lang.com/documentation/at-rules/use#partials), [index
|
||||
* files](https://sass-lang.com/documentation/at-rules/use#index-files), and
|
||||
* extensions, library authors must specify the entire file path in `exports`.
|
||||
*
|
||||
* In addition to the `sass` condition, the `style` condition is also
|
||||
* acceptable. Sass will match the `default` condition if it's a relevant file
|
||||
* type, but authors are discouraged from relying on this. Notably, the key
|
||||
* order matters, and the importer will resolve to the first value with a key
|
||||
* that is `sass`, `style`, or `default`, so you should always put `default`
|
||||
* last.
|
||||
*
|
||||
* To help package authors who haven't transitioned to package entry points
|
||||
* using the `exports` field, the Node.js package importer provides several
|
||||
* fallback options. If the `pkg:` URL does not have a subpath, the Node.js
|
||||
* package importer will look for a `sass` or `style` key at the root of
|
||||
* `package.json`.
|
||||
*
|
||||
* ```json
|
||||
* // node_modules/uicomponents/package.json
|
||||
* {
|
||||
* "sass": "./src/scss/index.scss",
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This allows a user to write `@use "pkg:uicomponents";` to import the
|
||||
* `index.scss` file.
|
||||
*
|
||||
* Finally, the Node.js package importer will look for an `index` file at the
|
||||
* package root, resolving partials and extensions. For example, if the file
|
||||
* `_index.scss` exists in the package root of `uicomponents`, a user can import
|
||||
* that with `@use "pkg:uicomponents";`.
|
||||
*
|
||||
* If a `pkg:` URL includes a subpath that doesn't have a match in package entry
|
||||
* points, the Node.js importer will attempt to find that file relative to the
|
||||
* package root, resolving for file extensions, partials and index files. For
|
||||
* example, if the file `src/sass/_colors.scss` exists in the `uicomponents`
|
||||
* package, a user can import that file using `@use
|
||||
* "pkg:uicomponents/src/sass/colors";`.
|
||||
*
|
||||
* @compatibility dart: "2.0", node: false
|
||||
* @category Importer
|
||||
*/
|
||||
export class NodePackageImporter {
|
||||
/** Used to distinguish this type from any arbitrary object. */
|
||||
private readonly [nodePackageImporterKey]: true;
|
||||
|
||||
/**
|
||||
* The NodePackageImporter has an optional `entryPointDirectory` option, which
|
||||
* is the directory where the Node Package Importer should start when
|
||||
* resolving `pkg:` URLs in sources other than files on disk. This will be
|
||||
* used as the `parentURL` in the [Node Module
|
||||
* Resolution](https://nodejs.org/api/esm.html#resolution-algorithm-specification)
|
||||
* algorithm.
|
||||
*
|
||||
* In order to be found by the Node Package Importer, a package will need to
|
||||
* be inside a node_modules folder located in the `entryPointDirectory`, or
|
||||
* one of its parent directories, up to the filesystem root.
|
||||
*
|
||||
* Relative paths will be resolved relative to the current working directory.
|
||||
* If a path is not provided, the default value of `require.main.filename`
|
||||
* will be used.
|
||||
*/
|
||||
constructor(entryPointDirectory?: string);
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of successfully loading a stylesheet with an {@link Importer}.
|
||||
*
|
||||
|
1
js-api-doc/index.d.ts
vendored
1
js-api-doc/index.d.ts
vendored
@ -19,6 +19,7 @@ export {
|
||||
FileImporter,
|
||||
Importer,
|
||||
ImporterResult,
|
||||
NodePackageImporter,
|
||||
} from './importer';
|
||||
export {Logger, SourceSpan, SourceLocation} from './logger';
|
||||
export {
|
||||
|
19
js-api-doc/legacy/options.d.ts
vendored
19
js-api-doc/legacy/options.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
import {Logger} from '../logger';
|
||||
import {LegacyImporter} from './importer';
|
||||
import {LegacyFunction} from './function';
|
||||
import {NodePackageImporter} from '../importer';
|
||||
|
||||
/**
|
||||
* Options for {@link render} and {@link renderSync} that are shared between
|
||||
@ -508,6 +509,24 @@ export interface LegacySharedOptions<sync extends 'sync' | 'async'> {
|
||||
* @compatibility dart: "1.43.0", node: false
|
||||
*/
|
||||
logger?: Logger;
|
||||
|
||||
/**
|
||||
* If this option is set to an instance of `NodePackageImporter`, Sass will
|
||||
* use the built-in Node.js package importer to resolve Sass files with a
|
||||
* `pkg:` URL scheme. Details for library authors and users can be found in
|
||||
* the {@link NodePackageImporter} documentation.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* sass.renderSync({
|
||||
* data: '@use "pkg:vuetify";',
|
||||
* pkgImporter: new sass.NodePackageImporter()
|
||||
* });
|
||||
* ```
|
||||
* @category Plugins
|
||||
* @compatibility dart: "2.0", node: false
|
||||
*/
|
||||
pkgImporter?: NodePackageImporter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
8
js-api-doc/options.d.ts
vendored
8
js-api-doc/options.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import {FileImporter, Importer} from './importer';
|
||||
import {FileImporter, Importer, NodePackageImporter} from './importer';
|
||||
import {Logger} from './logger';
|
||||
import {Value} from './value';
|
||||
import {PromiseOr} from './util/promise_or';
|
||||
@ -208,8 +208,8 @@ export interface Options<sync extends 'sync' | 'async'> {
|
||||
* - The importer that was used to load the current stylesheet, with the
|
||||
* loaded URL resolved relative to the current stylesheet's canonical URL.
|
||||
*
|
||||
* - Each {@link Importer} or {@link FileImporter} in {@link importers}, in
|
||||
* order.
|
||||
* - Each {@link Importer}, {@link FileImporter}, or
|
||||
* {@link NodePackageImporter} in {@link importers}, in order.
|
||||
*
|
||||
* - Each load path in {@link loadPaths}, in order.
|
||||
*
|
||||
@ -218,7 +218,7 @@ export interface Options<sync extends 'sync' | 'async'> {
|
||||
*
|
||||
* @category Plugins
|
||||
*/
|
||||
importers?: (Importer<sync> | FileImporter<sync>)[];
|
||||
importers?: (Importer<sync> | FileImporter<sync> | NodePackageImporter)[];
|
||||
|
||||
/**
|
||||
* Paths in which to look for stylesheets loaded by rules like
|
||||
|
@ -1 +1 @@
|
||||
2.4.0
|
||||
2.5.0
|
||||
|
@ -98,6 +98,13 @@ message InboundMessage {
|
||||
// generating this ID and ensuring that it's unique across all importers
|
||||
// registered for this compilation.
|
||||
uint32 file_importer_id = 3;
|
||||
|
||||
// The [Node.js package importer], which is a built-in Package Importer
|
||||
// with an associated `entry_point_directory` that resolves `pkg:` URLs
|
||||
// using the standards and conventions of the Node ecosystem.
|
||||
//
|
||||
// [Node.js package importer]: https://github.com/sass/sass/tree/main/spec/modules.md#node-package-importer
|
||||
NodePackageImporter node_package_importer = 5;
|
||||
}
|
||||
|
||||
// The set of URL schemes that are considered *non-canonical* for this
|
||||
@ -1063,3 +1070,16 @@ enum CalculationOperator {
|
||||
// The division operator.
|
||||
DIVIDE = 3;
|
||||
}
|
||||
|
||||
// The built-in Node.js Package Importer, which is a Package Importer that
|
||||
// resolves using the standards and conventions of the Node.js ecosystem. It
|
||||
// enables a `pkg:` URL scheme for usage with `@use` that directs an
|
||||
// implementation to resolve a URL within a dependency.
|
||||
message NodePackageImporter {
|
||||
// The absolute path to associate with the Node Package Importer, with
|
||||
// semantics identical to the [entryPointDirectory option] in the JavaScript
|
||||
// API.
|
||||
//
|
||||
// [entryPointDirectory option]: https://sass-lang.com/documentation/js-api/classes/NodePackageImporter.html#constructor
|
||||
string entry_point_directory = 1;
|
||||
}
|
||||
|
@ -126,6 +126,26 @@ When `dispose` is invoked on an Async Compiler:
|
||||
|
||||
Compiles the Sass file at `path`:
|
||||
|
||||
* If any item in `options.importers` is an instance of the `NodePackageImporter`
|
||||
class:
|
||||
|
||||
* If no filesystem is available, throw an error.
|
||||
|
||||
> This primarily refers to a browser environment, but applies to other
|
||||
> sandboxed JavaScript environments as well.
|
||||
|
||||
* Let `entryPointDirectory` be the class's `entryPointDirectory` value if set,
|
||||
resolved relative to the current working directory, and otherwise the parent
|
||||
directory of `require.main.filename`. If `entryPointDirectory` is not passed
|
||||
and `require.main.filename` is not available, throw an error.
|
||||
|
||||
* Let `pkgImporter` be a [Node Package Importer] with an associated
|
||||
`entryPointURL` of the absolute file URL for`entryPointDirectory`.
|
||||
|
||||
[Node Package Importer]: ../modules.md#node-package-importer
|
||||
|
||||
* Replace the item with `pkgImporter` in a copy of `options.importers`.
|
||||
|
||||
* If any object in `options.importers` has both `findFileUrl` and `canonicalize`
|
||||
fields, throw an error.
|
||||
|
||||
@ -177,6 +197,24 @@ export function compileAsync(
|
||||
|
||||
Compiles the Sass `source`:
|
||||
|
||||
* If any item in `options.importers` is an instance of the `NodePackageImporter`
|
||||
class:
|
||||
|
||||
* If no filesystem is available, throw an error.
|
||||
|
||||
> This primarily refers to a browser environment, but applies to other
|
||||
> sandboxed JavaScript environments as well.
|
||||
|
||||
* Let `entryPointDirectory` be the class's `entryPointDirectory` value if set,
|
||||
resolved relative to the current working directory, and otherwise the parent
|
||||
directory of `require.main.filename`. If `entryPointDirectory` is not passed
|
||||
and `require.main.filename` is not available, throw an error.
|
||||
|
||||
* Let `pkgImporter` be a [Node Package Importer] with an associated
|
||||
`entryPointURL` of the absolute file URL for`entryPointDirectory`.
|
||||
|
||||
* Replace the item with `pkgImporter` in a copy of `options.importers`.
|
||||
|
||||
* If `options.importer` or any object in `options.importers` has both
|
||||
`findFileUrl` and `canonicalize` fields, throw an error.
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {PromiseOr} from './util/promise_or';
|
||||
* [`FileImporter`](#fileimporter)
|
||||
* [`Importer`](#importer)
|
||||
* [`nonCanonicalScheme`](#noncanonicalscheme)
|
||||
* [`NodePackageImporter`](#nodepackageimporter)
|
||||
* [`ImporterResult`](#importerresult)
|
||||
|
||||
## Types
|
||||
@ -161,6 +162,19 @@ nonCanonicalScheme?: string | string[];
|
||||
} // Importer
|
||||
```
|
||||
|
||||
### `NodePackageImporter`
|
||||
|
||||
```ts
|
||||
declare const nodePackageImporterKey: unique symbol;
|
||||
|
||||
export class NodePackageImporter {
|
||||
/** Used to distinguish this type from any arbitrary object. */
|
||||
private readonly [nodePackageImporterKey]: true;
|
||||
|
||||
constructor(entryPointDirectory?: string);
|
||||
}
|
||||
```
|
||||
|
||||
### `ImporterResult`
|
||||
|
||||
```ts
|
||||
|
@ -56,6 +56,7 @@ export {
|
||||
FileImporter,
|
||||
Importer,
|
||||
ImporterResult,
|
||||
NodePackageImporter,
|
||||
} from './importer';
|
||||
export {Logger, SourceSpan, SourceLocation} from './logger';
|
||||
export {
|
||||
|
@ -4,6 +4,7 @@
|
||||
import {Logger} from '../logger';
|
||||
import {LegacyImporter} from './importer';
|
||||
import {LegacyFunction} from './function';
|
||||
import {NodePackageImporter} from '../importer';
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
@ -27,6 +28,7 @@ import {LegacyFunction} from './function';
|
||||
* [`quietDeps`](#quietdeps)
|
||||
* [`verbose`](#verbose)
|
||||
* [`logger`](#logger)
|
||||
* [`pkgImporter`](#pkgimporter)
|
||||
* [`LegacyFileOptions`](#legacyfileoptions)
|
||||
* [`LegacyStringOptions`](#legacystringoptions)
|
||||
* [`LegacyOptions`](#legacyoptions)
|
||||
@ -173,6 +175,19 @@ doesn't have `warn` or `debug` fields.
|
||||
logger?: Logger;
|
||||
```
|
||||
|
||||
#### `pkgImporter`
|
||||
|
||||
If set to an instance of [NodePackageImporter], the compiler will use it as the
|
||||
specified built-in package importer to resolve any URL with the `pkg:` scheme.
|
||||
This importer will be checked immediately before the existing legacy importer
|
||||
logic, and if it returns `null`, the legacy importer logic will be invoked.
|
||||
|
||||
[NodePackageImporter]: ../importer.d.ts.md#nodepackageimporter
|
||||
|
||||
```ts
|
||||
pkgImporter?: NodePackageImporter;
|
||||
```
|
||||
|
||||
```ts
|
||||
} // LegacySharedOptions
|
||||
```
|
||||
|
@ -6,7 +6,7 @@
|
||||
> [compile API]: compile.d.ts.md
|
||||
|
||||
```ts
|
||||
import {FileImporter, Importer} from './importer';
|
||||
import {FileImporter, Importer, NodePackageImporter} from './importer';
|
||||
import {Logger} from './logger';
|
||||
import {Value} from './value';
|
||||
import {PromiseOr} from './util/promise_or';
|
||||
@ -165,12 +165,14 @@ functions?: Record<string, CustomFunction<sync>>;
|
||||
|
||||
#### `importers`
|
||||
|
||||
The list of [custom importers] to use to resolve file loads.
|
||||
The list of [custom importers] or [package importers] to use to resolve file
|
||||
loads.
|
||||
|
||||
[custom importers]: importer.d.ts.md
|
||||
[package importers]: importer.d.ts.md
|
||||
|
||||
```ts
|
||||
importers?: (Importer<sync> | FileImporter<sync>)[];
|
||||
importers?: (Importer<sync> | FileImporter<sync> | NodePackageImporter)[];
|
||||
```
|
||||
|
||||
#### `loadPaths`
|
||||
|
242
spec/modules.md
242
spec/modules.md
@ -12,6 +12,8 @@
|
||||
* [Built-In Module](#built-in-module)
|
||||
* [Importer](#importer)
|
||||
* [Filesystem Importer](#filesystem-importer)
|
||||
* [Package Importers](#package-importers)
|
||||
* [Node Package Importer](#node-package-importer)
|
||||
* [Global Importer List](#global-importer-list)
|
||||
* [Basename](#basename)
|
||||
* [Dirname](#dirname)
|
||||
@ -23,6 +25,13 @@
|
||||
* [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)
|
||||
* [Node Package Importer Semantics](#node-package-importer-semantics)
|
||||
* [Node Algorithm for Resolving a `pkg:` URL](#node-algorithm-for-resolving-a-pkg-url)
|
||||
* [Resolving a package name](#resolving-a-package-name)
|
||||
* [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package)
|
||||
* [Resolving package exports](#resolving-package-exports)
|
||||
* [Resolving package root values](#resolving-package-root-values)
|
||||
* [Export Load Paths](#export-load-paths)
|
||||
|
||||
## Definitions
|
||||
|
||||
@ -211,6 +220,81 @@ named `string`:
|
||||
|
||||
[parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser
|
||||
|
||||
### Package Importers
|
||||
|
||||
This proposal defines the requirements for Package Importers written by users or
|
||||
provided by implementations. It is a type of [Importer] and, in addition to the
|
||||
standard requirements for importers, it must handle only non-canonical URLs that:
|
||||
|
||||
* have the scheme `pkg`, and
|
||||
* whose path begins with a package name, and
|
||||
* optionally followed by a path, with path segments separated with a forward
|
||||
slash.
|
||||
|
||||
The package name will often be the first path segment, but the importer may take
|
||||
into account any conventions in the environment. For instance, Node supports
|
||||
scoped package names, which start with `@` followed by 2 path segments. Note
|
||||
that package names that contain non-alphanumeric characters may be less portable
|
||||
across different package importers.
|
||||
|
||||
Package Importers must reject the following patterns:
|
||||
|
||||
* A URL whose path begins with `/`.
|
||||
* A URL with non-empty/null username, password, host, port, query, or fragment.
|
||||
|
||||
[importer]: #importer
|
||||
|
||||
### Node Package Importer
|
||||
|
||||
The Node Package Importer is an implementation of a [Package Importer] using the
|
||||
standards and conventions of the Node ecosystem. It has an associated absolute
|
||||
`file:` URL named `entryPointURL`.
|
||||
|
||||
When the Node Package Importer is invoked with a string named `string`:
|
||||
|
||||
* If `string` is a relative URL, return null.
|
||||
|
||||
* Let `url` be the result of [parsing `string` as a URL][parsing a URL]. If this
|
||||
returns a failure, throw that failure.
|
||||
|
||||
* If `url`'s scheme is not `pkg:`, return null.
|
||||
|
||||
* If `url`'s path begins with a `/` or is empty, throw an error.
|
||||
|
||||
* If `url` contains a username, password, host, port, query, or fragment, throw
|
||||
an error.
|
||||
|
||||
* Let `sourceFile` be the canonical URL of the [current source file] that
|
||||
contained the load.
|
||||
|
||||
* If `sourceFile`'s scheme is `file:`, let `baseURL` be `sourceFile`.
|
||||
|
||||
* Otherwise, let `baseURL` be `entryPointURL`.
|
||||
|
||||
* Let `resolved` be the result of [resolving a `pkg:` URL as Node] with `url` and
|
||||
`baseURL`.
|
||||
|
||||
* If `resolved` is null, return null.
|
||||
|
||||
* Let `text` be the contents of the file at `resolved`.
|
||||
|
||||
* Let `syntax` be:
|
||||
|
||||
* "scss" if `resolved` ends in `.scss`.
|
||||
|
||||
* "indented" if `resolved` ends in `.sass`.
|
||||
|
||||
* "css" if `resolved` ends in `.css`.
|
||||
|
||||
> The algorithm for [resolving a `pkg:` URL as Node] guarantees that
|
||||
> `resolved` will have one of these extensions.
|
||||
|
||||
* Return `text`, `syntax`, and `resolved`.
|
||||
|
||||
[Package Importer]: #package-importers
|
||||
[current source file]: ./spec.md#current-source-file
|
||||
[resolving a `pkg:` URL as Node]: #node-algorithm-for-resolving-a-pkg-url
|
||||
|
||||
### Global Importer List
|
||||
|
||||
The *global importer list* is a list of importers that's set for the entire
|
||||
@ -322,7 +406,6 @@ null.
|
||||
|
||||
* Return null.
|
||||
|
||||
[current source file]: spec.md#current-source-file
|
||||
[parsing]: syntax.md#parsing-text
|
||||
|
||||
### Resolving a `file:` URL
|
||||
@ -506,3 +589,160 @@ and returns a member of type `type` or null.
|
||||
> implementation's host language API.
|
||||
|
||||
* Otherwise, return null.
|
||||
|
||||
### Node Package Importer Semantics
|
||||
|
||||
#### Node Algorithm for Resolving a `pkg:` URL
|
||||
|
||||
This algorithm takes a URL with scheme `pkg:` named `url`, and a URL `baseURL`.
|
||||
It returns a canonical `file:` URL or null.
|
||||
|
||||
* Let `fullPath` be `url`'s path.
|
||||
|
||||
* Let `packageName` be the result of [resolving a package name] with `fullPath`,
|
||||
and `subpath` be `fullPath` without the `packageName`.
|
||||
|
||||
* Let `packageRoot` be the result of [resolving the root directory for a
|
||||
package] with `packageName` and `baseURL`.
|
||||
|
||||
* If a `package.json` file does not exist at `packageRoot`, throw an error.
|
||||
|
||||
* Let `packageManifest` be the result of parsing the `package.json` file at
|
||||
`packageRoot` as [JSON].
|
||||
|
||||
* Let `resolved` be the result of [resolving package exports] with
|
||||
`packageRoot`, `subpath`, and `packageManifest`.
|
||||
|
||||
* If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or
|
||||
`css`, return it.
|
||||
|
||||
* Otherwise, if `resolved` is not null, throw an error.
|
||||
|
||||
* If `subpath` is empty, return the result of [resolving package root values].
|
||||
|
||||
* Let `resolved` be `subpath` resolved relative to `packageRoot`.
|
||||
|
||||
* Return the result of [resolving a `file:` URL] with `resolved`.
|
||||
|
||||
[Resolving package exports]: #resolving-package-exports
|
||||
[resolving package root values]: #resolving-package-root-values
|
||||
[resolving a package name]: #resolving-a-package-name
|
||||
[JSON]: https://datatracker.ietf.org/doc/html/rfc8259
|
||||
[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package
|
||||
[resolving a `file:` URL]: #resolving-a-file-url
|
||||
|
||||
#### Resolving a package name
|
||||
|
||||
This algorithm takes a string, `path`, and returns the portion that identifies
|
||||
the Node package.
|
||||
|
||||
* If `path` starts with `@`, it is a scoped package. Return the first 2 [URL path
|
||||
segments], including the separating `/`.
|
||||
|
||||
* Otherwise, return the first URL path segment.
|
||||
|
||||
#### Resolving the root directory for a package
|
||||
|
||||
This algorithm takes a string, `packageName`, and an absolute URL `baseURL`, and
|
||||
returns an absolute URL to the root directory for the most proximate installed
|
||||
`packageName`.
|
||||
|
||||
* Return the result of `PACKAGE_RESOLVE(packageName, baseURL)` as defined in
|
||||
the [Node resolution algorithm specification].
|
||||
|
||||
[Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification
|
||||
|
||||
#### Resolving package exports
|
||||
|
||||
This algorithm takes a package.json value `packageManifest`, a directory URL
|
||||
`packageRoot` and a relative URL path `subpath`. It returns a file URL or null.
|
||||
|
||||
* Let `exports` be the value of `packageManifest.exports`.
|
||||
|
||||
* If `exports` is undefined, return null.
|
||||
|
||||
* Let `subpathVariants` be the result of [Export load paths] with `subpath`.
|
||||
|
||||
* Let `resolvedPaths` be a list of the results of calling
|
||||
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",
|
||||
"style"])` as defined in the [Node resolution algorithm specification], with
|
||||
each `subpathVariants` as `subpathVariant`.
|
||||
|
||||
> The PACKAGE_EXPORTS_RESOLVE algorithm always includes a `default` condition,
|
||||
> so one does not have to be passed here.
|
||||
|
||||
* If `resolvedPaths` contains more than one resolved URL, throw an error.
|
||||
|
||||
* If `resolvedPaths` contains exactly one resolved URL, return it.
|
||||
|
||||
* If `subpath` has an extension, return null.
|
||||
|
||||
* Let `subpathIndex` be `subpath` + `"/index"`.
|
||||
|
||||
* Let `subpathIndexVariants` be the result of [Export load paths] with `subpathIndex`.
|
||||
|
||||
* Let `resolvedIndexPaths` be a list of the results of calling
|
||||
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",
|
||||
"style"])` as defined in the [Node resolution algorithm specification], with
|
||||
each `subpathIndexVariants` as `subpathVariant`.
|
||||
|
||||
* If `resolvedIndexPaths` contains more than one resolved URL, throw an error.
|
||||
|
||||
* If `resolvedIndexPaths` contains exactly one resolved URL, return it.
|
||||
|
||||
* Return null.
|
||||
|
||||
> Where possible in Node, implementations can use [resolve.exports] which
|
||||
> exposes the Node resolution algorithm, allowing for per-path custom
|
||||
> conditions, and without needing filesystem access.
|
||||
|
||||
[Export load paths]: #export-load-paths
|
||||
[resolve.exports]: https://github.com/lukeed/resolve.exports
|
||||
|
||||
#### Resolving package root values
|
||||
|
||||
This algorithm takes a string `packagePath`, which is the root directory for a
|
||||
package, and `packageManifest`, which is the contents of that package's
|
||||
`package.json` file, and returns a file URL.
|
||||
|
||||
* Let `sassValue` be the value of `sass` in `packageManifest`.
|
||||
|
||||
* If `sassValue` is a relative path with an extension of `sass`, `scss` or
|
||||
`css`:
|
||||
|
||||
* Return the canonicalized `file:` URL for `${packagePath}/${sassValue}`.
|
||||
|
||||
* Let `styleValue` be the value of `style` in `packageManifest`.
|
||||
|
||||
* If `styleValue` is a relative path with an extension of `sass`, `scss` or
|
||||
`css`:
|
||||
|
||||
* Return the canonicalized `file:` URL for `${packagePath}/${styleValue}`.
|
||||
|
||||
* Otherwise return the result of [resolving a `file:` URL for extensions] with
|
||||
`packagePath + "/index"`.
|
||||
|
||||
[resolving a `file:` URL for extensions]: #resolving-a-file-url-for-extensions
|
||||
|
||||
[URL path segments]: https://url.spec.whatwg.org/#url-path-segment
|
||||
|
||||
#### Export Load Paths
|
||||
|
||||
This algorithm takes a relative URL path `subpath` and returns a list of
|
||||
potential subpaths, resolving for partials and file extensions.
|
||||
|
||||
* Let `paths` be a list.
|
||||
|
||||
* If `subpath` ends in `.scss`, `.sass`, or `.css`:
|
||||
|
||||
* Add `subpath` to `paths`.
|
||||
|
||||
* Otherwise, add `subpath` + `.scss`, `subpath` + `.sass`, and `subpath` +
|
||||
`.css` to `paths`.
|
||||
|
||||
* If `subpath`'s [basename] does not start with `_`, for each `item` in
|
||||
`paths`, prepend `"_"` to the basename, and add to `paths`.
|
||||
|
||||
* Return `paths`.
|
||||
|
||||
[basename]: #basename
|
||||
|
Loading…
Reference in New Issue
Block a user