From cae27d5a6363449002fe6d70f8f444d35160e3fd Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 29 Jun 2023 22:40:48 -0400 Subject: [PATCH 01/38] Add proposal for pkg: dependency imports --- proposal/package-importer.md | 122 +++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 proposal/package-importer.md diff --git a/proposal/package-importer.md b/proposal/package-importer.md new file mode 100644 index 0000000000..b123d93556 --- /dev/null +++ b/proposal/package-importer.md @@ -0,0 +1,122 @@ +# Package Importer + +*([Issue](https://github.com/sass/sass/issues/2739))* + +This proposal adds Sass support for a standard package importer, introducing a `pkg` URL scheme to indicate Sass package imports in an implementation-agnostic format. + +## Table of contents + +* [Background](#background) +* [Summary](#summary) + * [Design Decisions](#design-decisions) +* [Syntax](#syntax) +* [Semantics](#semantics) +* [Deprecation Process](#deprecation-process) + +## Background + +> This section is non-normative. + +Historically, Sass has not specified a standard method for using packages from dependencies. A number of domain-specific solutions exist using custom importers or by specifying a load path. This can lead to Sass code being written in a way that is tied to a specific domain and make it difficult to rely on dependencies. + +## Summary + +> This section is non-normative. + +Sass users often need to use styles from a dependency to customize an existing theme or access styling utilities. + +This proposal defines a `pkg` URL scheme for usage with `@use` that directs an implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. + +For example, `@use 'pkg:bootstrap';` would be able to resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be resolved within `node_modules`, using the [node resolution algorithm]. In Dart, that would be resolved within `pub-cache`, using [package-config]. + +[node module algorithm]: https://nodejs.org/api/packages.html +[package-config]: https://pub.dev/packages/package_config + +There will be tension between the different path resolution algorithms that are used. To address this, a `pkg:` url will only use the environment's resolution initially in order to resolve a starting path. After that, it will be handed off to the Sass algorithm, which works directly with the filesystem, to resolve index files and partials. + +To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. + +[Sass pkg: test]: https://github.com/oddbird/sass-pkg-test + +### Design Decisions + +We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or 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 could add `node_modules` to the load path, but that again suffers from lack of clarity to the implementation and the reader. + +Some npm packages have adopted a convention of declaring their Sass entrypoints using `"style"` or `"sass"` keys in their `package.json`. This is tied to the npm context, and would require parsing package.json to find these files. Instead, each implementation should use the standard patterns and tooling (`require` for Node and `package_config` for Dart) for resolving file paths. This allows library authors control over what is public, which follows Sass's general design. + +This proposal does not add support for a [Node custom user condition]. Node currently does not provide a native way to set a condition on a single invocation of `require.resolve`, meaning we would need to implement our own `package.json` parsing similar to [Rollup] or add a dependency on a community solution like [resolve.exports]. However, this proposal will not prevent future support. + +[Node custom user condition]: https://nodejs.org/api/packages.html#community-conditions-definitions +[Rollup]: https://github.com/rollup/plugins/blob/master/packages/node-resolve/src/package/resolvePackageExports.js +[resolve.exports]: https://github.com/lukeed/resolve.exports + +A potential source of confusion is in instances where a dependency defines a default or main export that is not in the root folder. In these cases, Sass will look for index and partial files in the same folder as the default or main export, and not in the package root. + +The `pkg` scheme will not be supported in the browser version of Dart Sass. To support a similar functionality, a user would need to ensure that files are served, and the loader would need to fetch the URL. In order to follow the same algorithm for [resolving a file: URL], we would need to make many fetches. If we instead require the browser version to have a fully resolved URL, we negate many of this spec's benefits. + +[resolving a file: URL]: ../spec/modules.md#resolving-a-file-url + +## Semantics + +This proposal defines a new step in the [Loading a Source File] in modules that implementations must include. + +New step for [Loading a Source File], after "if argument is a relative URL" step- + +- If `argument` is a valid URL with scheme `pkg`: + - Let `resolved` be the result of resolving a pkg url + - If `resolved` is not null, let `argument` equal `resolved`, and continue. Otherwise, return null. + +[Loading a Source File]: ../spec/modules.md#loading-a-source-file + +### Resolving a `pkg`: URL + +This algorithm takes a URL, `url`, whose `scheme` must be `pkg` and returns either another URL of a file or directory on disk, or null. At this point, the URL is not guaranteed to resolve to a file or disk. + +- Let `resolved` be the result of the implementation's dependency resolution algorithm. +- If resolution fails: + - Let `dependencyDefault` be the result of the implementation's dependency resolution algorithm for the dependency name. + - Let `urlSubpath` be the path segments of `url` that are not part of the dependency name. + - Let `resolved` be `dependencyDefault` appended with `urlSubpath`. +- If `resolved` is unknown, meaning neither the full path nor the dependency name were resolved, return `null`. +- If `resolved` is a directory or a file with extension of `scss`, `sass`, or `css`, return it. Otherwise: +- If `resolved` is a file with a name that is different than the name of `url`, return the file's containing directory. +- Return null. + +Note: The fifth step, returning the containing directory, is required to support instances where a dependency's default export is not a `sass`, `scss` or `css` file. + + +[Loading a Module]: ../spec/modules.md#loading-a-source-file + +## Deprecation Process + +The `package` url scheme supported in Dart is not part of the Sass spec, but is supported by the `dart-sass` implementation when running in Dart. This should be deprecated in favor of moving authors to the new `pkg` url scheme. Usage of the `package` syntax will result in a deprecation message, and a future major version of Sass should remove support. + +## Examples + +### Node + +For Bootstrap to support `pkg` imports, they have 2 options. They can add `_index.scss` to `dist/js`, as that is the containing folder for their main export, and use `@forward` to expose the files. + +``` +@forward './scss/bootstrap.scss'; +``` + +Alternatively, they could recommend using `@use 'pkg:bootstrap/scss';`, and add an `exports` key to their `package.json`, using node subpath exports. Opting into this format requires library authors to be exhaustive. Here, they are exposing the default js library, forwarding `./scss` and also exposing `./scss/bootstrap.scss` to not break existing imports of `@use 'bootstrap/scss/bootstrap.scss'`. + +``` +"exports": { + ".": "./dist/js/bootstrap.js", + "./scss": "./scss/bootstrap.scss", + "./scss/bootstrap.scss": "./scss/bootstrap.scss" +} +``` + +### Dart + +For Dart libraries to take advantage of this, they can place a file at `lib/_index.scss`. Other files within the `lib` folder would also be available for library consumers. For example, if a library `libraryName` contains `lib/themes/dark/_index.scss`, a consumer could write `@use 'pkg:libraryName/themes/dark'`. + +More examples can be found in the [Sass pkg: test] example repo. + +[Sass pkg: test]: https://github.com/oddbird/sass-pkg-test From a763eed8c3f32c39500b83889d20790660ed3cc2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 29 Jun 2023 22:58:04 -0400 Subject: [PATCH 02/38] Remove broken link --- proposal/package-importer.md | 1 - 1 file changed, 1 deletion(-) diff --git a/proposal/package-importer.md b/proposal/package-importer.md index b123d93556..31da3469d1 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -9,7 +9,6 @@ This proposal adds Sass support for a standard package importer, introducing a ` * [Background](#background) * [Summary](#summary) * [Design Decisions](#design-decisions) -* [Syntax](#syntax) * [Semantics](#semantics) * [Deprecation Process](#deprecation-process) From 9e8ab52a718340c2de4d474b8d994b7b551afd40 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 30 Jun 2023 11:48:06 -0400 Subject: [PATCH 03/38] wrap --- proposal/package-importer.md | 134 ++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/proposal/package-importer.md b/proposal/package-importer.md index 31da3469d1..a94a57ec84 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -2,7 +2,9 @@ *([Issue](https://github.com/sass/sass/issues/2739))* -This proposal adds Sass support for a standard package importer, introducing a `pkg` URL scheme to indicate Sass package imports in an implementation-agnostic format. +This proposal adds Sass support for a standard package importer, introducing a +`pkg` URL scheme to indicate Sass package imports in an implementation-agnostic +format. ## Table of contents @@ -16,93 +18,157 @@ This proposal adds Sass support for a standard package importer, introducing a ` > This section is non-normative. -Historically, Sass has not specified a standard method for using packages from dependencies. A number of domain-specific solutions exist using custom importers or by specifying a load path. This can lead to Sass code being written in a way that is tied to a specific domain and make it difficult to rely on dependencies. +Historically, Sass has not specified a standard method for using packages from +dependencies. A number of domain-specific solutions exist using custom importers +or by specifying a load path. This can lead to Sass code being written in a way +that is tied to a specific domain and make it difficult to rely on dependencies. ## Summary > This section is non-normative. -Sass users often need to use styles from a dependency to customize an existing theme or access styling utilities. +Sass users often need to use styles from a dependency to customize an existing +theme or access styling utilities. -This proposal defines a `pkg` URL scheme for usage with `@use` that directs an implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. +This proposal defines a `pkg` URL scheme for usage with `@use` that directs an +implementation to resolve a URL within a dependency. The implementation will +resolve the dependency URL using the standard resolution for that environment. +Once resolved, this URL will be loaded in the same way as any other `file:` URL. -For example, `@use 'pkg:bootstrap';` would be able to resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be resolved within `node_modules`, using the [node resolution algorithm]. In Dart, that would be resolved within `pub-cache`, using [package-config]. +For example, `@use 'pkg:bootstrap';` would be able to resolve to the path of a +library-defined export within the `bootstrap` dependency. In Node, that would be +resolved within `node_modules`, using the [node resolution algorithm]. In Dart, +that would be resolved within `pub-cache`, using [package-config]. [node module algorithm]: https://nodejs.org/api/packages.html [package-config]: https://pub.dev/packages/package_config -There will be tension between the different path resolution algorithms that are used. To address this, a `pkg:` url will only use the environment's resolution initially in order to resolve a starting path. After that, it will be handed off to the Sass algorithm, which works directly with the filesystem, to resolve index files and partials. +There will be tension between the different path resolution algorithms that are +used. To address this, a `pkg:` url will only use the environment's resolution +initially in order to resolve a starting path. After that, it will be handed off +to the Sass algorithm, which works directly with the filesystem, to resolve +index files and partials. -To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. +To better understand and allow for testing against the recommended algorithm, a +[Sass pkg: test] repository has been made with a rudimentary implementation of +the algorithm. [Sass pkg: test]: https://github.com/oddbird/sass-pkg-test ### Design Decisions -We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or 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 could add `node_modules` to the load path, but that again suffers from lack of clarity to the implementation and the reader. - -Some npm packages have adopted a convention of declaring their Sass entrypoints using `"style"` or `"sass"` keys in their `package.json`. This is tied to the npm context, and would require parsing package.json to find these files. Instead, each implementation should use the standard patterns and tooling (`require` for Node and `package_config` for Dart) for resolving file paths. This allows library authors control over what is public, which follows Sass's general design. - -This proposal does not add support for a [Node custom user condition]. Node currently does not provide a native way to set a condition on a single invocation of `require.resolve`, meaning we would need to implement our own `package.json` parsing similar to [Rollup] or add a dependency on a community solution like [resolve.exports]. However, this proposal will not prevent future support. +We could use the `~` popularized by Webpack's `load-sass` format, but this has +been deprecated since 2021. In addition, since this creates a URL that is +syntactically a relative URL, it does not make it clear to the implementation or +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 could add `node_modules` to +the load path, but that again suffers from lack of clarity to the implementation +and the reader. + +Some npm packages have adopted a convention of declaring their Sass entrypoints +using `"style"` or `"sass"` keys in their `package.json`. This is tied to the +npm context, and would require parsing package.json to find these files. +Instead, each implementation should use the standard patterns and tooling +(`require` for Node and `package_config` for Dart) for resolving file paths. +This allows library authors control over what is public, which follows Sass's +general design. + +This proposal does not add support for a [Node custom user condition]. Node +currently does not provide a native way to set a condition on a single +invocation of `require.resolve`, meaning we would need to implement our own +`package.json` parsing similar to [Rollup] or add a dependency on a community +solution like [resolve.exports]. However, this proposal will not prevent future +support. [Node custom user condition]: https://nodejs.org/api/packages.html#community-conditions-definitions [Rollup]: https://github.com/rollup/plugins/blob/master/packages/node-resolve/src/package/resolvePackageExports.js [resolve.exports]: https://github.com/lukeed/resolve.exports -A potential source of confusion is in instances where a dependency defines a default or main export that is not in the root folder. In these cases, Sass will look for index and partial files in the same folder as the default or main export, and not in the package root. +A potential source of confusion is in instances where a dependency defines a +default or main export that is not in the root folder. In these cases, Sass will +look for index and partial files in the same folder as the default or main +export, and not in the package root. -The `pkg` scheme will not be supported in the browser version of Dart Sass. To support a similar functionality, a user would need to ensure that files are served, and the loader would need to fetch the URL. In order to follow the same algorithm for [resolving a file: URL], we would need to make many fetches. If we instead require the browser version to have a fully resolved URL, we negate many of this spec's benefits. +The `pkg` scheme will not be supported in the browser version of Dart Sass. To +support a similar functionality, a user would need to ensure that files are +served, and the loader would need to fetch the URL. In order to follow the same +algorithm for [resolving a file: URL], we would need to make many fetches. If we +instead require the browser version to have a fully resolved URL, we negate many +of this spec's benefits. [resolving a file: URL]: ../spec/modules.md#resolving-a-file-url ## Semantics -This proposal defines a new step in the [Loading a Source File] in modules that implementations must include. +This proposal defines a new step in the [Loading a Source File] in modules that +implementations must include. -New step for [Loading a Source File], after "if argument is a relative URL" step- +New step for [Loading a Source File], after "if argument is a relative URL" +step: - If `argument` is a valid URL with scheme `pkg`: - Let `resolved` be the result of resolving a pkg url - - If `resolved` is not null, let `argument` equal `resolved`, and continue. Otherwise, return null. + - If `resolved` is not null, let `argument` equal `resolved`, and continue. + Otherwise, return null. [Loading a Source File]: ../spec/modules.md#loading-a-source-file ### Resolving a `pkg`: URL -This algorithm takes a URL, `url`, whose `scheme` must be `pkg` and returns either another URL of a file or directory on disk, or null. At this point, the URL is not guaranteed to resolve to a file or disk. +This algorithm takes a URL, `url`, whose `scheme` must be `pkg` and returns +either another URL of a file or directory on disk, or null. At this point, the +URL is not guaranteed to resolve to a file or disk. -- Let `resolved` be the result of the implementation's dependency resolution algorithm. +- Let `resolved` be the result of the implementation's dependency resolution + algorithm. - If resolution fails: - - Let `dependencyDefault` be the result of the implementation's dependency resolution algorithm for the dependency name. - - Let `urlSubpath` be the path segments of `url` that are not part of the dependency name. + - Let `dependencyDefault` be the result of the implementation's dependency + resolution algorithm for the dependency name. + - Let `urlSubpath` be the path segments of `url` that are not part of the + dependency name. - Let `resolved` be `dependencyDefault` appended with `urlSubpath`. -- If `resolved` is unknown, meaning neither the full path nor the dependency name were resolved, return `null`. -- If `resolved` is a directory or a file with extension of `scss`, `sass`, or `css`, return it. Otherwise: -- If `resolved` is a file with a name that is different than the name of `url`, return the file's containing directory. +- If `resolved` is unknown, meaning neither the full path nor the dependency + name were resolved, return `null`. +- If `resolved` is a directory or a file with extension of `scss`, `sass`, or + `css`, return it. Otherwise: +- If `resolved` is a file with a name that is different than the name of `url`, + return the file's containing directory. - Return null. -Note: The fifth step, returning the containing directory, is required to support instances where a dependency's default export is not a `sass`, `scss` or `css` file. - +Note: The fifth step, returning the containing directory, is required to support +instances where a dependency's default export is not a `sass`, `scss` or `css` +file. [Loading a Module]: ../spec/modules.md#loading-a-source-file ## Deprecation Process -The `package` url scheme supported in Dart is not part of the Sass spec, but is supported by the `dart-sass` implementation when running in Dart. This should be deprecated in favor of moving authors to the new `pkg` url scheme. Usage of the `package` syntax will result in a deprecation message, and a future major version of Sass should remove support. +The `package` url scheme supported in Dart is not part of the Sass spec, but is +supported by the `dart-sass` implementation when running in Dart. This should be +deprecated in favor of moving authors to the new `pkg` url scheme. Usage of the +`package` syntax will result in a deprecation message, and a future major +version of Sass should remove support. ## Examples ### Node -For Bootstrap to support `pkg` imports, they have 2 options. They can add `_index.scss` to `dist/js`, as that is the containing folder for their main export, and use `@forward` to expose the files. +For Bootstrap to support `pkg` imports, they have 2 options. They can add +`_index.scss` to `dist/js`, as that is the containing folder for their main +export, and use `@forward` to expose the files. ``` @forward './scss/bootstrap.scss'; ``` -Alternatively, they could recommend using `@use 'pkg:bootstrap/scss';`, and add an `exports` key to their `package.json`, using node subpath exports. Opting into this format requires library authors to be exhaustive. Here, they are exposing the default js library, forwarding `./scss` and also exposing `./scss/bootstrap.scss` to not break existing imports of `@use 'bootstrap/scss/bootstrap.scss'`. +Alternatively, they could recommend using `@use 'pkg:bootstrap/scss';`, and add +an `exports` key to their `package.json`, using node subpath exports. Opting +into this format requires library authors to be exhaustive. Here, they are +exposing the default js library, forwarding `./scss` and also exposing +`./scss/bootstrap.scss` to not break existing imports of `@use +'bootstrap/scss/bootstrap.scss'`. ``` "exports": { @@ -114,7 +180,11 @@ Alternatively, they could recommend using `@use 'pkg:bootstrap/scss';`, and add ### Dart -For Dart libraries to take advantage of this, they can place a file at `lib/_index.scss`. Other files within the `lib` folder would also be available for library consumers. For example, if a library `libraryName` contains `lib/themes/dark/_index.scss`, a consumer could write `@use 'pkg:libraryName/themes/dark'`. +For Dart libraries to take advantage of this, they can place a file at +`lib/_index.scss`. Other files within the `lib` folder would also be available +for library consumers. For example, if a library `libraryName` contains +`lib/themes/dark/_index.scss`, a consumer could write `@use +'pkg:libraryName/themes/dark'`. More examples can be found in the [Sass pkg: test] example repo. From 27523a2ac120ec88825f673fb337cbf908781b42 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 30 Jun 2023 11:48:18 -0400 Subject: [PATCH 04/38] fix link --- proposal/package-importer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/package-importer.md b/proposal/package-importer.md index a94a57ec84..9ab892feea 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -40,7 +40,7 @@ library-defined export within the `bootstrap` dependency. In Node, that would be resolved within `node_modules`, using the [node resolution algorithm]. In Dart, that would be resolved within `pub-cache`, using [package-config]. -[node module algorithm]: https://nodejs.org/api/packages.html +[node resolution algorithm]: https://nodejs.org/api/packages.html [package-config]: https://pub.dev/packages/package_config There will be tension between the different path resolution algorithms that are From 14b5df60a4becb03470e4b6a752b89eede2eee1d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 11 Jul 2023 11:50:06 -0400 Subject: [PATCH 05/38] Package importer 2nd draft --- js-api-doc/options.d.ts | 10 ++ proposal/package-importer.md | 235 ++++++++++++++++++++++------------- spec/js-api/options.d.ts.md | 13 ++ 3 files changed, 169 insertions(+), 89 deletions(-) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index ce8afe9549..728accf5f3 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -332,6 +332,16 @@ export interface Options { */ style?: OutputStyle; + /** + * Whether or not to enable the built-in package importer to resolve any url + * with the `pkg` scheme. + * + * @defaultValue `false` + * @category Input + */ + + usePkgImporter?: boolean; + /** * By default, Dart Sass will print only five instances of the same * deprecation warning per compilation to avoid deluging users in console diff --git a/proposal/package-importer.md b/proposal/package-importer.md index 9ab892feea..f07cda8289 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -43,12 +43,6 @@ that would be resolved within `pub-cache`, using [package-config]. [node resolution algorithm]: https://nodejs.org/api/packages.html [package-config]: https://pub.dev/packages/package_config -There will be tension between the different path resolution algorithms that are -used. To address this, a `pkg:` url will only use the environment's resolution -initially in order to resolve a starting path. After that, it will be handed off -to the Sass algorithm, which works directly with the filesystem, to resolve -index files and partials. - To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. @@ -60,88 +54,127 @@ the algorithm. We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or -the reader where to find the file. +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 could add `node_modules` to -the load path, but that again suffers from lack of clarity to the implementation -and the reader. - -Some npm packages have adopted a convention of declaring their Sass entrypoints -using `"style"` or `"sass"` keys in their `package.json`. This is tied to the -npm context, and would require parsing package.json to find these files. -Instead, each implementation should use the standard patterns and tooling -(`require` for Node and `package_config` for Dart) for resolving file paths. -This allows library authors control over what is public, which follows Sass's -general design. - -This proposal does not add support for a [Node custom user condition]. Node -currently does not provide a native way to set a condition on a single -invocation of `require.resolve`, meaning we would need to implement our own -`package.json` parsing similar to [Rollup] or add a dependency on a community -solution like [resolve.exports]. However, this proposal will not prevent future -support. - -[Node custom user condition]: https://nodejs.org/api/packages.html#community-conditions-definitions -[Rollup]: https://github.com/rollup/plugins/blob/master/packages/node-resolve/src/package/resolvePackageExports.js -[resolve.exports]: https://github.com/lukeed/resolve.exports - -A potential source of confusion is in instances where a dependency defines a -default or main export that is not in the root folder. In these cases, Sass will -look for index and partial files in the same folder as the default or main -export, and not in the package root. +scheme, a similar standard doesn't exist in Node. We choose the `pkg:` url +scheme as it is clearly communicates to both the user and compiler, and does not +have known conflicts in the ecosystem. The `pkg` scheme will not be supported in the browser version of Dart Sass. To support a similar functionality, a user would need to ensure that files are served, and the loader would need to fetch the URL. In order to follow the same algorithm for [resolving a file: URL], we would need to make many fetches. If we instead require the browser version to have a fully resolved URL, we negate many -of this spec's benefits. +of this spec's benefits. Users may write their own custom importers to fit their +needs. [resolving a file: URL]: ../spec/modules.md#resolving-a-file-url -## Semantics +The `pkg` import loader will be exposed through an opt-in option as it adds the +potential for file system interaction to `compileString` and +`compileStringAsync`. + +#### Node resolution descisions + +The current recommendation for resolving packages in node is to add +`node_modules` to the load paths. We could add `node_modules` to the load path +by default, but that lacks clarity to the implementation and the reader. In +addition, a file may have access to multiple `node_module` directories, and +different files may have access to different `node_module` directories in the +same compilation. -This proposal defines a new step in the [Loading a Source File] in modules that -implementations must include. +There are a variety of methods currently in use for specifying a location of the +default Sass export for npm packages. For the most part, packages contain both +Javascript and styles, and use the `main` or `module` root keys to define the +Javascript entry point. Some packages use the `"sass"` key at the root of their +`package.json`. Others have adopted [conditional exports], which is supported by +Vite. -New step for [Loading a Source File], after "if argument is a relative URL" -step: +[conditional exports]: https://nodejs.org/api/packages.html#conditional-exports + +Because conditional exports is flexible and recommended for modern packages, +this will be the primary method used within Node. We will support both the +`"sass"` and the `"style"` conditions, as Sass can also use the CSS exports +exposed through `"style"`. + +## Semantics -- If `argument` is a valid URL with scheme `pkg`: - - Let `resolved` be the result of resolving a pkg url - - If `resolved` is not null, let `argument` equal `resolved`, and continue. - Otherwise, return null. +This proposal defines a new importer that implementations should make available. +It will be disabled by default. The loader will handle URLs: -[Loading a Source File]: ../spec/modules.md#loading-a-source-file +- with the scheme `pkg` +- followed by `:` +- followed by a package name +- optionally followed by a path, with path segments separated with a forward + slash. ### Resolving a `pkg`: URL -This algorithm takes a URL, `url`, whose `scheme` must be `pkg` and returns -either another URL of a file or directory on disk, or null. At this point, the -URL is not guaranteed to resolve to a file or disk. - -- Let `resolved` be the result of the implementation's dependency resolution - algorithm. -- If resolution fails: - - Let `dependencyDefault` be the result of the implementation's dependency - resolution algorithm for the dependency name. - - Let `urlSubpath` be the path segments of `url` that are not part of the - dependency name. - - Let `resolved` be `dependencyDefault` appended with `urlSubpath`. -- If `resolved` is unknown, meaning neither the full path nor the dependency - name were resolved, return `null`. -- If `resolved` is a directory or a file with extension of `scss`, `sass`, or - `css`, return it. Otherwise: -- If `resolved` is a file with a name that is different than the name of `url`, - return the file's containing directory. -- Return null. - -Note: The fifth step, returning the containing directory, is required to support -instances where a dependency's default export is not a `sass`, `scss` or `css` -file. - -[Loading a Module]: ../spec/modules.md#loading-a-source-file +The `pkg importer` will provide a method to canonicalize a `pkg:` URL, and +extend the implementation's existing File Importer. +### Platform specific semantics + +#### Dart + +Given `url` with the format `pkg:.*`: + +- Let `path` be `url` without the `pkg` scheme +- Use [package-config] to resolve `path` + +[package-config]: https://pub.dev/packages/package_config + +#### Node + +Given `url` with the format `pkg:.*`: + +- Let `fullPath` be `url` without the `pkg` scheme +- Let `resolved` be the result of using [require.exports] to resolve `fullPath` + with the `sass` condition set + - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or + `css`, return it. +- Let `resolved` be the result of using [require.exports] to resolve `fullPath` + with the `style` condition set. + - If `resolved` has the scheme `file:` and an extension of `css`, return it. +- Let `packageName` be the package identifier, and `subPath` be the path without + the package identifier. +- If `subPath` is empty, return result of `[resolving pkg root]`. +- Otherwise, return result of `[resolving pkg subpath]`. + +[resolving pkg root]: #resolving-pkg-root +[resolving pkg subpath]: #resolving-pkg-subpath +#### Resolving pkg root + +- Let `packagePath` be the file path to the package root. +- Let `sassValue` be the value of `sass` in the `package.json` at the pkg root. +- If `sassValue` is a relative path with an extension of `sass`, `scss` or + `css`, return the `packagePath` appended with `sassValue`. +- Let `styleValue` be the value of `style` in the `package.json` at the pkg + root. +- If `styleValue` is a relative path with an extension of `css`, return the + `packagePath` appended with `styleValue`. +- Otherwise return the result of [resolving a file url] with `packagePath`. + +[resolving a file url]: ../spec/modules.md#resolving-a-file-url +#### Resolving pkg subpath + +- Let `packagePath` be the file path to the package root. +- Let `fullPath` be `subpath` resolved relative to `packagePath`. +- Return the result of [resolving a file url] with `fullPath`. + +[resolving a file url]: ../spec/modules.md#resolving-a-file-url +#### Resolution order + +This algorithm resolves in the following order: + +1. `sass` condition in package.json `exports` +1. `style` condition in package.json `exports` +1. If no subpath, then find root export- + 1. `sass` key at package.json root + 1. `style` key at package.json root + 1. `index` file at package root, resolved for file extensions and partials +1. If there is a subpath, resolve that path relative to the package root, and + resolve for file extensions and partials ## Deprecation Process @@ -155,29 +188,26 @@ version of Sass should remove support. ### Node -For Bootstrap to support `pkg` imports, they have 2 options. They can add -`_index.scss` to `dist/js`, as that is the containing folder for their main -export, and use `@forward` to expose the files. - -``` -@forward './scss/bootstrap.scss'; +For library creators, the recommended method is to add a `sass` conditional +export key as the first key in `package.json`. + +```json +{ + "exports": { + ".": { + "sass": "./dist/scss/index.scss", + "import": "./dist/js/index.mjs", + "require": "./dist/js/index.js" + } + } +} ``` -Alternatively, they could recommend using `@use 'pkg:bootstrap/scss';`, and add -an `exports` key to their `package.json`, using node subpath exports. Opting -into this format requires library authors to be exhaustive. Here, they are -exposing the default js library, forwarding `./scss` and also exposing -`./scss/bootstrap.scss` to not break existing imports of `@use -'bootstrap/scss/bootstrap.scss'`. +Then, library consumers can use the pkg syntax to get the default export. +```sass +@use "pkg:library" ``` -"exports": { - ".": "./dist/js/bootstrap.js", - "./scss": "./scss/bootstrap.scss", - "./scss/bootstrap.scss": "./scss/bootstrap.scss" -} -``` - ### Dart For Dart libraries to take advantage of this, they can place a file at @@ -189,3 +219,30 @@ for library consumers. For example, if a library `libraryName` contains More examples can be found in the [Sass pkg: test] example repo. [Sass pkg: test]: https://github.com/oddbird/sass-pkg-test + +## Ecosystem notes + +Vite is currently using the Legacy JS API, and has an [open issue] to update to +the modern API. They also do not expose Sass options to the user, so would need +to enable the `usePkgImporter` on their behalf or expose some configuration. + +[open issue]: https://github.com/vitejs/vite/issues/7116 + +Webpack's [sass-loader] allows users to opt in to the modern API and exposes +Sass options to users. + +For Rollup, [rollup-plugin-sass] uses the Legacy JS API. They do expose Sass +options to the user. + +[rollup-plugin-sass]: https://github.com/elycruz/rollup-plugin-sass + +It may be worth adding a [Community Conditions Definition] to the Node +Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway +in standardizing the usage of custom conditions for runtimes, but Sass doesn't +cleanly fit into that specification. + +[Community Conditions Definition]: + https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[WinterCG]: https://wintercg.org/ +[Runtime Keys proposal specification]: + https://runtime-keys.proposal.wintercg.org/#adding-a-key diff --git a/spec/js-api/options.d.ts.md b/spec/js-api/options.d.ts.md index fc1c09eb1e..ff3e397a51 100644 --- a/spec/js-api/options.d.ts.md +++ b/spec/js-api/options.d.ts.md @@ -30,6 +30,7 @@ import {PromiseOr} from './util/promise_or'; * [`sourceMap`](#sourcemap) * [`sourceMapIncludeSources`](#sourcemapincludesources) * [`style`](#style) + * [`usePkgImporter`](#usepkgimporter) * [`verbose`](#verbose) * [`StringOptionsWithoutImporter`](#stringoptionswithoutimporter) * [`syntax`](#syntax) @@ -239,6 +240,18 @@ Implementations may support any subset of `OutputStyle`s, provided that: style?: OutputStyle; ``` +#### `usePkgImporter` + +If true, the compiler will use the built-in [package importer] to resolve any url with the `pkg` scheme. + +[package importer]: ../../proposal/package-importer.md + +Defaults to false. + +```ts +usePkgImporter?: boolean; +``` + #### `verbose` If true, the compiler must print every single deprecation warning it encounters From 2781c92f41fc30bb04e04a328f71a4b00fe9221d Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Thu, 13 Jul 2023 14:44:51 -0400 Subject: [PATCH 06/38] review --- js-api-doc/options.d.ts | 1 - proposal/package-importer.md | 92 ++++++++++++++++++++---------------- spec/js-api/options.d.ts.md | 5 +- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index 27f061ba8e..ae7e27d2fe 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -337,7 +337,6 @@ export interface Options { * @defaultValue `false` * @category Input */ - usePkgImporter?: boolean; /** diff --git a/proposal/package-importer.md b/proposal/package-importer.md index f07cda8289..9d375ff2b8 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -11,8 +11,20 @@ format. * [Background](#background) * [Summary](#summary) * [Design Decisions](#design-decisions) + * [Node Resolution Decisions](#node-resolution-decisions) * [Semantics](#semantics) + * [Resolving a `pkg:` URL](#resolving-a-pkg-url) + * [Platform-Specific Semantics](#platform-specific-semantics) + * [Dart](#dart) + * [Node](#node) + * [Resolving `pkg` Root](#resolving-pkg-root) + * [Resolving `pkg` Subpath](#resolving-pkg-subpath) + * [Resolution Order](#resolution-order) * [Deprecation Process](#deprecation-process) +* [Examples](#examples) + * [Node](#node-1) + * [Dart](#dart-1) +* [Ecosystem Notes](#ecosystem-notes) ## Background @@ -35,9 +47,9 @@ implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. -For example, `@use 'pkg:bootstrap';` would be able to resolve to the path of a +For example, `@use "pkg:bootstrap";` would resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be -resolved within `node_modules`, using the [node resolution algorithm]. In Dart, +resolved within `node_modules`, using the [Node resolution algorithm]. In Dart, that would be resolved within `pub-cache`, using [package-config]. [node resolution algorithm]: https://nodejs.org/api/packages.html @@ -54,12 +66,12 @@ the algorithm. We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or -the reader where to find the file. +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 choose the `pkg:` url -scheme as it is clearly communicates to both the user and compiler, and does not -have known conflicts in the ecosystem. +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, and does not have +known conflicts in the ecosystem. The `pkg` scheme will not be supported in the browser version of Dart Sass. To support a similar functionality, a user would need to ensure that files are @@ -75,28 +87,28 @@ The `pkg` import loader will be exposed through an opt-in option as it adds the potential for file system interaction to `compileString` and `compileStringAsync`. -#### Node resolution descisions +#### Node Resolution Decisions -The current recommendation for resolving packages in node is to add -`node_modules` to the load paths. We could add `node_modules` to the load path +The current recommendation for resolving packages in Node is to add +`node_modules` to the load paths. We could add `node_modules` to the load paths by default, but that lacks clarity to the implementation and the reader. In -addition, a file may have access to multiple `node_module` directories, and -different files may have access to different `node_module` directories in the +addition, a file may have access to multiple `node_modules` directories, and +different files may have access to different `node_modules` directories in the same compilation. There are a variety of methods currently in use for specifying a location of the default Sass export for npm packages. For the most part, packages contain both -Javascript and styles, and use the `main` or `module` root keys to define the -Javascript entry point. Some packages use the `"sass"` key at the root of their +JavaScript and styles, and use the `main` or `module` root keys to define the +JavaScript entry point. Some packages use the `"sass"` key at the root of their `package.json`. Others have adopted [conditional exports], which is supported by -Vite. +Vite. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports -Because conditional exports is flexible and recommended for modern packages, -this will be the primary method used within Node. We will support both the -`"sass"` and the `"style"` conditions, as Sass can also use the CSS exports -exposed through `"style"`. +Because use of conditional exports is flexible and recommended for modern +packages, this will be the primary method used within Node. We will support both +the `"sass"` and the `"style"` conditions, as Sass can also use the CSS exports +exposed through `"style"`. ## Semantics @@ -109,11 +121,12 @@ It will be disabled by default. The loader will handle URLs: - optionally followed by a path, with path segments separated with a forward slash. -### Resolving a `pkg`: URL +### Resolving a `pkg:` URL The `pkg importer` will provide a method to canonicalize a `pkg:` URL, and extend the implementation's existing File Importer. -### Platform specific semantics + +### Platform-Specific Semantics #### Dart @@ -129,21 +142,23 @@ Given `url` with the format `pkg:.*`: Given `url` with the format `pkg:.*`: - Let `fullPath` be `url` without the `pkg` scheme -- Let `resolved` be the result of using [require.exports] to resolve `fullPath` +- Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` with the `sass` condition set - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or `css`, return it. -- Let `resolved` be the result of using [require.exports] to resolve `fullPath` +- Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` with the `style` condition set. - If `resolved` has the scheme `file:` and an extension of `css`, return it. - Let `packageName` be the package identifier, and `subPath` be the path without the package identifier. -- If `subPath` is empty, return result of `[resolving pkg root]`. -- Otherwise, return result of `[resolving pkg subpath]`. +- If `subPath` is empty, return result of [resolving `pkg` root]. +- Otherwise, return result of [resolving `pkg` subpath]. +[resolve.exports]: https://github.com/lukeed/resolve.exports [resolving pkg root]: #resolving-pkg-root [resolving pkg subpath]: #resolving-pkg-subpath -#### Resolving pkg root + +#### Resolving `pkg` Root - Let `packagePath` be the file path to the package root. - Let `sassValue` be the value of `sass` in the `package.json` at the pkg root. @@ -156,20 +171,20 @@ Given `url` with the format `pkg:.*`: - Otherwise return the result of [resolving a file url] with `packagePath`. [resolving a file url]: ../spec/modules.md#resolving-a-file-url -#### Resolving pkg subpath + +#### Resolving `pkg` Subpath - Let `packagePath` be the file path to the package root. - Let `fullPath` be `subpath` resolved relative to `packagePath`. - Return the result of [resolving a file url] with `fullPath`. -[resolving a file url]: ../spec/modules.md#resolving-a-file-url -#### Resolution order +#### Resolution Order This algorithm resolves in the following order: 1. `sass` condition in package.json `exports` 1. `style` condition in package.json `exports` -1. If no subpath, then find root export- +1. If no subpath, then find root export: 1. `sass` key at package.json root 1. `style` key at package.json root 1. `index` file at package root, resolved for file extensions and partials @@ -205,22 +220,21 @@ export key as the first key in `package.json`. Then, library consumers can use the pkg syntax to get the default export. -```sass -@use "pkg:library" +```scss +@use "pkg:library"; ``` + ### Dart For Dart libraries to take advantage of this, they can place a file at `lib/_index.scss`. Other files within the `lib` folder would also be available for library consumers. For example, if a library `libraryName` contains `lib/themes/dark/_index.scss`, a consumer could write `@use -'pkg:libraryName/themes/dark'`. - -More examples can be found in the [Sass pkg: test] example repo. +"pkg:libraryName/themes/dark";`. -[Sass pkg: test]: https://github.com/oddbird/sass-pkg-test +More examples can be found in the [Sass pkg: test] example repo. -## Ecosystem notes +## Ecosystem Notes Vite is currently using the Legacy JS API, and has an [open issue] to update to the modern API. They also do not expose Sass options to the user, so would need @@ -241,8 +255,6 @@ Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't cleanly fit into that specification. -[Community Conditions Definition]: - https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[Community Conditions Definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions [WinterCG]: https://wintercg.org/ -[Runtime Keys proposal specification]: - https://runtime-keys.proposal.wintercg.org/#adding-a-key +[Runtime Keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key diff --git a/spec/js-api/options.d.ts.md b/spec/js-api/options.d.ts.md index ff3e397a51..ec4adc20f3 100644 --- a/spec/js-api/options.d.ts.md +++ b/spec/js-api/options.d.ts.md @@ -145,7 +145,7 @@ throwing an error. The following values are considered invalid: * A `SassFunction` whose `signature` field isn't a valid Sass function signature that could appear after the `@function` directive in a Sass stylesheet. - + ```ts functions?: Record>; ``` @@ -242,7 +242,8 @@ style?: OutputStyle; #### `usePkgImporter` -If true, the compiler will use the built-in [package importer] to resolve any url with the `pkg` scheme. +If true, the compiler will use the built-in [package importer] to resolve any +url with the `pkg` scheme. [package importer]: ../../proposal/package-importer.md From 856e4c7703d54b69676d2c9a0792b86f7362dc1e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 13 Jul 2023 16:52:54 -0400 Subject: [PATCH 07/38] Clarify Vite comment --- proposal/package-importer.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proposal/package-importer.md b/proposal/package-importer.md index 9d375ff2b8..cb77de6b7c 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -100,8 +100,9 @@ There are a variety of methods currently in use for specifying a location of the default Sass export for npm packages. For the most part, packages contain both JavaScript and styles, and use the `main` or `module` root keys to define the JavaScript entry point. Some packages use the `"sass"` key at the root of their -`package.json`. Others have adopted [conditional exports], which is supported by -Vite. +`package.json`. Other packages have adopted [conditional exports], largely +driven by Vite, which resolves Sass paths using the `"sass"` and the `"style"` +custom conditions. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports From 4bfa2fb54ba399faf9fefa104b654c6abaf5242b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 14 Jul 2023 12:25:01 -0400 Subject: [PATCH 08/38] review response --- proposal/package-importer.md | 142 +++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/proposal/package-importer.md b/proposal/package-importer.md index cb77de6b7c..23c2a131ef 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.md @@ -55,6 +55,54 @@ that would be resolved within `pub-cache`, using [package-config]. [node resolution algorithm]: https://nodejs.org/api/packages.html [package-config]: https://pub.dev/packages/package_config +### Resolution Order + +This algorithm resolves in the following order: + +1. `sass` condition in package.json `exports` +2. `style` condition in package.json `exports` +3. If no subpath, then find root export: + 1. `sass` key at package.json root + 2. `style` key at package.json root + 3. `index` file at package root, resolved for file extensions and partials +4. If there is a subpath, resolve that path relative to the package root, and + resolve for file extensions and partials + +### Examples + +#### Node + +For library creators, the recommended method is to add a `sass` conditional +export key as the first key in `package.json`. + +```json +{ + "exports": { + ".": { + "sass": "./dist/scss/index.scss", + "import": "./dist/js/index.mjs", + "require": "./dist/js/index.js" + } + } +} +``` + +Then, library consumers can use the pkg syntax to get the default export. + +```scss +@use "pkg:library"; +``` + +#### Dart + +For Dart libraries to take advantage of this, they can place a file at +`lib/_index.scss`. Other files within the `lib` folder would also be available +for library consumers. For example, if a library `libraryName` contains +`lib/themes/dark/_index.scss`, a consumer could write `@use +"pkg:libraryName/themes/dark";`. + +More examples can be found in the [Sass pkg: test] example repo. + To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. @@ -63,6 +111,7 @@ the algorithm. ### Design Decisions +#### Using a `pkg` url scheme We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or @@ -73,19 +122,25 @@ 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, and does not have known conflicts in the ecosystem. -The `pkg` scheme will not be supported in the browser version of Dart Sass. To -support a similar functionality, a user would need to ensure that files are -served, and the loader would need to fetch the URL. In order to follow the same -algorithm for [resolving a file: URL], we would need to make many fetches. If we -instead require the browser version to have a fully resolved URL, we negate many -of this spec's benefits. Users may write their own custom importers to fit their -needs. +#### No built-in `pkg` resolver for browsers + +Dart Sass will not provide a built-in resolver for browsers to use the `pkg` +scheme. To support a similar functionality, a user would need to ensure that +files are served, and the loader would need to fetch the URL. In order to follow +the same algorithm for [resolving a file: URL], we would need to make many +fetches. If we instead require the browser version to have a fully resolved URL, +we negate many of this spec's benefits. Users may write their own custom +importers to fit their needs. [resolving a file: URL]: ../spec/modules.md#resolving-a-file-url -The `pkg` import loader will be exposed through an opt-in option as it adds the +#### Available as an opt-in setting + +The `pkg` import loader will be exposed through an opt-in setting as it adds the potential for file system interaction to `compileString` and -`compileStringAsync`. +`compileStringAsync`. Specifically, we want people who invoke Sass compilation +functions to have control over what files get accessed, and there's even a risk +of leaking file contents in error messages. #### Node Resolution Decisions @@ -101,15 +156,18 @@ default Sass export for npm packages. For the most part, packages contain both JavaScript and styles, and use the `main` or `module` root keys to define the JavaScript entry point. Some packages use the `"sass"` key at the root of their `package.json`. Other packages have adopted [conditional exports], largely -driven by Vite, which resolves Sass paths using the `"sass"` and the `"style"` +driven by [Vite], which resolves Sass paths using the `"sass"` and the `"style"` custom conditions. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports +[Vite]: https://github.com/vitejs/vite/pull/7817 Because use of conditional exports is flexible and recommended for modern packages, this will be the primary method used within Node. We will support both the `"sass"` and the `"style"` conditions, as Sass can also use the CSS exports -exposed through `"style"`. +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"`. ## Semantics @@ -117,7 +175,6 @@ This proposal defines a new importer that implementations should make available. It will be disabled by default. The loader will handle URLs: - with the scheme `pkg` -- followed by `:` - followed by a package name - optionally followed by a path, with path segments separated with a forward slash. @@ -133,7 +190,7 @@ extend the implementation's existing File Importer. Given `url` with the format `pkg:.*`: -- Let `path` be `url` without the `pkg` scheme +- Let `path` be `url`'s path - Use [package-config] to resolve `path` [package-config]: https://pub.dev/packages/package_config @@ -142,14 +199,15 @@ Given `url` with the format `pkg:.*`: Given `url` with the format `pkg:.*`: -- Let `fullPath` be `url` without the `pkg` scheme +- Let `fullPath` be `url`'s path - Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` with the `sass` condition set - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or `css`, return it. - Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` with the `style` condition set. - - If `resolved` has the scheme `file:` and an extension of `css`, return it. + - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or + `css`, return it. - Let `packageName` be the package identifier, and `subPath` be the path without the package identifier. - If `subPath` is empty, return result of [resolving `pkg` root]. @@ -179,19 +237,6 @@ Given `url` with the format `pkg:.*`: - Let `fullPath` be `subpath` resolved relative to `packagePath`. - Return the result of [resolving a file url] with `fullPath`. -#### Resolution Order - -This algorithm resolves in the following order: - -1. `sass` condition in package.json `exports` -1. `style` condition in package.json `exports` -1. If no subpath, then find root export: - 1. `sass` key at package.json root - 1. `style` key at package.json root - 1. `index` file at package root, resolved for file extensions and partials -1. If there is a subpath, resolve that path relative to the package root, and - resolve for file extensions and partials - ## Deprecation Process The `package` url scheme supported in Dart is not part of the Sass spec, but is @@ -200,41 +245,6 @@ deprecated in favor of moving authors to the new `pkg` url scheme. Usage of the `package` syntax will result in a deprecation message, and a future major version of Sass should remove support. -## Examples - -### Node - -For library creators, the recommended method is to add a `sass` conditional -export key as the first key in `package.json`. - -```json -{ - "exports": { - ".": { - "sass": "./dist/scss/index.scss", - "import": "./dist/js/index.mjs", - "require": "./dist/js/index.js" - } - } -} -``` - -Then, library consumers can use the pkg syntax to get the default export. - -```scss -@use "pkg:library"; -``` - -### Dart - -For Dart libraries to take advantage of this, they can place a file at -`lib/_index.scss`. Other files within the `lib` folder would also be available -for library consumers. For example, if a library `libraryName` contains -`lib/themes/dark/_index.scss`, a consumer could write `@use -"pkg:libraryName/themes/dark";`. - -More examples can be found in the [Sass pkg: test] example repo. - ## Ecosystem Notes Vite is currently using the Legacy JS API, and has an [open issue] to update to @@ -256,6 +266,8 @@ Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't cleanly fit into that specification. -[Community Conditions Definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[Community Conditions Definition]: + https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions [WinterCG]: https://wintercg.org/ -[Runtime Keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key +[Runtime Keys proposal specification]: + https://runtime-keys.proposal.wintercg.org/#adding-a-key From 7b30f4933392fd80a37fd6fbe89b8137b1bad44b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 17 Jul 2023 10:07:53 -0400 Subject: [PATCH 09/38] Move type into package-importer --- ...e-importer.md => package-importer.d.ts.md} | 25 +++++++++++++++++++ spec/js-api/options.d.ts.md | 14 ----------- 2 files changed, 25 insertions(+), 14 deletions(-) rename proposal/{package-importer.md => package-importer.d.ts.md} (94%) diff --git a/proposal/package-importer.md b/proposal/package-importer.d.ts.md similarity index 94% rename from proposal/package-importer.md rename to proposal/package-importer.d.ts.md index 23c2a131ef..e366d24fa7 100644 --- a/proposal/package-importer.md +++ b/proposal/package-importer.d.ts.md @@ -169,6 +169,31 @@ 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"`. +## Types + +#### `usePkgImporter` + +If true, the compiler will use the built-in package importer to resolve any +url with the `pkg` scheme. This importer follows Node.js logic to locate Sass files. + +Defaults to false. + +```ts +declare module '../spec/js-api' { + interface Options { + /** + * Whether or not to enable the built-in package importer to resolve any url + * with the `pkg` scheme. This importer follows Node.js resolution logic + * + * @defaultValue `false` + * @category Input + */ + usePkgImporter?: boolean; + } +} +``` + + ## Semantics This proposal defines a new importer that implementations should make available. diff --git a/spec/js-api/options.d.ts.md b/spec/js-api/options.d.ts.md index ec4adc20f3..374744d5ed 100644 --- a/spec/js-api/options.d.ts.md +++ b/spec/js-api/options.d.ts.md @@ -30,7 +30,6 @@ import {PromiseOr} from './util/promise_or'; * [`sourceMap`](#sourcemap) * [`sourceMapIncludeSources`](#sourcemapincludesources) * [`style`](#style) - * [`usePkgImporter`](#usepkgimporter) * [`verbose`](#verbose) * [`StringOptionsWithoutImporter`](#stringoptionswithoutimporter) * [`syntax`](#syntax) @@ -240,19 +239,6 @@ Implementations may support any subset of `OutputStyle`s, provided that: style?: OutputStyle; ``` -#### `usePkgImporter` - -If true, the compiler will use the built-in [package importer] to resolve any -url with the `pkg` scheme. - -[package importer]: ../../proposal/package-importer.md - -Defaults to false. - -```ts -usePkgImporter?: boolean; -``` - #### `verbose` If true, the compiler must print every single deprecation warning it encounters From 358123a6510fc3ba069a916a4f89c566eba710b2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 17 Jul 2023 10:27:42 -0400 Subject: [PATCH 10/38] Include md.d.ts files in toc scripts --- proposal/package-importer.d.ts.md | 22 ++++++++++++++-------- tool/toc.ts | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index e366d24fa7..81a62c536b 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -6,26 +6,32 @@ This proposal adds Sass support for a standard package importer, introducing a `pkg` URL scheme to indicate Sass package imports in an implementation-agnostic format. -## Table of contents +## Table of Contents * [Background](#background) * [Summary](#summary) + * [Resolution Order](#resolution-order) + * [Examples](#examples) + * [Node](#node) + * [Dart](#dart) * [Design Decisions](#design-decisions) + * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) + * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) + * [Available as an opt-in setting](#available-as-an-opt-in-setting) * [Node Resolution Decisions](#node-resolution-decisions) +* [Types](#types) + * [`usePkgImporter`](#usepkgimporter) * [Semantics](#semantics) * [Resolving a `pkg:` URL](#resolving-a-pkg-url) * [Platform-Specific Semantics](#platform-specific-semantics) - * [Dart](#dart) - * [Node](#node) + * [Dart](#dart-1) + * [Node](#node-1) * [Resolving `pkg` Root](#resolving-pkg-root) * [Resolving `pkg` Subpath](#resolving-pkg-subpath) - * [Resolution Order](#resolution-order) * [Deprecation Process](#deprecation-process) -* [Examples](#examples) - * [Node](#node-1) - * [Dart](#dart-1) * [Ecosystem Notes](#ecosystem-notes) + ## Background > This section is non-normative. @@ -183,7 +189,7 @@ declare module '../spec/js-api' { interface Options { /** * Whether or not to enable the built-in package importer to resolve any url - * with the `pkg` scheme. This importer follows Node.js resolution logic + * with the `pkg` scheme. This importer follows Node.js resolution logic. * * @defaultValue `false` * @category Input diff --git a/tool/toc.ts b/tool/toc.ts index ddea09abaa..145cb3fe5c 100644 --- a/tool/toc.ts +++ b/tool/toc.ts @@ -2,7 +2,7 @@ import * as glob from 'glob'; import markdownToc = require('markdown-toc'); /** Files that may contain tables of contents. */ -export const files = glob.sync('**/*.md', { +export const files = glob.sync('**/*.md?(.d.ts)', { ignore: ['node_modules/**/*.md', '**/*.changes.md'], }); From 3c3af5e1d4ada0bb15ae66c857ab9f2daca65dc2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 20 Jul 2023 22:07:25 -0400 Subject: [PATCH 11/38] Review response --- proposal/package-importer.d.ts.md | 166 ++++++++++++++++++++---------- 1 file changed, 110 insertions(+), 56 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 81a62c536b..6ca4fa1ff7 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -1,6 +1,6 @@ # Package Importer -*([Issue](https://github.com/sass/sass/issues/2739))* +_([Issue](https://github.com/sass/sass/issues/2739))_ This proposal adds Sass support for a standard package importer, introducing a `pkg` URL scheme to indicate Sass package imports in an implementation-agnostic @@ -10,7 +10,6 @@ format. * [Background](#background) * [Summary](#summary) - * [Resolution Order](#resolution-order) * [Examples](#examples) * [Node](#node) * [Dart](#dart) @@ -22,16 +21,16 @@ format. * [Types](#types) * [`usePkgImporter`](#usepkgimporter) * [Semantics](#semantics) - * [Resolving a `pkg:` URL](#resolving-a-pkg-url) * [Platform-Specific Semantics](#platform-specific-semantics) * [Dart](#dart-1) - * [Node](#node-1) - * [Resolving `pkg` Root](#resolving-pkg-root) + * [Node package importer](#node-package-importer) + * [Resolving `pkg` root values](#resolving-pkg-root-values) * [Resolving `pkg` Subpath](#resolving-pkg-subpath) + * [Resolving a package name](#resolving-a-package-name) + * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) * [Deprecation Process](#deprecation-process) * [Ecosystem Notes](#ecosystem-notes) - ## Background > This section is non-normative. @@ -53,6 +52,8 @@ implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. +This proposal also defines a built-in Node.js importer. + For example, `@use "pkg:bootstrap";` would resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be resolved within `node_modules`, using the [Node resolution algorithm]. In Dart, @@ -61,23 +62,21 @@ that would be resolved within `pub-cache`, using [package-config]. [node resolution algorithm]: https://nodejs.org/api/packages.html [package-config]: https://pub.dev/packages/package_config -### Resolution Order +### Examples + +#### Node -This algorithm resolves in the following order: +The built-in Node importer resolves in the following order: 1. `sass` condition in package.json `exports` 2. `style` condition in package.json `exports` 3. If no subpath, then find root export: - 1. `sass` key at package.json root - 2. `style` key at package.json root - 3. `index` file at package root, resolved for file extensions and partials -4. If there is a subpath, resolve that path relative to the package root, and +4. `sass` key at package.json root +5. `style` key at package.json root +6. `index` file at package root, resolved for file extensions and partials +7. If there is a subpath, resolve that path relative to the package root, and resolve for file extensions and partials -### Examples - -#### Node - For library creators, the recommended method is to add a `sass` conditional export key as the first key in `package.json`. @@ -96,28 +95,28 @@ export key as the first key in `package.json`. Then, library consumers can use the pkg syntax to get the default export. ```scss -@use "pkg:library"; +@use 'pkg:library'; ``` -#### Dart - -For Dart libraries to take advantage of this, they can place a file at -`lib/_index.scss`. Other files within the `lib` folder would also be available -for library consumers. For example, if a library `libraryName` contains -`lib/themes/dark/_index.scss`, a consumer could write `@use -"pkg:libraryName/themes/dark";`. - More examples can be found in the [Sass pkg: test] example repo. To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. -[Sass pkg: test]: https://github.com/oddbird/sass-pkg-test +[sass pkg: test]: https://github.com/oddbird/sass-pkg-test + +#### Dart + +For Dart libraries to take advantage of this, they can place a file at +`lib/_index.scss`. Other files within the `lib` folder would also be available +for library consumers. For example, if a library `libraryName` contains +`lib/themes/dark/_index.scss`, a consumer could write `@use "pkg:libraryName/themes/dark";`. ### Design Decisions #### Using a `pkg` url scheme + We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or @@ -138,7 +137,7 @@ fetches. If we instead require the browser version to have a fully resolved URL, we negate many of this spec's benefits. Users may write their own custom importers to fit their needs. -[resolving a file: URL]: ../spec/modules.md#resolving-a-file-url +[resolving a file: url]: ../spec/modules.md#resolving-a-file-url #### Available as an opt-in setting @@ -166,21 +165,21 @@ driven by [Vite], which resolves Sass paths using the `"sass"` and the `"style"` custom conditions. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports -[Vite]: https://github.com/vitejs/vite/pull/7817 +[vite]: https://github.com/vitejs/vite/pull/7817 Because use of conditional exports is flexible and recommended for modern -packages, this will be the primary method used within Node. We will support both -the `"sass"` and the `"style"` conditions, as Sass can also 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"`. +packages, this will be the primary method used for the Node package resolver. We +will support both the `"sass"` and the `"style"` conditions, as Sass can also +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"`. ## Types #### `usePkgImporter` -If true, the compiler will use the built-in package importer to resolve any -url with the `pkg` scheme. This importer follows Node.js logic to locate Sass files. +If true, the compiler will use the built-in package importer to resolve any url +with the `pkg` scheme. This importer follows Node.js logic to locate Sass files. Defaults to false. @@ -199,21 +198,20 @@ declare module '../spec/js-api' { } ``` - ## Semantics -This proposal defines a new importer that implementations should make available. -It will be disabled by default. The loader will handle URLs: +This proposal defines the requirements for Package Importers written by users or provided by implementations. It is a type of [Filesystem Importer] and will handle non-canonical URLs: - with the scheme `pkg` - followed by a package name - optionally followed by a path, with path segments separated with a forward slash. -### Resolving a `pkg:` URL +The package name will often be the first path segment, but the importer should +take into account any conventions in the environment. For instance, Node.js +supports scoped package names, which start with `@` followed by 2 path segments. -The `pkg importer` will provide a method to canonicalize a `pkg:` URL, and -extend the implementation's existing File Importer. +[filesystem importer]: ../spec/modules.md#filesystem-importer ### Platform-Specific Semantics @@ -226,31 +224,50 @@ Given `url` with the format `pkg:.*`: [package-config]: https://pub.dev/packages/package_config -#### Node +#### Node package importer -Given `url` with the format `pkg:.*`: +The Node package importer is an [importer] with an associated absolute `pkg:` URL named `base`. When the Node package importer is invoked with a string named `string` and a [previous url] `previousUrl`: -- Let `fullPath` be `url`'s path +- 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. +- Let `fullPath` be `url`'s path. +- Let `packageName` be the result of [resolving a package name], and `subPath` + be the path without the `packageName`. +- Let `packageRoot` be the result of [resolving the root directory for a package]. +- Let `packageManifest` be the result of loading the `package.json` at `packageRoot`. +- If `packageManifest` is not set, throw an error. - Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` - with the `sass` condition set + with the `sass` condition set, using `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. - Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` - with the `style` condition set. + with the `style` condition set, using `packageManifest`. - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or `css`, return it. -- Let `packageName` be the package identifier, and `subPath` be the path without - the package identifier. + - Otherwise, if `resolved` is not null, throw an error. + - If `subPath` is empty, return result of [resolving `pkg` root]. - Otherwise, return result of [resolving `pkg` subpath]. +> Note that this algorithm does not automatically resolve index files, partials +> or extensions. When using the `pkg:` url scheme, authors need to explicitly +> use the fully resolved filename. + +[previous url]: ../accepted/prev-url.d.ts.md [resolve.exports]: https://github.com/lukeed/resolve.exports -[resolving pkg root]: #resolving-pkg-root +[resolving pkg root values]: #resolving-pkg-root-values [resolving pkg subpath]: #resolving-pkg-subpath +[resolving a package name]: #resolving-a-package-name +[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser +[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package -#### Resolving `pkg` Root +#### Resolving `pkg` root values -- Let `packagePath` be the file path to the package root. +This algorithm takes a string `packagePath` which is the root directory for a package and `packageManifest`, which is the contents of the `package.json` file, and returns a file URL. + +- Let `packagePath` be the result of [Resolving the root directory for a + package]. - Let `sassValue` be the value of `sass` in the `package.json` at the pkg root. - If `sassValue` is a relative path with an extension of `sass`, `scss` or `css`, return the `packagePath` appended with `sassValue`. @@ -260,6 +277,7 @@ Given `url` with the format `pkg:.*`: `packagePath` appended with `styleValue`. - Otherwise return the result of [resolving a file url] with `packagePath`. +[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package [resolving a file url]: ../spec/modules.md#resolving-a-file-url #### Resolving `pkg` Subpath @@ -268,6 +286,44 @@ Given `url` with the format `pkg:.*`: - Let `fullPath` be `subpath` resolved relative to `packagePath`. - Return the result of [resolving a file url] with `fullPath`. +#### Resolving a package name + +This algorithm takes a string, `url`, and returns the portion that identifies +the node package. + +- If `url` starts with `@`, it is a scoped package. Return the first 2 [url path + segments], including the separating `/` +- Otherwise, return the first url path segment. + +[url path segments]: https://url.spec.whatwg.org/#url-path-segment + +#### Resolving the root directory for a package + +This algorithm takes a string, `packageName`, an absolute URL +`currentDirectory`, and an optional absolute URL `previousUrl`, and returns an +absolute URL to the root directory for the most proximate installed +`packageName`. + +> We need to replicate Node's behavior, as defined in [Loading from node_modules +> > folders], of walking up the directory chain to find packages from +> `previousUrl`, so that packages can use the correct version of the packages +> that they depend on. + +- If `previousUrl` is null, return the result of `require.resolve` with the + `packageName`. +- While `rootDirectory` is undefined: + - Remove the final path segment from `previousUrl` + - If `node_modules` is the new final path segment of `previousUrl`, + continue. + - Let `potentialPath` be `previousUrl` appended with `node_modules/` and + `packageName`. + - If `potentialPath` is a directory, let `rootDirectory` be `potentialPath`. + - Otherwise, if `previousUrl` is the root of the file system, throw an + error. +- Return `rootDirectory`. + +[loading from node_modules folders]: https://nodejs.org/api/modules.html#loading-from-node_modules-folders + ## Deprecation Process The `package` url scheme supported in Dart is not part of the Sass spec, but is @@ -297,8 +353,6 @@ Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't cleanly fit into that specification. -[Community Conditions Definition]: - https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions -[WinterCG]: https://wintercg.org/ -[Runtime Keys proposal specification]: - https://runtime-keys.proposal.wintercg.org/#adding-a-key +[community conditions definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[wintercg]: https://wintercg.org/ +[runtime keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key From fd9fbd2ccfdbdccfbf9a8ec2c4cc490c64147263 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 20 Jul 2023 22:22:58 -0400 Subject: [PATCH 12/38] Remove Dart semantics --- proposal/package-importer.d.ts.md | 37 +++++-------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 6ca4fa1ff7..96f45d9c91 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -12,7 +12,6 @@ format. * [Summary](#summary) * [Examples](#examples) * [Node](#node) - * [Dart](#dart) * [Design Decisions](#design-decisions) * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) @@ -21,14 +20,12 @@ format. * [Types](#types) * [`usePkgImporter`](#usepkgimporter) * [Semantics](#semantics) - * [Platform-Specific Semantics](#platform-specific-semantics) - * [Dart](#dart-1) - * [Node package importer](#node-package-importer) + * [Package Importers](#package-importers) + * [Node Specific Semantics](#node-specific-semantics) * [Resolving `pkg` root values](#resolving-pkg-root-values) * [Resolving `pkg` Subpath](#resolving-pkg-subpath) * [Resolving a package name](#resolving-a-package-name) * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) -* [Deprecation Process](#deprecation-process) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -106,13 +103,6 @@ the algorithm. [sass pkg: test]: https://github.com/oddbird/sass-pkg-test -#### Dart - -For Dart libraries to take advantage of this, they can place a file at -`lib/_index.scss`. Other files within the `lib` folder would also be available -for library consumers. For example, if a library `libraryName` contains -`lib/themes/dark/_index.scss`, a consumer could write `@use "pkg:libraryName/themes/dark";`. - ### Design Decisions #### Using a `pkg` url scheme @@ -200,6 +190,8 @@ declare module '../spec/js-api' { ## Semantics +### Package Importers + This proposal defines the requirements for Package Importers written by users or provided by implementations. It is a type of [Filesystem Importer] and will handle non-canonical URLs: - with the scheme `pkg` @@ -213,18 +205,7 @@ supports scoped package names, which start with `@` followed by 2 path segments. [filesystem importer]: ../spec/modules.md#filesystem-importer -### Platform-Specific Semantics - -#### Dart - -Given `url` with the format `pkg:.*`: - -- Let `path` be `url`'s path -- Use [package-config] to resolve `path` - -[package-config]: https://pub.dev/packages/package_config - -#### Node package importer +### Node Specific Semantics The Node package importer is an [importer] with an associated absolute `pkg:` URL named `base`. When the Node package importer is invoked with a string named `string` and a [previous url] `previousUrl`: @@ -324,14 +305,6 @@ absolute URL to the root directory for the most proximate installed [loading from node_modules folders]: https://nodejs.org/api/modules.html#loading-from-node_modules-folders -## Deprecation Process - -The `package` url scheme supported in Dart is not part of the Sass spec, but is -supported by the `dart-sass` implementation when running in Dart. This should be -deprecated in favor of moving authors to the new `pkg` url scheme. Usage of the -`package` syntax will result in a deprecation message, and a future major -version of Sass should remove support. - ## Ecosystem Notes Vite is currently using the Legacy JS API, and has an [open issue] to update to From 54e4039422c4ab35fb6b5010f5a47a38577efa8f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 20 Jul 2023 22:55:01 -0400 Subject: [PATCH 13/38] Add additional community examples --- proposal/package-importer.d.ts.md | 59 ++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 96f45d9c91..431784ea40 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -14,7 +14,8 @@ format. * [Node](#node) * [Design Decisions](#design-decisions) * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) - * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) + * [No built-in `pkg` resolver for + browsers](#no-built-in-pkg-resolver-for-browsers) * [Available as an opt-in setting](#available-as-an-opt-in-setting) * [Node Resolution Decisions](#node-resolution-decisions) * [Types](#types) @@ -25,7 +26,8 @@ format. * [Resolving `pkg` root values](#resolving-pkg-root-values) * [Resolving `pkg` Subpath](#resolving-pkg-subpath) * [Resolving a package name](#resolving-a-package-name) - * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) + * [Resolving the root directory for a + package](#resolving-the-root-directory-for-a-package) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -150,12 +152,18 @@ There are a variety of methods currently in use for specifying a location of the default Sass export for npm packages. For the most part, packages contain both JavaScript and styles, and use the `main` or `module` root keys to define the JavaScript entry point. Some packages use the `"sass"` key at the root of their -`package.json`. Other packages have adopted [conditional exports], largely -driven by [Vite], which resolves Sass paths using the `"sass"` and the `"style"` -custom conditions. +`package.json`. + +Other packages have adopted [conditional exports], driven by build tools like +[Vite], [Parcel] and [Sass Loader for Webpack] which all resolve Sass paths +using the `"sass"` and the `"style"` custom conditions. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports -[vite]: https://github.com/vitejs/vite/pull/7817 +[Vite]: https://github.com/vitejs/vite/pull/7817 +[Parcel]: + https://github.com/parcel-bundler/parcel/blob/2d2400ded4615375ee6bd53ef77b4857ad1591dd/packages/transformers/sass/src/SassTransformer.js#L163 +[Sass Loader for Webpack]: + https://github.com/webpack-contrib/sass-loader/blob/02df41203adfda96959e56abb43bd35a89ec11ba/src/utils.js#L514 Because use of conditional exports is flexible and recommended for modern packages, this will be the primary method used for the Node package resolver. We @@ -192,7 +200,9 @@ declare module '../spec/js-api' { ### Package Importers -This proposal defines the requirements for Package Importers written by users or provided by implementations. It is a type of [Filesystem Importer] and will handle non-canonical URLs: +This proposal defines the requirements for Package Importers written by users or +provided by implementations. It is a type of [Filesystem Importer] and will +handle non-canonical URLs: - with the scheme `pkg` - followed by a package name @@ -207,15 +217,19 @@ supports scoped package names, which start with `@` followed by 2 path segments. ### Node Specific Semantics -The Node package importer is an [importer] with an associated absolute `pkg:` URL named `base`. When the Node package importer is invoked with a string named `string` and a [previous url] `previousUrl`: +The Node package importer is an [importer] with an associated absolute `pkg:` +URL named `base`. When the Node package importer is invoked with a string named +`string` and a [previous url] `previousUrl`: - 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. - Let `fullPath` be `url`'s path. - Let `packageName` be the result of [resolving a package name], and `subPath` be the path without the `packageName`. -- Let `packageRoot` be the result of [resolving the root directory for a package]. -- Let `packageManifest` be the result of loading the `package.json` at `packageRoot`. +- Let `packageRoot` be the result of [resolving the root directory for a + package]. +- Let `packageManifest` be the result of loading the `package.json` at + `packageRoot`. - If `packageManifest` is not set, throw an error. - Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` with the `sass` condition set, using `packageManifest`. @@ -241,11 +255,14 @@ The Node package importer is an [importer] with an associated absolute `pkg:` UR [resolving pkg subpath]: #resolving-pkg-subpath [resolving a package name]: #resolving-a-package-name [parsing a url]: https://url.spec.whatwg.org/#concept-url-parser -[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package +[resolving the root directory for a package]: + #resolving-the-root-directory-for-a-package #### Resolving `pkg` root values -This algorithm takes a string `packagePath` which is the root directory for a package and `packageManifest`, which is the contents of the `package.json` file, and returns a file URL. +This algorithm takes a string `packagePath` which is the root directory for a +package and `packageManifest`, which is the contents of the `package.json` file, +and returns a file URL. - Let `packagePath` be the result of [Resolving the root directory for a package]. @@ -258,7 +275,8 @@ This algorithm takes a string `packagePath` which is the root directory for a pa `packagePath` appended with `styleValue`. - Otherwise return the result of [resolving a file url] with `packagePath`. -[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package +[resolving the root directory for a package]: + #resolving-the-root-directory-for-a-package [resolving a file url]: ../spec/modules.md#resolving-a-file-url #### Resolving `pkg` Subpath @@ -294,16 +312,15 @@ absolute URL to the root directory for the most proximate installed `packageName`. - While `rootDirectory` is undefined: - Remove the final path segment from `previousUrl` - - If `node_modules` is the new final path segment of `previousUrl`, - continue. + - If `node_modules` is the new final path segment of `previousUrl`, continue. - Let `potentialPath` be `previousUrl` appended with `node_modules/` and `packageName`. - If `potentialPath` is a directory, let `rootDirectory` be `potentialPath`. - - Otherwise, if `previousUrl` is the root of the file system, throw an - error. + - Otherwise, if `previousUrl` is the root of the file system, throw an error. - Return `rootDirectory`. -[loading from node_modules folders]: https://nodejs.org/api/modules.html#loading-from-node_modules-folders +[loading from node_modules folders]: + https://nodejs.org/api/modules.html#loading-from-node_modules-folders ## Ecosystem Notes @@ -326,6 +343,8 @@ Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't cleanly fit into that specification. -[community conditions definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[community conditions definition]: + https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions [wintercg]: https://wintercg.org/ -[runtime keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key +[runtime keys proposal specification]: + https://runtime-keys.proposal.wintercg.org/#adding-a-key From dadaa1de2fb81232360b419bb38f5518390e5479 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 10:14:08 -0400 Subject: [PATCH 14/38] Remove proposed option from options.d.ts --- js-api-doc/options.d.ts | 15 +++------------ proposal/package-importer.d.ts.md | 6 ++---- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index f82e59340a..ce8afe9549 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -129,9 +129,9 @@ export interface Options { * rule`](https://sass-lang.com/documentation/at-rules/function) and whose * values are {@link CustomFunction}s. * - * Functions are passed subclasses of {@link Value}, and must return the same. - * If the return value includes {@link SassCalculation}s they will be - * simplified before being returned. + * Functions are passed JavaScript representations of [Sass value + * types](https://sass-lang.com/documentation/js-api#value-types), and must + * return the same. * * When writing custom functions, it's important to make them as user-friendly * and as close to the standards set by Sass's core functions as possible. Some @@ -332,15 +332,6 @@ export interface Options { */ style?: OutputStyle; - /** - * Whether or not to enable the built-in package importer to resolve any url - * with the `pkg` scheme. - * - * @defaultValue `false` - * @category Input - */ - usePkgImporter?: boolean; - /** * By default, Dart Sass will print only five instances of the same * deprecation warning per compilation to avoid deluging users in console diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 431784ea40..3ad638726a 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -14,8 +14,7 @@ format. * [Node](#node) * [Design Decisions](#design-decisions) * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) - * [No built-in `pkg` resolver for - browsers](#no-built-in-pkg-resolver-for-browsers) + * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) * [Available as an opt-in setting](#available-as-an-opt-in-setting) * [Node Resolution Decisions](#node-resolution-decisions) * [Types](#types) @@ -26,8 +25,7 @@ format. * [Resolving `pkg` root values](#resolving-pkg-root-values) * [Resolving `pkg` Subpath](#resolving-pkg-subpath) * [Resolving a package name](#resolving-a-package-name) - * [Resolving the root directory for a - package](#resolving-the-root-directory-for-a-package) + * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) * [Ecosystem Notes](#ecosystem-notes) ## Background From 9d549eed162f0144dfbf5c65d8691caa69fba52d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 10:18:17 -0400 Subject: [PATCH 15/38] Replace options with correct copy --- js-api-doc/options.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js-api-doc/options.d.ts b/js-api-doc/options.d.ts index ce8afe9549..2b5def268c 100644 --- a/js-api-doc/options.d.ts +++ b/js-api-doc/options.d.ts @@ -129,9 +129,9 @@ export interface Options { * rule`](https://sass-lang.com/documentation/at-rules/function) and whose * values are {@link CustomFunction}s. * - * Functions are passed JavaScript representations of [Sass value - * types](https://sass-lang.com/documentation/js-api#value-types), and must - * return the same. + * Functions are passed subclasses of {@link Value}, and must return the same. + * If the return value includes {@link SassCalculation}s they will be + * simplified before being returned. * * When writing custom functions, it's important to make them as user-friendly * and as close to the standards set by Sass's core functions as possible. Some From 7cc0f47014b6a4b205bf9d7988f097b5294b4490 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 11:04:26 -0400 Subject: [PATCH 16/38] Edits --- proposal/package-importer.d.ts.md | 43 ++++++++++--------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 3ad638726a..b7aef8f3a5 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -2,16 +2,16 @@ _([Issue](https://github.com/sass/sass/issues/2739))_ -This proposal adds Sass support for a standard package importer, introducing a +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. +format. It also defines the semantics for a new built-in Node.js Package +Importer. ## Table of Contents * [Background](#background) * [Summary](#summary) - * [Examples](#examples) - * [Node](#node) + * [Node built-in importer](#node-built-in-importer) * [Design Decisions](#design-decisions) * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) @@ -23,7 +23,6 @@ format. * [Package Importers](#package-importers) * [Node Specific Semantics](#node-specific-semantics) * [Resolving `pkg` root values](#resolving-pkg-root-values) - * [Resolving `pkg` Subpath](#resolving-pkg-subpath) * [Resolving a package name](#resolving-a-package-name) * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) * [Ecosystem Notes](#ecosystem-notes) @@ -53,15 +52,11 @@ This proposal also defines a built-in Node.js importer. For example, `@use "pkg:bootstrap";` would resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be -resolved within `node_modules`, using the [Node resolution algorithm]. In Dart, -that would be resolved within `pub-cache`, using [package-config]. +resolved within `node_modules`, using the [Node resolution algorithm]. [node resolution algorithm]: https://nodejs.org/api/packages.html -[package-config]: https://pub.dev/packages/package_config -### Examples - -#### Node +### Node built-in importer The built-in Node importer resolves in the following order: @@ -239,18 +234,17 @@ URL named `base`. When the Node package importer is invoked with a string named - 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 result of [resolving `pkg` root]. -- Otherwise, return result of [resolving `pkg` subpath]. +- If `subPath` is empty, return result of [resolving `pkg` root values]. +- Let `resolved` be `subPath` resolved relative to `packageRoot`. +- Return the result of [resolving a file url] with `resolved`. > Note that this algorithm does not automatically resolve index files, partials -> or extensions. When using the `pkg:` url scheme, authors need to explicitly -> use the fully resolved filename. +> or extensions, except where specified. When using the `pkg:` url scheme, +> authors need to explicitly use the fully resolved filename. [previous url]: ../accepted/prev-url.d.ts.md [resolve.exports]: https://github.com/lukeed/resolve.exports -[resolving pkg root values]: #resolving-pkg-root-values -[resolving pkg subpath]: #resolving-pkg-subpath +[resolving `pkg` root values]: #resolving-pkg-root-values [resolving a package name]: #resolving-a-package-name [parsing a url]: https://url.spec.whatwg.org/#concept-url-parser [resolving the root directory for a package]: @@ -262,13 +256,10 @@ This algorithm takes a string `packagePath` which is the root directory for a package and `packageManifest`, which is the contents of the `package.json` file, and returns a file URL. -- Let `packagePath` be the result of [Resolving the root directory for a - package]. -- Let `sassValue` be the value of `sass` in the `package.json` at the pkg root. +- 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 `packagePath` appended with `sassValue`. -- Let `styleValue` be the value of `style` in the `package.json` at the pkg - root. +- Let `styleValue` be the value of `style` in `packageManifest`. - If `styleValue` is a relative path with an extension of `css`, return the `packagePath` appended with `styleValue`. - Otherwise return the result of [resolving a file url] with `packagePath`. @@ -277,12 +268,6 @@ and returns a file URL. #resolving-the-root-directory-for-a-package [resolving a file url]: ../spec/modules.md#resolving-a-file-url -#### Resolving `pkg` Subpath - -- Let `packagePath` be the file path to the package root. -- Let `fullPath` be `subpath` resolved relative to `packagePath`. -- Return the result of [resolving a file url] with `fullPath`. - #### Resolving a package name This algorithm takes a string, `url`, and returns the portion that identifies From 82b3739f111f824567c8856346cbdc55932b9d5a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 11:37:29 -0400 Subject: [PATCH 17/38] Add procedures section --- proposal/package-importer.d.ts.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index b7aef8f3a5..d30e63baad 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -22,9 +22,10 @@ Importer. * [Semantics](#semantics) * [Package Importers](#package-importers) * [Node Specific Semantics](#node-specific-semantics) - * [Resolving `pkg` root values](#resolving-pkg-root-values) - * [Resolving a package name](#resolving-a-package-name) - * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) +* [Procedures](#procedures) + * [Resolving `pkg` root values](#resolving-pkg-root-values) + * [Resolving a package name](#resolving-a-package-name) + * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -216,6 +217,7 @@ URL named `base`. When the Node package importer is invoked with a string named - 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 `pkg`, return null. - Let `fullPath` be `url`'s path. - Let `packageName` be the result of [resolving a package name], and `subPath` be the path without the `packageName`. @@ -249,8 +251,11 @@ URL named `base`. When the Node package importer is invoked with a string named [parsing a url]: https://url.spec.whatwg.org/#concept-url-parser [resolving the root directory for a package]: #resolving-the-root-directory-for-a-package +[resolving a file url]: ../spec/modules.md#resolving-a-file-url + +## Procedures -#### Resolving `pkg` root values +### Resolving `pkg` root values This algorithm takes a string `packagePath` which is the root directory for a package and `packageManifest`, which is the contents of the `package.json` file, @@ -268,7 +273,7 @@ and returns a file URL. #resolving-the-root-directory-for-a-package [resolving a file url]: ../spec/modules.md#resolving-a-file-url -#### Resolving a package name +### Resolving a package name This algorithm takes a string, `url`, and returns the portion that identifies the node package. @@ -279,7 +284,7 @@ the node package. [url path segments]: https://url.spec.whatwg.org/#url-path-segment -#### Resolving the root directory for a package +### Resolving the root directory for a package This algorithm takes a string, `packageName`, an absolute URL `currentDirectory`, and an optional absolute URL `previousUrl`, and returns an From a2a890d7985fa00d8e525187e6a211f6d7ff664c Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 12:12:23 -0400 Subject: [PATCH 18/38] Break out pkg url resolution, and define Package importer results --- proposal/package-importer.d.ts.md | 49 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index d30e63baad..3ccf816feb 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -23,6 +23,7 @@ Importer. * [Package Importers](#package-importers) * [Node Specific Semantics](#node-specific-semantics) * [Procedures](#procedures) + * [Resolving a `pkg` URL](#resolving-a-pkg-url) * [Resolving `pkg` root values](#resolving-pkg-root-values) * [Resolving a package name](#resolving-a-package-name) * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) @@ -218,6 +219,39 @@ URL named `base`. When the Node package importer is invoked with a string named - 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 `pkg`, return null. +- Let `resolved` be the result of [resolving a `pkg` URL] with `url` and `previousURL` +- 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](../spec/modules.md#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 +[resolving a `pkg` URL]: #resolving-a-pkg-url + +> Note that this algorithm does not automatically resolve index files, partials +> or extensions, except where specified. When using the `pkg:` url scheme, +> authors need to explicitly use the fully resolved filename. + +[previous url]: ../accepted/prev-url.d.ts.md +[resolve.exports]: https://github.com/lukeed/resolve.exports +[resolving `pkg` root values]: #resolving-pkg-root-values +[resolving a package name]: #resolving-a-package-name +[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser +[resolving the root directory for a package]: + #resolving-the-root-directory-for-a-package +[resolving a file url]: ../spec/modules.md#resolving-a-file-url + +## Procedures + +### Resolving a `pkg` URL + +This algorithm takes a URL with scheme `pkg`, and an optional URL `previousURL`. It returns a canonical file path or null. + - Let `fullPath` be `url`'s path. - Let `packageName` be the result of [resolving a package name], and `subPath` be the path without the `packageName`. @@ -240,21 +274,6 @@ URL named `base`. When the Node package importer is invoked with a string named - Let `resolved` be `subPath` resolved relative to `packageRoot`. - Return the result of [resolving a file url] with `resolved`. -> Note that this algorithm does not automatically resolve index files, partials -> or extensions, except where specified. When using the `pkg:` url scheme, -> authors need to explicitly use the fully resolved filename. - -[previous url]: ../accepted/prev-url.d.ts.md -[resolve.exports]: https://github.com/lukeed/resolve.exports -[resolving `pkg` root values]: #resolving-pkg-root-values -[resolving a package name]: #resolving-a-package-name -[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser -[resolving the root directory for a package]: - #resolving-the-root-directory-for-a-package -[resolving a file url]: ../spec/modules.md#resolving-a-file-url - -## Procedures - ### Resolving `pkg` root values This algorithm takes a string `packagePath` which is the root directory for a From 2554f7094cfb60f7a92321ed9a71b9cedc44fa59 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 12:57:57 -0400 Subject: [PATCH 19/38] Document exports resolution, make vs more consistent --- proposal/package-importer.d.ts.md | 45 +++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 3ccf816feb..40fe4e76a4 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -24,9 +24,10 @@ Importer. * [Node Specific Semantics](#node-specific-semantics) * [Procedures](#procedures) * [Resolving a `pkg` URL](#resolving-a-pkg-url) - * [Resolving `pkg` root values](#resolving-pkg-root-values) + * [Resolving package root values](#resolving-package-root-values) * [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) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -237,15 +238,6 @@ URL named `base`. When the Node package importer is invoked with a string named > or extensions, except where specified. When using the `pkg:` url scheme, > authors need to explicitly use the fully resolved filename. -[previous url]: ../accepted/prev-url.d.ts.md -[resolve.exports]: https://github.com/lukeed/resolve.exports -[resolving `pkg` root values]: #resolving-pkg-root-values -[resolving a package name]: #resolving-a-package-name -[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser -[resolving the root directory for a package]: - #resolving-the-root-directory-for-a-package -[resolving a file url]: ../spec/modules.md#resolving-a-file-url - ## Procedures ### Resolving a `pkg` URL @@ -260,21 +252,31 @@ This algorithm takes a URL with scheme `pkg`, and an optional URL `previousURL`. - Let `packageManifest` be the result of loading the `package.json` at `packageRoot`. - If `packageManifest` is not set, throw an error. -- Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` +- Let `resolved` be the result of [Resolving package exports] to resolve `fullPath` with the `sass` condition set, using `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. -- Let `resolved` be the result of using [resolve.exports] to resolve `fullPath` +- Let `resolved` be the result of [Resolving package exports] to resolve `fullPath` with the `style` condition set, using `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 result of [resolving `pkg` root values]. +- If `subPath` is empty, return 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 `pkg` root values + +[previous url]: ../accepted/prev-url.d.ts.md +[Resolving package exports]: #resolving-package-exports +[resolving package root values]: #resolving-package-root-values +[resolving a package name]: #resolving-a-package-name +[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser +[resolving the root directory for a package]: + #resolving-the-root-directory-for-a-package +[resolving a file url]: ../spec/modules.md#resolving-a-file-url + +### 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 the `package.json` file, @@ -329,6 +331,21 @@ absolute URL to the root directory for the most proximate installed [loading from node_modules folders]: https://nodejs.org/api/modules.html#loading-from-node_modules-folders +### Resolving package exports + +This algorithm takes a string `condition`, a package.json value +`packageManifest`, and a pkg URL `url`. It returns a file URL or null. + +This algorithm should follow the Node resolution algorithm, as defined in the +Node documentation under [Node Modules] and [Conditional Exports]. Where +possible in Node.js, it can use [resolve.exports] which exposes the Node +resolution algorithm, allowing for per-path custom conditions, and without +needing filesystem access. + +[resolve.exports]: https://github.com/lukeed/resolve.exports +[Conditional Exports]: https://nodejs.org/api/packages.html#conditional-exports +[Node Modules]: https://nodejs.org/api/modules.html#all-together + ## Ecosystem Notes Vite is currently using the Legacy JS API, and has an [open issue] to update to From 6e10182b70eaf5108d8505aec4f5b7cd4c483523 Mon Sep 17 00:00:00 2001 From: Ed Rivas Date: Fri, 21 Jul 2023 18:17:24 +0000 Subject: [PATCH 20/38] Add missing links --- proposal/package-importer.d.ts.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 40fe4e76a4..1990ee4610 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -178,7 +178,7 @@ with the `pkg` scheme. This importer follows Node.js logic to locate Sass files. Defaults to false. ```ts -declare module '../spec/js-api' { +declare module '../spec/js-api/options' { interface Options { /** * Whether or not to enable the built-in package importer to resolve any url @@ -313,7 +313,7 @@ absolute URL to the root directory for the most proximate installed `packageName`. > We need to replicate Node's behavior, as defined in [Loading from node_modules -> > folders], of walking up the directory chain to find packages from +> folders], of walking up the directory chain to find packages from > `previousUrl`, so that packages can use the correct version of the packages > that they depend on. @@ -348,6 +348,12 @@ needing filesystem access. ## Ecosystem Notes +The new `usePkgImporter` option will not be available in the [Legacy JS API]. +Third-party applications that don't support the modern API will be unable to use +the built-in package importer. Some notable examples follow. + +[Legacy JS API]: https://sass-lang.com/documentation/js-api/#md:legacy-api + Vite is currently using the Legacy JS API, and has an [open issue] to update to the modern API. They also do not expose Sass options to the user, so would need to enable the `usePkgImporter` on their behalf or expose some configuration. @@ -357,6 +363,8 @@ to enable the `usePkgImporter` on their behalf or expose some configuration. Webpack's [sass-loader] allows users to opt in to the modern API and exposes Sass options to users. +[sass-loader]: https://webpack.js.org/loaders/sass-loader/ + For Rollup, [rollup-plugin-sass] uses the Legacy JS API. They do expose Sass options to the user. From f8b6f1ce5e0476c0716e37d6fd273680d27841ac Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 21 Jul 2023 22:29:53 -0400 Subject: [PATCH 21/38] Add import to ts to satisfy ambient module error Co-authored-by: Ed Rivas --- proposal/package-importer.d.ts.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 1990ee4610..971bf016c3 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -170,6 +170,10 @@ tends to be used solely for `css` files, we will support `scss`, `sass` and ## Types +```ts +import '../spec/js-api'; +``` + #### `usePkgImporter` If true, the compiler will use the built-in package importer to resolve any url From 64a724e61a8d0a83590e3b482bd92f1df23d8806 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 24 Jul 2023 10:08:21 -0400 Subject: [PATCH 22/38] Standardize language --- proposal/package-importer.d.ts.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 971bf016c3..8109df714a 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -4,7 +4,7 @@ _([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.js Package +format. It also defines the semantics for a new built-in Node Package Importer. ## Table of Contents @@ -51,7 +51,7 @@ implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. -This proposal also defines a built-in Node.js importer. +This proposal also defines a built-in Node importer. For example, `@use "pkg:bootstrap";` would resolve to the path of a library-defined export within the `bootstrap` dependency. In Node, that would be @@ -87,7 +87,7 @@ export key as the first key in `package.json`. } ``` -Then, library consumers can use the pkg syntax to get the default export. +Then, library consumers can use the `pkg` syntax to get the default export. ```scss @use 'pkg:library'; @@ -162,7 +162,7 @@ using the `"sass"` and the `"style"` custom conditions. https://github.com/webpack-contrib/sass-loader/blob/02df41203adfda96959e56abb43bd35a89ec11ba/src/utils.js#L514 Because use of conditional exports is flexible and recommended for modern -packages, this will be the primary method used for the Node package resolver. We +packages, this will be the primary method used for the Node package importer. We will support both the `"sass"` and the `"style"` conditions, as Sass can also 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 @@ -177,7 +177,7 @@ import '../spec/js-api'; #### `usePkgImporter` If true, the compiler will use the built-in package importer to resolve any url -with the `pkg` scheme. This importer follows Node.js logic to locate Sass files. +with the `pkg` scheme. This importer follows Node logic to locate Sass files. Defaults to false. @@ -210,7 +210,7 @@ handle non-canonical URLs: slash. The package name will often be the first path segment, but the importer should -take into account any conventions in the environment. For instance, Node.js +take into account any conventions in the environment. For instance, Node supports scoped package names, which start with `@` followed by 2 path segments. [filesystem importer]: ../spec/modules.md#filesystem-importer @@ -301,7 +301,7 @@ and returns a file URL. ### Resolving a package name This algorithm takes a string, `url`, and returns the portion that identifies -the node package. +the Node package. - If `url` starts with `@`, it is a scoped package. Return the first 2 [url path segments], including the separating `/` @@ -338,11 +338,11 @@ absolute URL to the root directory for the most proximate installed ### Resolving package exports This algorithm takes a string `condition`, a package.json value -`packageManifest`, and a pkg URL `url`. It returns a file URL or null. +`packageManifest`, and a `pkg` URL `url`. It returns a file URL or null. This algorithm should follow the Node resolution algorithm, as defined in the Node documentation under [Node Modules] and [Conditional Exports]. Where -possible in Node.js, it can use [resolve.exports] which exposes the Node +possible in Node, it can use [resolve.exports] which exposes the Node resolution algorithm, allowing for per-path custom conditions, and without needing filesystem access. From a4da3ac21ffc2447ca47d6f3ab0566698808a51f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 24 Jul 2023 10:09:52 -0400 Subject: [PATCH 23/38] Temporarily add debug info to link check --- test/link-check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/link-check.ts b/test/link-check.ts index 728b44b551..084f6ae0a7 100644 --- a/test/link-check.ts +++ b/test/link-check.ts @@ -71,7 +71,7 @@ function verifyLinkCheckResults( colors.yellow(`Server error on target: ${result.link}`) ); } else { - flagDeadLink(result.link); + flagDeadLink(`${result.link} ${result.status} ${result.err}`); } break; From 031fec9172d6356799719ce58b10055f2a5cb312 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 24 Jul 2023 10:18:55 -0400 Subject: [PATCH 24/38] Revert debugging in link check --- test/link-check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/link-check.ts b/test/link-check.ts index 084f6ae0a7..728b44b551 100644 --- a/test/link-check.ts +++ b/test/link-check.ts @@ -71,7 +71,7 @@ function verifyLinkCheckResults( colors.yellow(`Server error on target: ${result.link}`) ); } else { - flagDeadLink(`${result.link} ${result.status} ${result.err}`); + flagDeadLink(result.link); } break; From daee9d960247ce56ffcc485374c0f79431ea8d5b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 11:41:48 -0400 Subject: [PATCH 25/38] Address review and style --- proposal/package-importer.d.ts.md | 187 +++++++++++++++--------------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 8109df714a..1a39188b30 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -3,7 +3,7 @@ _([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 +`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. @@ -13,17 +13,17 @@ Importer. * [Summary](#summary) * [Node built-in importer](#node-built-in-importer) * [Design Decisions](#design-decisions) - * [Using a `pkg` url scheme](#using-a-pkg-url-scheme) - * [No built-in `pkg` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) + * [Using a `pkg:` URL scheme](#using-a-pkg-url-scheme) + * [No built-in `pkg:` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) * [Available as an opt-in setting](#available-as-an-opt-in-setting) * [Node Resolution Decisions](#node-resolution-decisions) * [Types](#types) - * [`usePkgImporter`](#usepkgimporter) + * [`useNodePkgImporter`](#usenodepkgimporter) * [Semantics](#semantics) * [Package Importers](#package-importers) - * [Node Specific Semantics](#node-specific-semantics) + * [Node Package Importer](#node-package-importer) * [Procedures](#procedures) - * [Resolving a `pkg` URL](#resolving-a-pkg-url) + * [Node Algorithm for Resolving a `pkg:` URL](#node-algorithm-for-resolving-a-pkg-url) * [Resolving package root values](#resolving-package-root-values) * [Resolving a package name](#resolving-a-package-name) * [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package) @@ -46,7 +46,7 @@ that is tied to a specific domain and make it difficult to rely on dependencies. Sass users often need to use styles from a dependency to customize an existing theme or access styling utilities. -This proposal defines a `pkg` URL scheme for usage with `@use` that directs an +This proposal defines a `pkg:` URL scheme for usage with `@use` that directs an implementation to resolve a URL within a dependency. The implementation will resolve the dependency URL using the standard resolution for that environment. Once resolved, this URL will be loaded in the same way as any other `file:` URL. @@ -66,9 +66,9 @@ The built-in Node importer resolves in the following order: 1. `sass` condition in package.json `exports` 2. `style` condition in package.json `exports` 3. If no subpath, then find root export: -4. `sass` key at package.json root -5. `style` key at package.json root -6. `index` file at package root, resolved for file extensions and partials + 1. `sass` key at package.json root + 2. `style` key at package.json root + 3. `index` file at package root, resolved for file extensions and partials 7. If there is a subpath, resolve that path relative to the package root, and resolve for file extensions and partials @@ -87,14 +87,12 @@ export key as the first key in `package.json`. } ``` -Then, library consumers can use the `pkg` syntax to get the default export. +Then, library consumers can use the `pkg:` syntax to get the default export. ```scss @use 'pkg:library'; ``` -More examples can be found in the [Sass pkg: test] example repo. - To better understand and allow for testing against the recommended algorithm, a [Sass pkg: test] repository has been made with a rudimentary implementation of the algorithm. @@ -103,21 +101,22 @@ the algorithm. ### Design Decisions -#### Using a `pkg` url scheme +#### Using a `pkg:` URL scheme We could use the `~` popularized by Webpack's `load-sass` format, but this has been deprecated since 2021. In addition, since this creates a URL that is syntactically a relative URL, it does not make it clear to the implementation or 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, and does not have -known conflicts in the ecosystem. +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. -#### No built-in `pkg` resolver for browsers +#### No built-in `pkg:` resolver for browsers -Dart Sass will not provide a built-in resolver for browsers to use the `pkg` +Dart Sass will not provide a built-in resolver for browsers to use the `pkg:` scheme. To support a similar functionality, a user would need to ensure that files are served, and the loader would need to fetch the URL. In order to follow the same algorithm for [resolving a file: URL], we would need to make many @@ -129,7 +128,7 @@ importers to fit their needs. #### Available as an opt-in setting -The `pkg` import loader will be exposed through an opt-in setting as it adds the +The `pkg:` import loader will be exposed through an opt-in setting as it adds the potential for file system interaction to `compileString` and `compileStringAsync`. Specifically, we want people who invoke Sass compilation functions to have control over what files get accessed, and there's even a risk @@ -156,10 +155,8 @@ using the `"sass"` and the `"style"` custom conditions. [conditional exports]: https://nodejs.org/api/packages.html#conditional-exports [Vite]: https://github.com/vitejs/vite/pull/7817 -[Parcel]: - https://github.com/parcel-bundler/parcel/blob/2d2400ded4615375ee6bd53ef77b4857ad1591dd/packages/transformers/sass/src/SassTransformer.js#L163 -[Sass Loader for Webpack]: - https://github.com/webpack-contrib/sass-loader/blob/02df41203adfda96959e56abb43bd35a89ec11ba/src/utils.js#L514 +[Parcel]: https://github.com/parcel-bundler/parcel/blob/2d2400ded4615375ee6bd53ef77b4857ad1591dd/packages/transformers/sass/src/SassTransformer.js#L163 +[Sass Loader for Webpack]: https://github.com/webpack-contrib/sass-loader/blob/02df41203adfda96959e56abb43bd35a89ec11ba/src/utils.js#L514 Because use of conditional exports is flexible and recommended for modern packages, this will be the primary method used for the Node package importer. We @@ -174,10 +171,10 @@ tends to be used solely for `css` files, we will support `scss`, `sass` and import '../spec/js-api'; ``` -#### `usePkgImporter` +#### `useNodePkgImporter` -If true, the compiler will use the built-in package importer to resolve any url -with the `pkg` scheme. This importer follows Node logic to locate Sass files. +If true, the compiler will use the built-in Node package importer to resolve any URL +with the `pkg:` scheme. This importer follows Node logic to locate Sass files. Defaults to false. @@ -185,13 +182,13 @@ Defaults to false. declare module '../spec/js-api/options' { interface Options { /** - * Whether or not to enable the built-in package importer to resolve any url - * with the `pkg` scheme. This importer follows Node.js resolution logic. + * Whether or not to enable the built-in package importer to resolve any URL + * with the `pkg:` scheme. This importer follows Node.js resolution logic. * * @defaultValue `false` * @category Input */ - usePkgImporter?: boolean; + useNodePkgImporter?: boolean; } } ``` @@ -201,11 +198,11 @@ declare module '../spec/js-api/options' { ### Package Importers This proposal defines the requirements for Package Importers written by users or -provided by implementations. It is a type of [Filesystem Importer] and will -handle non-canonical URLs: +provided by implementations. It is a type of [Importer] and will handle +non-canonical URLs: - with the scheme `pkg` -- followed by a package name +- whose path begins with a package name - optionally followed by a path, with path segments separated with a forward slash. @@ -213,101 +210,110 @@ The package name will often be the first path segment, but the importer should take into account any conventions in the environment. For instance, Node supports scoped package names, which start with `@` followed by 2 path segments. -[filesystem importer]: ../spec/modules.md#filesystem-importer +Package Importers will reject the following patterns: + +- A URL whose path begins with `/`. +- A URL with non-empty/null username, password, host, port, query, or fragment. + + +[importer]: ../spec/modules.md#importer -### Node Specific Semantics +### Node Package Importer The Node package importer is an [importer] with an associated absolute `pkg:` URL named `base`. When the Node package importer is invoked with a string named -`string` and a [previous url] `previousUrl`: +`string` and a [previous URL] `previousUrl`: -- Let `url` be the result of [parsing `string` as a URL][parsing a url] with +- 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 `pkg`, return null. -- Let `resolved` be the result of [resolving a `pkg` URL] with `url` and `previousURL` +- If `url`'s scheme is not `pkg:`, return null. +- Let `resolved` be the result of [resolving a `pkg:` URL] with `url` and `previousURL`. - 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`. + - "scss" if `resolved` ends in `.scss`. + - "indented" if `resolved` ends in `.sass`. + - "css" if `resolved` ends in `.css`. > The algorithm for [resolving a `file:` URL](../spec/modules.md#resolving-a-file-url) - > guarantees that `url` will have one of these extensions. + > guarantees that `resolved` will have one of these extensions. - Return `text`, `syntax`, and `resolved`. [parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser -[resolving a `pkg` URL]: #resolving-a-pkg-url +[resolving a `pkg:` URL]: #node-algorithm-for-resolving-a-pkg-url > Note that this algorithm does not automatically resolve index files, partials -> or extensions, except where specified. When using the `pkg:` url scheme, +> or extensions, except where specified. When using the `pkg:` URL scheme, > authors need to explicitly use the fully resolved filename. ## Procedures -### Resolving a `pkg` URL +### Node Algorithm for Resolving a `pkg:` URL -This algorithm takes a URL with scheme `pkg`, and an optional URL `previousURL`. It returns a canonical file path or null. +This algorithm takes a URL with scheme `pkg:`, and an optional URL `previousURL`. +It returns a canonical file path or null. - Let `fullPath` be `url`'s path. -- Let `packageName` be the result of [resolving a package name], and `subPath` - be the path without the `packageName`. +- Let `packageName` be the result of [resolving a package name] with `fullPath`, + and `subPath` be the path without the `packageName`. - Let `packageRoot` be the result of [resolving the root directory for a - package]. -- Let `packageManifest` be the result of loading the `package.json` at - `packageRoot`. -- If `packageManifest` is not set, throw an error. -- Let `resolved` be the result of [Resolving package exports] to resolve `fullPath` - with the `sass` condition set, using `packageManifest`. + package] with `packageName`, . +- If a `package.json` file does not 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 `sass` as the + condition, `packageManifest` and `fullPath` as the `url`. - 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. -- Let `resolved` be the result of [Resolving package exports] to resolve `fullPath` - with the `style` condition set, using `packageManifest`. +- Let `resolved` be the result of [resolving package exports] with `style` as + the condition, `packageManifest` and `fullPath` as the `url`. - 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 result of [resolving package root values]. +- 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`. +- Return the result of [resolving a `file:` URL] with `resolved`. -[previous url]: ../accepted/prev-url.d.ts.md +[previous URL]: ../accepted/prev-url.d.ts.md [Resolving package exports]: #resolving-package-exports [resolving package root values]: #resolving-package-root-values [resolving a package name]: #resolving-a-package-name -[parsing a url]: https://url.spec.whatwg.org/#concept-url-parser +[JSON]: https://datatracker.ietf.org/doc/html/rfc8259 +[parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser [resolving the root directory for a package]: #resolving-the-root-directory-for-a-package -[resolving a file url]: ../spec/modules.md#resolving-a-file-url +[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url ### 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 the `package.json` file, -and returns a file URL. +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 `packagePath` appended with `sassValue`. + `css`: + - If `sassValue` starts with `./`, remove that substring. + - Return `${packagePath}/${sassValue}`. - Let `styleValue` be the value of `style` in `packageManifest`. - If `styleValue` is a relative path with an extension of `css`, return the `packagePath` appended with `styleValue`. -- Otherwise return the result of [resolving a file url] with `packagePath`. +- Otherwise return the result of [resolving a `file:` URL] with `packagePath`. -[resolving the root directory for a package]: - #resolving-the-root-directory-for-a-package -[resolving a file url]: ../spec/modules.md#resolving-a-file-url +[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package +[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url ### Resolving a package name -This algorithm takes a string, `url`, and returns the portion that identifies +This algorithm takes a string, `path`, and returns the portion that identifies the Node package. -- If `url` starts with `@`, it is a scoped package. Return the first 2 [url path - segments], including the separating `/` -- Otherwise, return the first url path segment. +- 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. -[url path segments]: https://url.spec.whatwg.org/#url-path-segment +[URL path segments]: https://url.spec.whatwg.org/#url-path-segment ### Resolving the root directory for a package @@ -325,12 +331,12 @@ absolute URL to the root directory for the most proximate installed `packageName`. - While `rootDirectory` is undefined: - Remove the final path segment from `previousUrl` - - If `node_modules` is the new final path segment of `previousUrl`, continue. - - Let `potentialPath` be `previousUrl` appended with `node_modules/` and - `packageName`. - - If `potentialPath` is a directory, let `rootDirectory` be `potentialPath`. - - Otherwise, if `previousUrl` is the root of the file system, throw an error. -- Return `rootDirectory`. + - If the new final path segment of `previousUrl` isn't `node_modules`: + - Let `potentialPath` be `previousUrl` appended with `node_modules/` and + `packageName`. + - If `potentialPath` is a directory, return `potentialPath`. + - Otherwise, if `previousUrl` is the root of the file system, throw an + error. [loading from node_modules folders]: https://nodejs.org/api/modules.html#loading-from-node_modules-folders @@ -338,13 +344,14 @@ absolute URL to the root directory for the most proximate installed ### Resolving package exports This algorithm takes a string `condition`, a package.json value -`packageManifest`, and a `pkg` URL `url`. It returns a file URL or null. +`packageManifest`, and a `pkg:` URL `url`. It returns a file URL or null. This algorithm should follow the Node resolution algorithm, as defined in the -Node documentation under [Node Modules] and [Conditional Exports]. Where -possible in Node, it can use [resolve.exports] which exposes the Node -resolution algorithm, allowing for per-path custom conditions, and without -needing filesystem access. +Node documentation under [Node Modules] and [Conditional Exports]. + +> Where possible in Node, it can use [resolve.exports] which exposes the Node +> resolution algorithm, allowing for per-path custom conditions, and without +> needing filesystem access. [resolve.exports]: https://github.com/lukeed/resolve.exports [Conditional Exports]: https://nodejs.org/api/packages.html#conditional-exports @@ -379,8 +386,6 @@ Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't cleanly fit into that specification. -[community conditions definition]: - https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions +[community conditions definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions [wintercg]: https://wintercg.org/ -[runtime keys proposal specification]: - https://runtime-keys.proposal.wintercg.org/#adding-a-key +[runtime keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key From 5a026bb726eee408b2ae759f3dc5ecb9a014efca Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 11:49:29 -0400 Subject: [PATCH 26/38] Allow style at root to load sass and scss files. Fix JS option name. --- proposal/package-importer.d.ts.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 1a39188b30..0fae49a39f 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -297,8 +297,10 @@ package, and `packageManifest`, which is the contents of that package's - If `sassValue` starts with `./`, remove that substring. - Return `${packagePath}/${sassValue}`. - Let `styleValue` be the value of `style` in `packageManifest`. -- If `styleValue` is a relative path with an extension of `css`, return the - `packagePath` appended with `styleValue`. +- If `styleValue` is a relative path with an extension of `sass`, `scss` or + `css`: + - If `styleValue` starts with `./`, remove that substring. + - Return `${packagePath}/${styleValue}`. - Otherwise return the result of [resolving a `file:` URL] with `packagePath`. [resolving the root directory for a package]: #resolving-the-root-directory-for-a-package @@ -359,7 +361,7 @@ Node documentation under [Node Modules] and [Conditional Exports]. ## Ecosystem Notes -The new `usePkgImporter` option will not be available in the [Legacy JS API]. +The new `useNodePkgImporter` option will not be available in the [Legacy JS API]. Third-party applications that don't support the modern API will be unable to use the built-in package importer. Some notable examples follow. @@ -367,7 +369,7 @@ the built-in package importer. Some notable examples follow. Vite is currently using the Legacy JS API, and has an [open issue] to update to the modern API. They also do not expose Sass options to the user, so would need -to enable the `usePkgImporter` on their behalf or expose some configuration. +to enable the `useNodePkgImporter` on their behalf or expose some configuration. [open issue]: https://github.com/vitejs/vite/issues/7116 From 949e1b3834f1f9489b8fb9ebc42d3035b51fc565 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 14:58:55 -0400 Subject: [PATCH 27/38] Importer clarifications --- proposal/package-importer.d.ts.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 0fae49a39f..09e42c4526 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -69,7 +69,7 @@ The built-in Node importer resolves in the following order: 1. `sass` key at package.json root 2. `style` key at package.json root 3. `index` file at package root, resolved for file extensions and partials -7. If there is a subpath, resolve that path relative to the package root, and +4. If there is a subpath, resolve that path relative to the package root, and resolve for file extensions and partials For library creators, the recommended method is to add a `sass` conditional @@ -198,7 +198,8 @@ declare module '../spec/js-api/options' { ### Package Importers This proposal defines the requirements for Package Importers written by users or -provided by implementations. It is a type of [Importer] and will handle +provided by implementations. It is a type of [Importer] and, in addition to the +standard requirements for importers, it must handle only the following non-canonical URLs: - with the scheme `pkg` @@ -221,11 +222,11 @@ Package Importers will reject the following patterns: ### Node Package Importer The Node package importer is an [importer] with an associated absolute `pkg:` -URL named `base`. When the Node package importer is invoked with a string named -`string` and a [previous URL] `previousUrl`: +URL. When the Node package importer is invoked with a string named `string` and +a [previous URL] `previousUrl`: -- 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. +- 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. - Let `resolved` be the result of [resolving a `pkg:` URL] with `url` and `previousURL`. - If `resolved` is null, return null. @@ -242,8 +243,9 @@ URL named `base`. When the Node package importer is invoked with a string named [resolving a `pkg:` URL]: #node-algorithm-for-resolving-a-pkg-url > Note that this algorithm does not automatically resolve index files, partials -> or extensions, except where specified. When using the `pkg:` URL scheme, -> authors need to explicitly use the fully resolved filename. +> or extensions when resolving paths defined in the package.json `exports` +> section. When using conditional exports, package authors need to explicitly +> expose all paths that should resolve to a Sass file. ## Procedures From 85ebde3f8f9c3ded99b261d4af0b11f92e8c59ac Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 15:03:22 -0400 Subject: [PATCH 28/38] Sort procedures by appearance --- proposal/package-importer.d.ts.md | 52 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 09e42c4526..52c7b9cd8e 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -24,10 +24,10 @@ Importer. * [Node Package Importer](#node-package-importer) * [Procedures](#procedures) * [Node Algorithm for Resolving a `pkg:` URL](#node-algorithm-for-resolving-a-pkg-url) - * [Resolving package root values](#resolving-package-root-values) * [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) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -259,7 +259,7 @@ It returns a canonical file path or null. and `subPath` be the path without the `packageName`. - Let `packageRoot` be the result of [resolving the root directory for a package] with `packageName`, . -- If a `package.json` file does not at `packageRoot`, throw an error. +- 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 `sass` as the @@ -287,27 +287,6 @@ It returns a canonical file path or null. #resolving-the-root-directory-for-a-package [resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url -### 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`: - - If `sassValue` starts with `./`, remove that substring. - - Return `${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`: - - If `styleValue` starts with `./`, remove that substring. - - Return `${packagePath}/${styleValue}`. -- Otherwise return the result of [resolving a `file:` URL] with `packagePath`. - -[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package -[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url - ### Resolving a package name This algorithm takes a string, `path`, and returns the portion that identifies @@ -317,8 +296,6 @@ the Node package. segments], including the separating `/`. - Otherwise, return the first URL path segment. -[URL path segments]: https://url.spec.whatwg.org/#url-path-segment - ### Resolving the root directory for a package This algorithm takes a string, `packageName`, an absolute URL @@ -361,6 +338,31 @@ Node documentation under [Node Modules] and [Conditional Exports]. [Conditional Exports]: https://nodejs.org/api/packages.html#conditional-exports [Node Modules]: https://nodejs.org/api/modules.html#all-together +### 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`: + - If `sassValue` starts with `./`, remove that substring. + - Return `${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`: + - If `styleValue` starts with `./`, remove that substring. + - Return `${packagePath}/${styleValue}`. +- Otherwise return the result of [resolving a `file:` URL] with `packagePath`. + +[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package +[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url + + +[URL path segments]: https://url.spec.whatwg.org/#url-path-segment + + ## Ecosystem Notes The new `useNodePkgImporter` option will not be available in the [Legacy JS API]. From 5dbe6dc0777596634b5f1521a80e8736ac4539c2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 15:08:37 -0400 Subject: [PATCH 29/38] Updates to resolving package root values --- proposal/package-importer.d.ts.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 52c7b9cd8e..17c49219bc 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -348,16 +348,17 @@ package, and `packageManifest`, which is the contents of that package's - If `sassValue` is a relative path with an extension of `sass`, `scss` or `css`: - If `sassValue` starts with `./`, remove that substring. - - Return `${packagePath}/${sassValue}`. + - Return the `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`: - If `styleValue` starts with `./`, remove that substring. - - Return `${packagePath}/${styleValue}`. -- Otherwise return the result of [resolving a `file:` URL] with `packagePath`. + - Return the `file:` URL for `${packagePath}/${styleValue}`. +- Otherwise return the result of [resolving a `file:` URL for extensions] with + `packagePath + "/index"`. [resolving the root directory for a package]: #resolving-the-root-directory-for-a-package -[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url +[resolving a `file:` URL for extensions]: ../spec/modules.md#resolving-a-file-url-for-extensions [URL path segments]: https://url.spec.whatwg.org/#url-path-segment From eff344d24993107af6f4c1c190d6e8a1c33d3a3e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 8 Aug 2023 15:14:21 -0400 Subject: [PATCH 30/38] Clean up root directory resolution --- proposal/package-importer.d.ts.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 17c49219bc..961652e5ee 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -258,7 +258,7 @@ It returns a canonical file path or null. - Let `packageName` be the result of [resolving a package name] with `fullPath`, and `subPath` be the path without the `packageName`. - Let `packageRoot` be the result of [resolving the root directory for a - package] with `packageName`, . + package] with `packageName` and `previousURL`. - 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]. @@ -298,10 +298,9 @@ the Node package. ### Resolving the root directory for a package -This algorithm takes a string, `packageName`, an absolute URL -`currentDirectory`, and an optional absolute URL `previousUrl`, and returns an -absolute URL to the root directory for the most proximate installed -`packageName`. +This algorithm takes a string, `packageName`, and an optional absolute URL +`previousUrl`, and returns an absolute URL to the root directory for the most +proximate installed `packageName`. > We need to replicate Node's behavior, as defined in [Loading from node_modules > folders], of walking up the directory chain to find packages from From 676c409e53a8e916131eee1f42d07de5f5e5a7c1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 9 Aug 2023 15:12:04 -0400 Subject: [PATCH 31/38] Point Resolving package exports procedure to Node procedure --- proposal/package-importer.d.ts.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 961652e5ee..2937870f32 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -263,12 +263,12 @@ It returns a canonical file path or null. - 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 `sass` as the - condition, `packageManifest` and `fullPath` as the `url`. + condition, `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. -- Let `resolved` be the result of [resolving package exports] with `style` as - the condition, `packageManifest` and `fullPath` as the `url`. +- Let `resolved` be the result of [resolving package exports] with `style` as the + condition, `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. @@ -324,18 +324,20 @@ proximate installed `packageName`. ### Resolving package exports This algorithm takes a string `condition`, a package.json value -`packageManifest`, and a `pkg:` URL `url`. It returns a file URL or null. +`packageManifest`, a directory URL `packageRoot` and a relative URL path +`subpath`. It returns a file URL or null. -This algorithm should follow the Node resolution algorithm, as defined in the -Node documentation under [Node Modules] and [Conditional Exports]. +- Let `exports` be the value of `packageManifest.exports`. +- If `exports` is undefined, return null. +- Return the result of `PACKAGE_EXPORTS_RESOLVE(packageRoot, subpath, exports, + [condition])` as defined in the [Node resolution algorithm specification]. > Where possible in Node, it can use [resolve.exports] which exposes the Node > resolution algorithm, allowing for per-path custom conditions, and without > needing filesystem access. +[Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification [resolve.exports]: https://github.com/lukeed/resolve.exports -[Conditional Exports]: https://nodejs.org/api/packages.html#conditional-exports -[Node Modules]: https://nodejs.org/api/modules.html#all-together ### Resolving package root values From 7422ab8360a0cc539ca0223138ef0cd8b54657b2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 9 Aug 2023 15:39:24 -0400 Subject: [PATCH 32/38] Point package root procedure to Node procedures --- proposal/package-importer.d.ts.md | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 2937870f32..086f124d59 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -223,7 +223,7 @@ Package Importers will reject the following patterns: The Node package importer is an [importer] with an associated absolute `pkg:` URL. When the Node package importer is invoked with a string named `string` and -a [previous URL] `previousUrl`: +a [previous URL] `previousURL`: - Let `url` be the result of [parsing `string` as a URL][parsing a URL]. If this returns a failure, throw that failure. @@ -299,27 +299,15 @@ the Node package. ### Resolving the root directory for a package This algorithm takes a string, `packageName`, and an optional absolute URL -`previousUrl`, and returns an absolute URL to the root directory for the most +`previousURL`, and returns an absolute URL to the root directory for the most proximate installed `packageName`. -> We need to replicate Node's behavior, as defined in [Loading from node_modules -> folders], of walking up the directory chain to find packages from -> `previousUrl`, so that packages can use the correct version of the packages -> that they depend on. - -- If `previousUrl` is null, return the result of `require.resolve` with the - `packageName`. -- While `rootDirectory` is undefined: - - Remove the final path segment from `previousUrl` - - If the new final path segment of `previousUrl` isn't `node_modules`: - - Let `potentialPath` be `previousUrl` appended with `node_modules/` and - `packageName`. - - If `potentialPath` is a directory, return `potentialPath`. - - Otherwise, if `previousUrl` is the root of the file system, throw an - error. - -[loading from node_modules folders]: - https://nodejs.org/api/modules.html#loading-from-node_modules-folders +- Let `parentURL` be the value of `previousURL` if it is defined, otherwise the + URL to the current directory. +- Return the result of `PACKAGE_RESOLVE(packageName, parentURL)` as defined in + the [Node resolution algorithm specification]. + +[Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification ### Resolving package exports From eb26657651c013c84d0b4fd5d0c40687bcfd8ad2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 11 Aug 2023 15:43:41 -0400 Subject: [PATCH 33/38] Give a name to input url --- proposal/package-importer.d.ts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 086f124d59..4a86c8a56a 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -251,8 +251,8 @@ a [previous URL] `previousURL`: ### Node Algorithm for Resolving a `pkg:` URL -This algorithm takes a URL with scheme `pkg:`, and an optional URL `previousURL`. -It returns a canonical file path or null. +This algorithm takes a URL with scheme `pkg:` named `url`, and an optional URL +`previousURL`. It returns a canonical file path or null. - Let `fullPath` be `url`'s path. - Let `packageName` be the result of [resolving a package name] with `fullPath`, From 2adad7c52f5af54a9abcdfdce3f600783d514661 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 15 Aug 2023 15:16:52 -0400 Subject: [PATCH 34/38] Lint --- proposal/package-importer.d.ts.md | 145 +++++++++++++++++++----------- 1 file changed, 93 insertions(+), 52 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 4a86c8a56a..7207ea3063 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -63,12 +63,18 @@ resolved within `node_modules`, using the [Node resolution algorithm]. The built-in Node importer resolves in the following order: -1. `sass` condition in package.json `exports` -2. `style` condition in package.json `exports` +1. `sass` condition in package.json `exports`. + +2. `style` condition in package.json `exports`. + 3. If no subpath, then find root export: - 1. `sass` key at package.json root - 2. `style` key at package.json root - 3. `index` file at package root, resolved for file extensions and partials + + 1. `sass` key at package.json root. + + 2. `style` key at package.json root. + + 3. `index` file at package root, resolved for file extensions and partials. + 4. If there is a subpath, resolve that path relative to the package root, and resolve for file extensions and partials @@ -202,9 +208,9 @@ provided by implementations. It is a type of [Importer] and, in addition to the standard requirements for importers, it must handle only the following non-canonical URLs: -- with the scheme `pkg` -- whose path begins with a package name -- optionally followed by a path, with path segments separated with a forward +* with the scheme `pkg` +* whose path begins with a package name +* 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 should @@ -213,8 +219,8 @@ supports scoped package names, which start with `@` followed by 2 path segments. Package Importers will reject the following patterns: -- A URL whose path begins with `/`. -- A URL with non-empty/null username, password, host, port, query, or fragment. +* A URL whose path begins with `/`. +* A URL with non-empty/null username, password, host, port, query, or fragment. [importer]: ../spec/modules.md#importer @@ -225,19 +231,30 @@ The Node package importer is an [importer] with an associated absolute `pkg:` URL. When the Node package importer is invoked with a string named `string` and a [previous URL] `previousURL`: -- Let `url` be the result of [parsing `string` as a URL][parsing a URL]. If this +* 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. -- Let `resolved` be the result of [resolving a `pkg:` URL] with `url` and `previousURL`. -- 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`. + +* If `url`'s scheme is not `pkg:`, return null. + +* Let `resolved` be the result of [resolving a `pkg:` URL] with `url` and + `previousURL`. + +* 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 `file:` URL](../spec/modules.md#resolving-a-file-url) > guarantees that `resolved` will have one of these extensions. -- Return `text`, `syntax`, and `resolved`. + +* Return `text`, `syntax`, and `resolved`. [parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser [resolving a `pkg:` URL]: #node-algorithm-for-resolving-a-pkg-url @@ -254,27 +271,40 @@ a [previous URL] `previousURL`: This algorithm takes a URL with scheme `pkg:` named `url`, and an optional URL `previousURL`. It returns a canonical file path or null. -- Let `fullPath` be `url`'s path. -- Let `packageName` be the result of [resolving a package name] with `fullPath`, +* Let `fullPath` be `url`'s path. + +* Let `packageName` be the result of [resolving a package name] with `fullPath`, and `subPath` be the path without the `packageName`. -- Let `packageRoot` be the result of [resolving the root directory for a + +* Let `packageRoot` be the result of [resolving the root directory for a package] with `packageName` and `previousURL`. -- 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 + +* 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 `sass` as the + +* Let `resolved` be the result of [resolving package exports] with `sass` as the condition, `packageRoot`, `subpath`, and `packageManifest`. - - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or + + * 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. -- Let `resolved` be the result of [resolving package exports] with `style` as the + + * Otherwise, if `resolved` is not null, throw an error. + +* Let `resolved` be the result of [resolving package exports] with `style` as the condition, `packageRoot`, `subpath`, and `packageManifest`. - - If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or + + * 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`. + + * 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`. [previous URL]: ../accepted/prev-url.d.ts.md @@ -283,8 +313,7 @@ This algorithm takes a URL with scheme `pkg:` named `url`, and an optional URL [resolving a package name]: #resolving-a-package-name [JSON]: https://datatracker.ietf.org/doc/html/rfc8259 [parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser -[resolving the root directory for a package]: - #resolving-the-root-directory-for-a-package +[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package [resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url ### Resolving a package name @@ -292,9 +321,10 @@ This algorithm takes a URL with scheme `pkg:` named `url`, and an optional URL 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 +* 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. + +* Otherwise, return the first URL path segment. ### Resolving the root directory for a package @@ -302,9 +332,10 @@ This algorithm takes a string, `packageName`, and an optional absolute URL `previousURL`, and returns an absolute URL to the root directory for the most proximate installed `packageName`. -- Let `parentURL` be the value of `previousURL` if it is defined, otherwise the +* Let `parentURL` be the value of `previousURL` if it is defined, otherwise the URL to the current directory. -- Return the result of `PACKAGE_RESOLVE(packageName, parentURL)` as defined in + +* Return the result of `PACKAGE_RESOLVE(packageName, parentURL)` as defined in the [Node resolution algorithm specification]. [Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification @@ -315,9 +346,11 @@ This algorithm takes a string `condition`, 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. -- Return the result of `PACKAGE_EXPORTS_RESOLVE(packageRoot, subpath, exports, +* Let `exports` be the value of `packageManifest.exports`. + +* If `exports` is undefined, return null. + +* Return the result of `PACKAGE_EXPORTS_RESOLVE(packageRoot, subpath, exports, [condition])` as defined in the [Node resolution algorithm specification]. > Where possible in Node, it can use [resolve.exports] which exposes the Node @@ -333,17 +366,25 @@ 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 +* Let `sassValue` be the value of `sass` in `packageManifest`. + +* If `sassValue` is a relative path with an extension of `sass`, `scss` or `css`: - - If `sassValue` starts with `./`, remove that substring. - - Return the `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 + + * If `sassValue` starts with `./`, remove that substring. + + * Return the `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`: - - If `styleValue` starts with `./`, remove that substring. - - Return the `file:` URL for `${packagePath}/${styleValue}`. -- Otherwise return the result of [resolving a `file:` URL for extensions] with + + * If `styleValue` starts with `./`, remove that substring. + + * Return the `file:` URL for `${packagePath}/${styleValue}`. + +* Otherwise return the result of [resolving a `file:` URL for extensions] with `packagePath + "/index"`. [resolving the root directory for a package]: #resolving-the-root-directory-for-a-package From be55d1475f0730f9d58418f4f4cd787d2cf734cf Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 18 Aug 2023 09:14:11 -0400 Subject: [PATCH 35/38] Switch to pkgImporter: string option --- proposal/package-importer.d.ts.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 7207ea3063..106786bd74 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -18,7 +18,7 @@ Importer. * [Available as an opt-in setting](#available-as-an-opt-in-setting) * [Node Resolution Decisions](#node-resolution-decisions) * [Types](#types) - * [`useNodePkgImporter`](#usenodepkgimporter) + * [`pkgImporter`](#pkgimporter) * [Semantics](#semantics) * [Package Importers](#package-importers) * [Node Package Importer](#node-package-importer) @@ -177,24 +177,25 @@ tends to be used solely for `css` files, we will support `scss`, `sass` and import '../spec/js-api'; ``` -#### `useNodePkgImporter` +#### `pkgImporter` -If true, the compiler will use the built-in Node package importer to resolve any URL -with the `pkg:` scheme. This importer follows Node logic to locate Sass files. +If set, the compiler will use the specified built-in package importer to resolve +any URL with the `pkg:` scheme. Currently, the only available package importer +is `node`, which follows Node resolution logic to locate Sass files. -Defaults to false. +Defaults to undefined. ```ts declare module '../spec/js-api/options' { interface Options { /** - * Whether or not to enable the built-in package importer to resolve any URL - * with the `pkg:` scheme. This importer follows Node.js resolution logic. + * Specifies which, if any, built-in package importer to resolve any URL + * with the `pkg:` scheme. The `node` importer follows Node resolution logic. * - * @defaultValue `false` + * @defaultValue `undefined` * @category Input */ - useNodePkgImporter?: boolean; + pkgImporter?: 'node'; } } ``` @@ -396,7 +397,7 @@ package, and `packageManifest`, which is the contents of that package's ## Ecosystem Notes -The new `useNodePkgImporter` option will not be available in the [Legacy JS API]. +The new `pkgImporter` option will not be available in the [Legacy JS API]. Third-party applications that don't support the modern API will be unable to use the built-in package importer. Some notable examples follow. @@ -404,7 +405,7 @@ the built-in package importer. Some notable examples follow. Vite is currently using the Legacy JS API, and has an [open issue] to update to the modern API. They also do not expose Sass options to the user, so would need -to enable the `useNodePkgImporter` on their behalf or expose some configuration. +to enable the `pkgImporter` on their behalf or expose some configuration. [open issue]: https://github.com/vitejs/vite/issues/7116 From 6c3025b9d329edfda449c5da4c3dd4637443933f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 18 Aug 2023 09:37:49 -0400 Subject: [PATCH 36/38] Add pkg importer support to legacy API --- proposal/package-importer.d.ts.md | 39 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 106786bd74..04af6f8f15 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -16,6 +16,7 @@ Importer. * [Using a `pkg:` URL scheme](#using-a-pkg-url-scheme) * [No built-in `pkg:` resolver for browsers](#no-built-in-pkg-resolver-for-browsers) * [Available as an opt-in setting](#available-as-an-opt-in-setting) + * [Available in legacy API](#available-in-legacy-api) * [Node Resolution Decisions](#node-resolution-decisions) * [Types](#types) * [`pkgImporter`](#pkgimporter) @@ -140,6 +141,11 @@ potential for file system interaction to `compileString` and functions to have control over what files get accessed, and there's even a risk of leaking file contents in error messages. +#### Available in legacy API + +The built-in Node Package Importer will be added to the legacy API in order to +reduce the barrier to adoption. + #### Node Resolution Decisions The current recommendation for resolving packages in Node is to add @@ -198,6 +204,17 @@ declare module '../spec/js-api/options' { pkgImporter?: 'node'; } } + +export interface LegacySharedOptions { + /** + * Specifies which, if any, built-in package importer to resolve any URL + * with the `pkg:` scheme. The `node` importer follows Node resolution logic. + * + * @defaultValue `undefined` + * @category Input + */ + pkgImporter?: 'node'; +} ``` ## Semantics @@ -397,28 +414,6 @@ package, and `packageManifest`, which is the contents of that package's ## Ecosystem Notes -The new `pkgImporter` option will not be available in the [Legacy JS API]. -Third-party applications that don't support the modern API will be unable to use -the built-in package importer. Some notable examples follow. - -[Legacy JS API]: https://sass-lang.com/documentation/js-api/#md:legacy-api - -Vite is currently using the Legacy JS API, and has an [open issue] to update to -the modern API. They also do not expose Sass options to the user, so would need -to enable the `pkgImporter` on their behalf or expose some configuration. - -[open issue]: https://github.com/vitejs/vite/issues/7116 - -Webpack's [sass-loader] allows users to opt in to the modern API and exposes -Sass options to users. - -[sass-loader]: https://webpack.js.org/loaders/sass-loader/ - -For Rollup, [rollup-plugin-sass] uses the Legacy JS API. They do expose Sass -options to the user. - -[rollup-plugin-sass]: https://github.com/elycruz/rollup-plugin-sass - It may be worth adding a [Community Conditions Definition] to the Node Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway in standardizing the usage of custom conditions for runtimes, but Sass doesn't From b99d1958c157a4efd2d405e7101315e42ead9fda Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 18 Aug 2023 10:55:30 -0400 Subject: [PATCH 37/38] Add partial, extension and index matching on conditional export alias --- proposal/package-importer.d.ts.md | 72 ++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index 04af6f8f15..eafe1aa6f8 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -29,6 +29,7 @@ Importer. * [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) * [Ecosystem Notes](#ecosystem-notes) ## Background @@ -177,6 +178,22 @@ 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. + +```json +{ + "_variables.scss": { + "sass": "./src/sass/_variables.scss" + } +} +``` + ## Types ```ts @@ -277,10 +294,6 @@ a [previous URL] `previousURL`: [parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser [resolving a `pkg:` URL]: #node-algorithm-for-resolving-a-pkg-url -> Note that this algorithm does not automatically resolve index files, partials -> or extensions when resolving paths defined in the package.json `exports` -> section. When using conditional exports, package authors need to explicitly -> expose all paths that should resolve to a Sass file. ## Procedures @@ -368,13 +381,39 @@ This algorithm takes a string `condition`, a package.json value * If `exports` is undefined, return null. -* Return the result of `PACKAGE_EXPORTS_RESOLVE(packageRoot, subpath, exports, - [condition])` as defined in the [Node resolution algorithm specification]. +* 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, [condition])` + as defined in the [Node resolution algorithm specification, with each + `subpathVariants` as `subpathVariant`. + +* 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, [condition])` + 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, it 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 [Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification [resolve.exports]: https://github.com/lukeed/resolve.exports @@ -412,6 +451,27 @@ package, and `packageManifest`, which is the contents of that package's [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]: ../spec/modules.md#basename + ## Ecosystem Notes It may be worth adding a [Community Conditions Definition] to the Node From e926efa981f207a474af009b340c9cd2ee3aeee7 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 18 Aug 2023 11:37:32 -0400 Subject: [PATCH 38/38] Fix typo --- proposal/package-importer.d.ts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/package-importer.d.ts.md b/proposal/package-importer.d.ts.md index eafe1aa6f8..579acbbe99 100644 --- a/proposal/package-importer.d.ts.md +++ b/proposal/package-importer.d.ts.md @@ -465,7 +465,7 @@ potential subpaths, resolving for partials and file extensions. * Otherwise, add `subpath` + `.scss`, `subpath` + `.sass`, and `subpath` + `.css` to `paths`. -* If `subpath`'s [basename]' does not start with `_`, for each `item` in +* If `subpath`'s [basename] does not start with `_`, for each `item` in `paths`, prepend `"_"` to the basename, and add to `paths`. * Return `paths`.