Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 607bc74

Browse filesBrowse files
guybedfordrichardlau
authored andcommitted
module: support pattern trailers
PR-URL: #39635 Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
1 parent f30a26b commit 607bc74
Copy full SHA for 607bc74

File tree

Expand file treeCollapse file tree

5 files changed

+99
-30
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+99
-30
lines changed
Open diff view settings
Collapse file

‎doc/api/esm.md‎

Copy file name to clipboardExpand all lines: doc/api/esm.md
+41-14Lines changed: 41 additions & 14 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1169,25 +1169,36 @@ The resolver can throw the following errors:
11691169
**PACKAGE_IMPORTS_EXPORTS_RESOLVE**(_matchKey_, _matchObj_, _packageURL_,
11701170
_isImports_, _conditions_)
11711171

1172-
> 1. If _matchKey_ is a key of _matchObj_, and does not end in _"*"_, then
1172+
> 1. If _matchKey_ is a key of _matchObj_ and does not end in _"/"_ or contain
1173+
> _"*"_, then
11731174
> 1. Let _target_ be the value of _matchObj_\[_matchKey_\].
11741175
> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
11751176
> _packageURL_, _target_, _""_, **false**, _isImports_, _conditions_).
11761177
> 1. Return the object _{ resolved, exact: **true** }_.
1177-
> 1. Let _expansionKeys_ be the list of keys of _matchObj_ ending in _"/"_
1178-
> or _"*"_, sorted by length descending.
1178+
> 1. Let _expansionKeys_ be the list of keys of _matchObj_ either ending in
1179+
> _"/"_ or containing only a single _"*"_, sorted by the sorting function
1180+
> **PATTERN_KEY_COMPARE** which orders in descending order of specificity.
11791181
> 1. For each key _expansionKey_ in _expansionKeys_, do
1180-
> 1. If _expansionKey_ ends in _"*"_ and _matchKey_ starts with but is
1181-
> not equal to the substring of _expansionKey_ excluding the last _"*"_
1182-
> character, then
1183-
> 1. Let _target_ be the value of _matchObj_\[_expansionKey_\].
1184-
> 1. Let _subpath_ be the substring of _matchKey_ starting at the
1185-
> index of the length of _expansionKey_ minus one.
1186-
> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
1187-
> _packageURL_, _target_, _subpath_, **true**, _isImports_,
1188-
> _conditions_).
1189-
> 1. Return the object _{ resolved, exact: **true** }_.
1190-
> 1. If _matchKey_ starts with _expansionKey_, then
1182+
> 1. Let _patternBase_ be **null**.
1183+
> 1. If _expansionKey_ contains _"*"_, set _patternBase_ to the substring of
1184+
> _expansionKey_ up to but excluding the first _"*"_ character.
1185+
> 1. If _patternBase_ is not **null** and _matchKey_ starts with but is not
1186+
> equal to _patternBase_, then
1187+
> 1. Let _patternTrailer_ be the substring of _expansionKey_ from the
1188+
> index after the first _"*"_ character.
1189+
> 1. If _patternTrailer_ has zero length, or if _matchKey_ ends with
1190+
> _patternTrailer_ and the length of _matchKey_ is greater than or
1191+
> equal to the length of _expansionKey_, then
1192+
> 1. Let _target_ be the value of _matchObj_\[_expansionKey_\].
1193+
> 1. Let _subpath_ be the substring of _matchKey_ starting at the
1194+
> index of the length of _patternBase_ up to the length of
1195+
> _matchKey_ minus the length of _patternTrailer_.
1196+
> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
1197+
> _packageURL_, _target_, _subpath_, **true**, _isImports_,
1198+
> _conditions_).
1199+
> 1. Return the object _{ resolved, exact: **true** }_.
1200+
> 1. Otherwise if _patternBase_ is **null** and _matchKey_ starts with
1201+
> _expansionKey_, then
11911202
> 1. Let _target_ be the value of _matchObj_\[_expansionKey_\].
11921203
> 1. Let _subpath_ be the substring of _matchKey_ starting at the
11931204
> index of the length of _expansionKey_.
@@ -1197,6 +1208,22 @@ _isImports_, _conditions_)
11971208
> 1. Return the object _{ resolved, exact: **false** }_.
11981209
> 1. Return the object _{ resolved: **null**, exact: **true** }_.
11991210

1211+
**PATTERN_KEY_COMPARE**(_keyA_, _keyB_)
1212+
1213+
> 1. Assert: _keyA_ ends with _"/"_ or contains only a single _"*"_.
1214+
> 1. Assert: _keyB_ ends with _"/"_ or contains only a single _"*"_.
1215+
> 1. Let _baseLengthA_ be the index of _"*"_ in _keyA_ plus one, if _keyA_
1216+
> contains _"*"_, or the length of _keyA_ otherwise.
1217+
> 1. Let _baseLengthB_ be the index of _"*"_ in _keyB_ plus one, if _keyB_
1218+
> contains _"*"_, or the length of _keyB_ otherwise.
1219+
> 1. If _baseLengthA_ is greater than _baseLengthB_, return -1.
1220+
> 1. If _baseLengthB_ is greater than _baseLengthA_, return 1.
1221+
> 1. If _keyA_ does not contain _"*"_, return 1.
1222+
> 1. If _keyB_ does not contain _"*"_, return -1.
1223+
> 1. If the length of _keyA_ is greater than the length of _keyB_, return -1.
1224+
> 1. If the length of _keyB_ is greater than the length of _keyA_, return 1.
1225+
> 1. Return 0.
1226+
12001227
**PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _pattern_,
12011228
_internal_, _conditions_)
12021229

Collapse file

‎doc/api/packages.md‎

Copy file name to clipboardExpand all lines: doc/api/packages.md
+2-3Lines changed: 2 additions & 3 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,8 @@ For these use cases, subpath export patterns can be used instead:
360360
**`*` maps expose nested subpaths as it is a string replacement syntax
361361
only.**
362362

363-
The left hand matching pattern must always end in `*`. All instances of `*` on
364-
the right hand side will then be replaced with this value, including if it
365-
contains any `/` separators.
363+
All instances of `*` on the right hand side will then be replaced with this
364+
value, including if it contains any `/` separators.
366365

367366
```js
368367
import featureX from 'es-module-package/features/x';
Collapse file

‎lib/internal/modules/esm/resolve.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/resolve.js
+39-13Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const {
1515
SafeSet,
1616
String,
1717
StringPrototypeEndsWith,
18+
StringPrototypeIncludes,
1819
StringPrototypeIndexOf,
20+
StringPrototypeLastIndexOf,
1921
StringPrototypeReplace,
2022
StringPrototypeSlice,
2123
StringPrototypeSplit,
@@ -502,7 +504,9 @@ function packageExportsResolve(
502504
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base))
503505
exports = { '.': exports };
504506

505-
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath)) {
507+
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath) &&
508+
!StringPrototypeIncludes(packageSubpath, '*') &&
509+
!StringPrototypeEndsWith(packageSubpath, '/')) {
506510
const target = exports[packageSubpath];
507511
const resolved = resolvePackageTarget(
508512
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
@@ -513,30 +517,38 @@ function packageExportsResolve(
513517
}
514518

515519
let bestMatch = '';
520+
let bestMatchSubpath;
516521
const keys = ObjectGetOwnPropertyNames(exports);
517522
for (let i = 0; i < keys.length; i++) {
518523
const key = keys[i];
519-
if (key[key.length - 1] === '*' &&
524+
const patternIndex = StringPrototypeIndexOf(key, '*');
525+
if (patternIndex !== -1 &&
520526
StringPrototypeStartsWith(packageSubpath,
521-
StringPrototypeSlice(key, 0, -1)) &&
522-
packageSubpath.length >= key.length &&
523-
key.length > bestMatch.length) {
524-
bestMatch = key;
527+
StringPrototypeSlice(key, 0, patternIndex))) {
528+
const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
529+
if (packageSubpath.length >= key.length &&
530+
StringPrototypeEndsWith(packageSubpath, patternTrailer) &&
531+
patternKeyCompare(bestMatch, key) === 1 &&
532+
StringPrototypeLastIndexOf(key, '*') === patternIndex) {
533+
bestMatch = key;
534+
bestMatchSubpath = StringPrototypeSlice(
535+
packageSubpath, patternIndex,
536+
packageSubpath.length - patternTrailer.length);
537+
}
525538
} else if (key[key.length - 1] === '/' &&
526539
StringPrototypeStartsWith(packageSubpath, key) &&
527-
key.length > bestMatch.length) {
540+
patternKeyCompare(bestMatch, key) === 1) {
528541
bestMatch = key;
542+
bestMatchSubpath = StringPrototypeSlice(packageSubpath, key.length);
529543
}
530544
}
531545

532546
if (bestMatch) {
533547
const target = exports[bestMatch];
534-
const pattern = bestMatch[bestMatch.length - 1] === '*';
535-
const subpath = StringPrototypeSubstr(packageSubpath, bestMatch.length -
536-
(pattern ? 1 : 0));
537-
const resolved = resolvePackageTarget(packageJSONUrl, target, subpath,
538-
bestMatch, base, pattern, false,
539-
conditions);
548+
const pattern = StringPrototypeIncludes(bestMatch, '*');
549+
const resolved = resolvePackageTarget(packageJSONUrl, target,
550+
bestMatchSubpath, bestMatch, base,
551+
pattern, false, conditions);
540552
if (resolved === null || resolved === undefined)
541553
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
542554
return { resolved, exact: pattern };
@@ -545,6 +557,20 @@ function packageExportsResolve(
545557
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
546558
}
547559

560+
function patternKeyCompare(a, b) {
561+
const aPatternIndex = StringPrototypeIndexOf(a, '*');
562+
const bPatternIndex = StringPrototypeIndexOf(b, '*');
563+
const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
564+
const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
565+
if (baseLenA > baseLenB) return -1;
566+
if (baseLenB > baseLenA) return 1;
567+
if (aPatternIndex === -1) return 1;
568+
if (bPatternIndex === -1) return -1;
569+
if (a.length > b.length) return -1;
570+
if (b.length > a.length) return 1;
571+
return 0;
572+
}
573+
548574
function packageImportsResolve(name, base, conditions) {
549575
if (name === '#' || StringPrototypeStartsWith(name, '#/')) {
550576
const reason = 'is not a valid internal imports specifier name';
Collapse file

‎test/es-module/test-esm-exports.mjs‎

Copy file name to clipboardExpand all lines: test/es-module/test-esm-exports.mjs
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
3535
['pkgexports-sugar', { default: 'main' }],
3636
// Path patterns
3737
['pkgexports/subpath/sub-dir1', { default: 'main' }],
38+
['pkgexports/subpath/sub-dir1.js', { default: 'main' }],
3839
['pkgexports/features/dir1', { default: 'main' }],
40+
['pkgexports/dir1/dir1/trailer', { default: 'main' }],
41+
['pkgexports/dir2/dir2/trailer', { default: 'index' }],
42+
['pkgexports/a/dir1/dir1', { default: 'main' }],
43+
['pkgexports/a/b/dir1/dir1', { default: 'main' }],
3944
]);
4045

4146
if (isRequire) {
@@ -77,6 +82,8 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
7782
['pkgexports/null/subpath', './null/subpath'],
7883
// Empty fallback
7984
['pkgexports/nofallback1', './nofallback1'],
85+
// Non pattern matches
86+
['pkgexports/trailer', './trailer'],
8087
]);
8188

8289
const invalidExports = new Map([
@@ -147,6 +154,8 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
147154
['pkgexports/sub/not-a-file.js', `pkgexports${sep}not-a-file.js`],
148155
// No extension lookups
149156
['pkgexports/no-ext', `pkgexports${sep}asdf`],
157+
// Pattern specificity
158+
['pkgexports/dir2/trailer', `subpath${sep}dir2.js`],
150159
]);
151160

152161
if (!isRequire) {
Collapse file

‎test/fixtures/node_modules/pkgexports/package.json‎

Copy file name to clipboardExpand all lines: test/fixtures/node_modules/pkgexports/package.json
+8Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.