module: allow subpath imports that start with #/

It's a common ecosystem pattern to map a source root directory to
`@/` but it requires special tooling support. This turns `#/*` into
a more realistic alternative for that pattern.

PR-URL: https://github.com/nodejs/node/pull/60864
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Claudio Wunder <cwunder@gnome.org>
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
This commit is contained in:
Jan Olaf Martin 2025-12-03 18:27:04 -08:00 committed by GitHub
parent cb0eb58144
commit e8c9c43336
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 12 additions and 5 deletions

View File

@ -1102,7 +1102,7 @@ Note: This function is directly invoked by the CommonJS resolution algorithm.
Note: This function is directly invoked by the CommonJS resolution algorithm.
> 1. Assert: _specifier_ begins with _"#"_.
> 2. If _specifier_ is exactly equal to _"#"_ or starts with _"#/"_, then
> 2. If _specifier_ is exactly equal to _"#"_, then
> 1. Throw an _Invalid Module Specifier_ error.
> 3. Let _packageURL_ be the result of **LOOKUP\_PACKAGE\_SCOPE**(_parentURL_).
> 4. If _packageURL_ is not **null**, then

View File

@ -527,6 +527,10 @@ can be written:
added:
- v14.6.0
- v12.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60864
description: Allow subpath imports that start with `#/`.
-->
In addition to the [`"exports"`][] field, there is a package `"imports"` field

View File

@ -692,8 +692,7 @@ function patternKeyCompare(a, b) {
* @returns {URL} The resolved import URL.
*/
function packageImportsResolve(name, base, conditions) {
if (name === '#' || StringPrototypeStartsWith(name, '#/') ||
StringPrototypeEndsWith(name, '/')) {
if (name === '#' || StringPrototypeEndsWith(name, '/')) {
const reason = 'is not a valid internal imports specifier name';
throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
}

View File

@ -15,6 +15,10 @@ const { requireImport, importImport } = importer;
const internalImports = new Map([
// Base case
['#test', maybeWrapped({ default: 'test' })],
// Root wildcard import
['#/foo', maybeWrapped({ default: 'foo' })],
// Explicit #/ mapping
['#/initialslash', maybeWrapped({ default: 'test' })],
// import / require conditions
['#branch', maybeWrapped({ default: isRequire ? 'requirebranch' : 'importbranch' })],
// Subpath imports
@ -64,8 +68,6 @@ const { requireImport, importImport } = importer;
['#external/subpath/x%5Cy', 'must not include encoded "/" or "\\"'],
// Target must have a name
['#', '#'],
// Initial slash target must have a leading name
['#/initialslash', '#/initialslash'],
// Percent-encoded target paths
['#encodedslash', 'must not include encoded "/" or "\\"'],
['#encodedbackslash', 'must not include encoded "/" or "\\"'],

View File

@ -1,5 +1,6 @@
{
"imports": {
"#/*": "./src/*.js",
"#branch": {
"import": "./importbranch.js",
"require": "./requirebranch.js"

View File

@ -0,0 +1 @@
module.exports = 'foo';