@@ -60,7 +60,6 @@ mod tsconfig;
60
60
#[ cfg( test) ]
61
61
mod tests;
62
62
63
- use rustc_hash:: FxHashSet ;
64
63
use std:: {
65
64
borrow:: Cow ,
66
65
cmp:: Ordering ,
@@ -69,6 +68,9 @@ use std::{
69
68
path:: { Component , Path , PathBuf } ,
70
69
sync:: Arc ,
71
70
} ;
71
+
72
+ use rustc_hash:: FxHashSet ;
73
+ use serde_json:: Value as JSONValue ;
72
74
use typescript_tsconfig_json:: ExtendsField ;
73
75
74
76
pub use crate :: {
@@ -86,11 +88,11 @@ use crate::{
86
88
cache:: { Cache , CachedPath } ,
87
89
context:: ResolveContext as Ctx ,
88
90
file_system:: FileSystemOs ,
91
+ package_json:: ImportExportMap ,
89
92
path:: { PathUtil , SLASH_START } ,
90
93
specifier:: Specifier ,
91
94
tsconfig:: { ProjectReference , TsConfig } ,
92
95
} ;
93
- use nodejs_package_json:: { ImportExportField , ImportExportKey , ImportExportMap } ;
94
96
95
97
type ResolveResult = Result < Option < CachedPath > , ResolveError > ;
96
98
@@ -749,16 +751,13 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
749
751
} ;
750
752
// 3. Parse DIR/NAME/package.json, and look for "exports" field.
751
753
// 4. If "exports" is null or undefined, return.
752
- if package_json. exports . is_empty ( ) {
753
- return Ok ( None ) ;
754
- } ;
755
754
// 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
756
755
// `package.json` "exports", ["node", "require"]) defined in the ESM resolver.
757
756
// Note: The subpath is not prepended with a dot on purpose
758
- for exports in & package_json. exports {
757
+ for exports in package_json. exports_fields ( & self . options . exports_fields ) {
759
758
if let Some ( path) = self . package_exports_resolve (
760
759
cached_path. path ( ) ,
761
- subpath,
760
+ & format ! ( ".{ subpath}" ) ,
762
761
exports,
763
762
& self . options . condition_names ,
764
763
ctx,
@@ -784,30 +783,28 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
784
783
return Ok ( None ) ;
785
784
} ;
786
785
// 3. If the SCOPE/package.json "exports" is null or undefined, return.
787
- if !package_json. exports . is_empty ( ) {
788
- // 4. If the SCOPE/package.json "name" is not the first segment of X, return.
789
- if let Some ( subpath) = package_json
790
- . name
791
- . as_ref ( )
792
- . and_then ( |package_name| Self :: strip_package_name ( specifier, package_name) )
793
- {
794
- // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
795
- // "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
796
- // defined in the ESM resolver.
797
- let package_url = package_json. directory ( ) ;
798
- // Note: The subpath is not prepended with a dot on purpose
799
- // because `package_exports_resolve` matches subpath without the leading dot.
800
- for exports in & package_json. exports {
801
- if let Some ( cached_path) = self . package_exports_resolve (
802
- package_url,
803
- subpath,
804
- exports,
805
- & self . options . condition_names ,
806
- ctx,
807
- ) ? {
808
- // 6. RESOLVE_ESM_MATCH(MATCH)
809
- return self . resolve_esm_match ( specifier, & cached_path, & package_json, ctx) ;
810
- }
786
+ // 4. If the SCOPE/package.json "name" is not the first segment of X, return.
787
+ if let Some ( subpath) = package_json
788
+ . name
789
+ . as_ref ( )
790
+ . and_then ( |package_name| Self :: strip_package_name ( specifier, package_name) )
791
+ {
792
+ // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
793
+ // "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
794
+ // defined in the ESM resolver.
795
+ let package_url = package_json. directory ( ) ;
796
+ // Note: The subpath is not prepended with a dot on purpose
797
+ // because `package_exports_resolve` matches subpath without the leading dot.
798
+ for exports in package_json. exports_fields ( & self . options . exports_fields ) {
799
+ if let Some ( cached_path) = self . package_exports_resolve (
800
+ package_url,
801
+ & format ! ( ".{subpath}" ) ,
802
+ exports,
803
+ & self . options . condition_names ,
804
+ ctx,
805
+ ) ? {
806
+ // 6. RESOLVE_ESM_MATCH(MATCH)
807
+ return self . resolve_esm_match ( specifier, & cached_path, & package_json, ctx) ;
811
808
}
812
809
}
813
810
}
@@ -1143,18 +1140,16 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1143
1140
cached_path. package_json ( & self . cache . fs , & self . options , ctx) ?
1144
1141
{
1145
1142
// 5. If pjson is not null and pjson.exports is not null or undefined, then
1146
- if !package_json. exports . is_empty ( ) {
1147
- // 1. Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
1148
- for exports in & package_json. exports {
1149
- if let Some ( path) = self . package_exports_resolve (
1150
- cached_path. path ( ) ,
1151
- subpath,
1152
- exports,
1153
- & self . options . condition_names ,
1154
- ctx,
1155
- ) ? {
1156
- return Ok ( Some ( path) ) ;
1157
- }
1143
+ // 1. Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
1144
+ for exports in package_json. exports_fields ( & self . options . exports_fields ) {
1145
+ if let Some ( path) = self . package_exports_resolve (
1146
+ cached_path. path ( ) ,
1147
+ & format ! ( ".{subpath}" ) ,
1148
+ exports,
1149
+ & self . options . condition_names ,
1150
+ ctx,
1151
+ ) ? {
1152
+ return Ok ( Some ( path) ) ;
1158
1153
}
1159
1154
}
1160
1155
// 6. Otherwise, if packageSubpath is equal to ".", then
@@ -1187,18 +1182,17 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1187
1182
& self ,
1188
1183
package_url : & Path ,
1189
1184
subpath : & str ,
1190
- exports : & ImportExportField ,
1185
+ exports : & JSONValue ,
1191
1186
conditions : & [ String ] ,
1192
1187
ctx : & mut Ctx ,
1193
1188
) -> ResolveResult {
1194
1189
// 1. If exports is an Object with both a key starting with "." and a key not starting with ".", throw an Invalid Package Configuration error.
1195
- if let ImportExportField :: Map ( map) = exports {
1190
+ if let JSONValue :: Object ( map) = exports {
1196
1191
let mut has_dot = false ;
1197
1192
let mut without_dot = false ;
1198
1193
for key in map. keys ( ) {
1199
- has_dot =
1200
- has_dot || matches ! ( key, ImportExportKey :: Main | ImportExportKey :: Pattern ( _) ) ;
1201
- without_dot = without_dot || matches ! ( key, ImportExportKey :: CustomCondition ( _) ) ;
1194
+ has_dot = has_dot || key. starts_with ( |s| s == '.' || s == '#' ) ;
1195
+ without_dot = without_dot || !key. starts_with ( |s| s == '.' || s == '#' ) ;
1202
1196
if has_dot && without_dot {
1203
1197
return Err ( ResolveError :: InvalidPackageConfig (
1204
1198
package_url. join ( "package.json" ) ,
@@ -1208,32 +1202,31 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1208
1202
}
1209
1203
// 2. If subpath is equal to ".", then
1210
1204
// Note: subpath is not prepended with a dot when passed in.
1211
- if subpath. is_empty ( ) {
1205
+ if subpath == "." {
1212
1206
// enhanced-resolve appends query and fragment when resolving exports field
1213
1207
// https://github.com/webpack/enhanced-resolve/blob/a998c7d218b7a9ec2461fc4fddd1ad5dd7687485/lib/ExportsFieldPlugin.js#L57-L62
1214
1208
// This is only need when querying the main export, otherwise ctx is passed through.
1215
1209
if ctx. query . is_some ( ) || ctx. fragment . is_some ( ) {
1216
1210
let query = ctx. query . clone ( ) . unwrap_or_default ( ) ;
1217
1211
let fragment = ctx. fragment . clone ( ) . unwrap_or_default ( ) ;
1218
1212
return Err ( ResolveError :: PackagePathNotExported (
1219
- format ! ( "./{subpath }{query}{fragment}" ) ,
1213
+ format ! ( "./{}{query}{fragment}" , subpath . trim_start_matches ( '.' ) ) ,
1220
1214
package_url. join ( "package.json" ) ,
1221
1215
) ) ;
1222
1216
}
1223
1217
// 1. Let mainExport be undefined.
1224
1218
let main_export = match exports {
1225
- ImportExportField :: None => None ,
1226
1219
// 2. If exports is a String or Array, or an Object containing no keys starting with ".", then
1227
- ImportExportField :: String ( _) | ImportExportField :: Array ( _) => {
1220
+ JSONValue :: String ( _) | JSONValue :: Array ( _) => {
1228
1221
// 1. Set mainExport to exports.
1229
1222
Some ( exports)
1230
1223
}
1231
1224
// 3. Otherwise if exports is an Object containing a "." property, then
1232
- ImportExportField :: Map ( map) => {
1225
+ JSONValue :: Object ( map) => {
1233
1226
// 1. Set mainExport to exports["."].
1234
- map. get ( & ImportExportKey :: Main ) . map_or_else (
1227
+ map. get ( "." ) . map_or_else (
1235
1228
|| {
1236
- if map. keys ( ) . any ( |key| matches ! ( key, ImportExportKey :: Pattern ( _ ) ) ) {
1229
+ if map. keys ( ) . any ( |key| key. starts_with ( "./" ) || key . starts_with ( '#' ) ) {
1237
1230
None
1238
1231
} else {
1239
1232
Some ( exports)
@@ -1242,6 +1235,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1242
1235
Some ,
1243
1236
)
1244
1237
}
1238
+ _ => None ,
1245
1239
} ;
1246
1240
// 4. If mainExport is not undefined, then
1247
1241
if let Some ( main_export) = main_export {
@@ -1262,7 +1256,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1262
1256
}
1263
1257
}
1264
1258
// 3. Otherwise, if exports is an Object and all keys of exports start with ".", then
1265
- if let ImportExportField :: Map ( exports) = exports {
1259
+ if let JSONValue :: Object ( exports) = exports {
1266
1260
// 1. Let matchKey be the string "./" concatenated with subpath.
1267
1261
// Note: `package_imports_exports_resolve` does not require the leading dot.
1268
1262
let match_key = & subpath;
@@ -1281,7 +1275,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1281
1275
}
1282
1276
// 4. Throw a Package Path Not Exported error.
1283
1277
Err ( ResolveError :: PackagePathNotExported (
1284
- format ! ( ".{ subpath}" ) ,
1278
+ subpath. to_string ( ) ,
1285
1279
package_url. join ( "package.json" ) ,
1286
1280
) )
1287
1281
}
@@ -1346,7 +1340,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1346
1340
// 1. If matchKey is a key of matchObj and does not contain "*", then
1347
1341
if !match_key. contains ( '*' ) {
1348
1342
// 1. Let target be the value of matchObj[matchKey].
1349
- if let Some ( target) = match_obj. get ( & ImportExportKey :: Pattern ( match_key. to_string ( ) ) ) {
1343
+ if let Some ( target) = match_obj. get ( match_key) {
1350
1344
// 2. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
1351
1345
return self . package_target_resolve (
1352
1346
package_url,
@@ -1366,7 +1360,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1366
1360
// 2. Let expansionKeys be the list of keys of matchObj containing only a single "*", sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
1367
1361
// 3. For each key expansionKey in expansionKeys, do
1368
1362
for ( expansion_key, target) in match_obj {
1369
- if let ImportExportKey :: Pattern ( expansion_key) = expansion_key {
1363
+ if expansion_key. starts_with ( "./" ) || expansion_key. starts_with ( '#' ) {
1370
1364
// 1. Let patternBase be the substring of expansionKey up to but excluding the first "*" character.
1371
1365
if let Some ( ( pattern_base, pattern_trailer) ) = expansion_key. split_once ( '*' ) {
1372
1366
// 2. If matchKey starts with but is not equal to patternBase, then
@@ -1419,7 +1413,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1419
1413
& self ,
1420
1414
package_url : & Path ,
1421
1415
target_key : & str ,
1422
- target : & ImportExportField ,
1416
+ target : & JSONValue ,
1423
1417
pattern_match : Option < & str > ,
1424
1418
is_imports : bool ,
1425
1419
conditions : & [ String ] ,
@@ -1452,9 +1446,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1452
1446
}
1453
1447
1454
1448
match target {
1455
- ImportExportField :: None => { }
1456
1449
// 1. If target is a String, then
1457
- ImportExportField :: String ( target) => {
1450
+ JSONValue :: String ( target) => {
1458
1451
// 1. If target does not start with "./", then
1459
1452
if !target. starts_with ( "./" ) {
1460
1453
// 1. If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then
@@ -1495,24 +1488,22 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1495
1488
return Ok ( Some ( value) ) ;
1496
1489
}
1497
1490
// 2. Otherwise, if target is a non-null Object, then
1498
- ImportExportField :: Map ( target) => {
1491
+ JSONValue :: Object ( target) => {
1499
1492
// 1. If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
1500
1493
// 2. For each property p of target, in object insertion order as,
1501
1494
for ( i, ( key, target_value) ) in target. iter ( ) . enumerate ( ) {
1502
1495
// https://nodejs.org/api/packages.html#conditional-exports
1503
1496
// "default" - the generic fallback that always matches. Can be a CommonJS or ES module file. This condition should always come last.
1504
1497
// Note: node.js does not throw this but enhanced-resolve does.
1505
- let is_default = matches ! ( key, ImportExportKey :: CustomCondition ( condition ) if condition == "default" ) ;
1498
+ let is_default = key == "default" ;
1506
1499
if i < target. len ( ) - 1 && is_default {
1507
1500
return Err ( ResolveError :: InvalidPackageConfigDefault (
1508
1501
package_url. join ( "package.json" ) ,
1509
1502
) ) ;
1510
1503
}
1511
1504
1512
1505
// 1. If p equals "default" or conditions contains an entry for p, then
1513
- if is_default
1514
- || matches ! ( key, ImportExportKey :: CustomCondition ( condition) if conditions. contains( condition) )
1515
- {
1506
+ if is_default || conditions. contains ( key) {
1516
1507
// 1. Let targetValue be the value of the p property in target.
1517
1508
// 2. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
1518
1509
let resolved = self . package_target_resolve (
@@ -1535,12 +1526,12 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1535
1526
return Ok ( None ) ;
1536
1527
}
1537
1528
// 3. Otherwise, if target is an Array, then
1538
- ImportExportField :: Array ( targets) => {
1529
+ JSONValue :: Array ( targets) => {
1539
1530
// 1. If _target.length is zero, return null.
1540
1531
if targets. is_empty ( ) {
1541
1532
// Note: return PackagePathNotExported has the same effect as return because there are no matches.
1542
1533
return Err ( ResolveError :: PackagePathNotExported (
1543
- format ! ( ".{}" , pattern_match. unwrap_or( "." ) ) ,
1534
+ pattern_match. unwrap_or ( "." ) . to_string ( ) ,
1544
1535
package_url. join ( "package.json" ) ,
1545
1536
) ) ;
1546
1537
}
@@ -1570,6 +1561,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
1570
1561
// 3. Return or throw the last fallback resolution null return or error.
1571
1562
// Note: see `resolved.is_err() && i == targets.len()`
1572
1563
}
1564
+ _ => { }
1573
1565
}
1574
1566
// 4. Otherwise, if target is null, return null.
1575
1567
Ok ( None )
0 commit comments