Skip to content

Commit 607bc74

Browse files
guybedfordrichardlau
authored andcommitted
module: support pattern trailers
PR-URL: #39635 Reviewed-By: Bradley Farias <[email protected]>
1 parent f30a26b commit 607bc74

File tree

5 files changed

+99
-30
lines changed

5 files changed

+99
-30
lines changed

‎doc/api/esm.md‎

Lines changed: 41 additions & 14 deletions
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_, return1.
1221+
>1. If _keyA_ does not contain _"*"_, return1.
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_, return1.
1225+
>1. Return 0.
1226+
12001227
**PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _pattern_,
12011228
_internal_, _conditions_)
12021229

‎doc/api/packages.md‎

Lines changed: 2 additions & 3 deletions
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
importfeatureXfrom'es-module-package/features/x';

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

Lines 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
consttarget=exports[packageSubpath];
507511
constresolved=resolvePackageTarget(
508512
packageJSONUrl,target,'',packageSubpath,base,false,false,conditions
@@ -513,30 +517,38 @@ function packageExportsResolve(
513517
}
514518

515519
letbestMatch='';
520+
letbestMatchSubpath;
516521
constkeys=ObjectGetOwnPropertyNames(exports);
517522
for(leti=0;i<keys.length;i++){
518523
constkey=keys[i];
519-
if(key[key.length-1]==='*'&&
524+
constpatternIndex=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+
constpatternTrailer=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
}elseif(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
consttarget=exports[bestMatch];
534-
constpattern=bestMatch[bestMatch.length-1]==='*';
535-
constsubpath=StringPrototypeSubstr(packageSubpath,bestMatch.length-
536-
(pattern ? 1 : 0));
537-
constresolved=resolvePackageTarget(packageJSONUrl,target,subpath,
538-
bestMatch,base,pattern,false,
539-
conditions);
548+
constpattern=StringPrototypeIncludes(bestMatch,'*');
549+
constresolved=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+
functionpatternKeyCompare(a,b){
561+
constaPatternIndex=StringPrototypeIndexOf(a,'*');
562+
constbPatternIndex=StringPrototypeIndexOf(b,'*');
563+
constbaseLenA=aPatternIndex===-1 ? a.length : aPatternIndex+1;
564+
constbaseLenB=bPatternIndex===-1 ? b.length : bPatternIndex+1;
565+
if(baseLenA>baseLenB)return-1;
566+
if(baseLenB>baseLenA)return1;
567+
if(aPatternIndex===-1)return1;
568+
if(bPatternIndex===-1)return-1;
569+
if(a.length>b.length)return-1;
570+
if(b.length>a.length)return1;
571+
return0;
572+
}
573+
548574
functionpackageImportsResolve(name,base,conditions){
549575
if(name==='#'||StringPrototypeStartsWith(name,'#/')){
550576
constreason='is not a valid internal imports specifier name';

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

Lines 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
constinvalidExports=newMap([
@@ -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){

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

Lines 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)