11
11
12
12
import type {
13
13
ExportMap ,
14
+ ExportMapWithFallbacks ,
14
15
ExportsField ,
15
16
FileResolution ,
16
17
ResolutionContext ,
@@ -54,15 +55,15 @@ export function resolvePackageTargetFromExports(
54
55
exportsField : ExportsField ,
55
56
platform : string | null ,
56
57
) : FileResolution {
57
- const raiseConfigError = ( reason : string ) => {
58
- throw new InvalidPackageConfigurationError ( {
58
+ const createConfigError = ( reason : string ) => {
59
+ return new InvalidPackageConfigurationError ( {
59
60
reason,
60
61
packagePath,
61
62
} ) ;
62
63
} ;
63
64
64
65
const subpath = getExportsSubpath ( packagePath , modulePath ) ;
65
- const exportMap = normalizeExportsField ( exportsField , raiseConfigError ) ;
66
+ const exportMap = normalizeExportsField ( exportsField , createConfigError ) ;
66
67
67
68
if ( ! isSubpathDefinedInExports ( exportMap , subpath ) ) {
68
69
throw new PackagePathNotExportedError (
@@ -76,14 +77,14 @@ export function resolvePackageTargetFromExports(
76
77
subpath ,
77
78
exportMap ,
78
79
platform ,
79
- raiseConfigError ,
80
+ createConfigError ,
80
81
) ;
81
82
82
83
if ( target != null ) {
83
84
const invalidSegmentInTarget = findInvalidPathSegment ( target . slice ( 2 ) ) ;
84
85
85
86
if ( invalidSegmentInTarget != null ) {
86
- raiseConfigError (
87
+ throw createConfigError (
87
88
`The target for "${ subpath } " defined in "exports" is "${ target } ", ` +
88
89
'however this value is an invalid subpath or subpath pattern ' +
89
90
`because it includes "${ invalidSegmentInTarget } ".` ,
@@ -117,7 +118,7 @@ export function resolvePackageTargetFromExports(
117
118
return { type : 'sourceFile' , filePath} ;
118
119
}
119
120
120
- raiseConfigError (
121
+ throw createConfigError (
121
122
`The resolution for "${ modulePath } " defined in "exports" is ${ filePath } , ` +
122
123
'however this file does not exist.' ,
123
124
) ;
@@ -142,45 +143,86 @@ function getExportsSubpath(packagePath: string, modulePath: string): string {
142
143
143
144
/**
144
145
* Normalise an "exports"-like field by parsing string shorthand and conditions
145
- * shorthand at root.
146
+ * shorthand at root, and flattening any legacy Node.js <13.7 array values .
146
147
*
147
148
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#exports-sugar.
148
149
*/
149
150
function normalizeExportsField (
150
151
exportsField : ExportsField ,
151
- raiseConfigError : ( reason : string ) = > void ,
152
+ createConfigError : ( reason : string ) = > Error ,
152
153
) : ExportMap {
153
- if ( typeof exportsField === 'string' ) {
154
- return { '.' : exportsField } ;
155
- }
154
+ let rootValue ;
156
155
157
156
if ( Array . isArray ( exportsField ) ) {
158
- return exportsField . reduce (
159
- ( result , subpath ) => ( {
160
- ...result ,
161
- [ subpath ] : subpath ,
162
- } ) ,
163
- { } ,
157
+ // If an array of strings, expand as subpath mapping (legacy root shorthand)
158
+ if ( exportsField . every ( value => typeof value === 'string' ) ) {
159
+ // $FlowIssue[incompatible-call] exportsField is refined to `string[]`
160
+ return exportsField . reduce (
161
+ ( result : ExportMap , subpath : string ) => ( {
162
+ ...result ,
163
+ [ subpath ] : subpath ,
164
+ } ) ,
165
+ { } ,
166
+ ) ;
167
+ }
168
+
169
+ // Otherwise, should be a condition map and fallback string (Node.js <13.7)
170
+ rootValue = exportsField [ 0 ] ;
171
+ } else {
172
+ rootValue = exportsField ;
173
+ }
174
+
175
+ if ( rootValue == null || Array . isArray ( rootValue ) ) {
176
+ throw createConfigError (
177
+ 'Could not parse non-standard array value at root of "exports" field.' ,
164
178
) ;
165
179
}
166
180
167
- const firstLevelKeys = Object . keys ( exportsField ) ;
181
+ if ( typeof rootValue === 'string' ) {
182
+ return { '. ': rootValue } ;
183
+ }
184
+
185
+ const firstLevelKeys = Object . keys ( rootValue ) ;
168
186
const subpathKeys = firstLevelKeys . filter ( subpathOrCondition =>
169
187
subpathOrCondition . startsWith ( '.' ) ,
170
188
) ;
171
189
172
190
if ( subpathKeys . length === firstLevelKeys . length ) {
173
- return exportsField ;
191
+ return flattenLegacySubpathValues ( rootValue , createConfigError ) ;
174
192
}
175
193
176
194
if ( subpathKeys . length !== 0 ) {
177
- raiseConfigError (
195
+ throw createConfigError (
178
196
'The "exports" field cannot have keys which are both subpaths and ' +
179
197
'condition names at the same level.' ,
180
198
) ;
181
199
}
182
200
183
- return { '. ': exportsField } ;
201
+ return { '. ': flattenLegacySubpathValues ( rootValue , createConfigError ) } ;
202
+ }
203
+
204
+ /**
205
+ * Flatten legacy Node.js <13.7 array subpath values in an exports mapping.
206
+ */
207
+ function flattenLegacySubpathValues (
208
+ exportMap : ExportMap | ExportMapWithFallbacks ,
209
+ createConfigError : ( reason : string ) = > Error ,
210
+ ) : ExportMap {
211
+ return Object . keys ( exportMap ) . reduce ( ( result : ExportMap , subpath : string ) => {
212
+ const value = exportMap [ subpath ] ;
213
+
214
+ // We do not support empty or nested arrays (non-standard)
215
+ if ( Array . isArray ( value ) && ( ! value . length || Array . isArray ( value [ 0 ] ) ) ) {
216
+ throw createConfigError (
217
+ 'Could not parse non-standard array value in "exports" field.' ,
218
+ ) ;
219
+ }
220
+
221
+ return {
222
+ ...result ,
223
+ [ subpath ] : Array . isArray ( value ) ? value [ 0 ] : value ,
224
+ } ;
225
+ } , { } ) ;
184
226
}
185
227
186
228
/**
@@ -224,7 +266,7 @@ function matchSubpathFromExports(
224
266
subpath : string ,
225
267
exportMap : ExportMap ,
226
268
platform : string | null ,
227
- raiseConfigError : ( reason : string ) = > void ,
269
+ createConfigError : ( reason : string ) = > Error ,
228
270
) : $ReadOnly < {
229
271
target : string | null ,
230
272
patternMatch : string | null ,
@@ -240,7 +282,7 @@ function matchSubpathFromExports(
240
282
const exportMapAfterConditions = reduceExportMap (
241
283
exportMap ,
242
284
conditionNames ,
243
- raiseConfigError ,
285
+ createConfigError ,
244
286
) ;
245
287
246
288
let target = exportMapAfterConditions [ subpath ] ;
@@ -283,7 +325,7 @@ type FlattenedExportMap = $ReadOnly<{[subpath: string]: string | null}>;
283
325
function reduceExportMap (
284
326
exportMap : ExportMap ,
285
327
conditionNames : $ReadOnlySet < string > ,
286
- raiseConfigError : ( reason : string ) = > void ,
328
+ createConfigError : ( reason : string ) = > Error ,
287
329
) : FlattenedExportMap {
288
330
const result : { [ subpath : string ] : string | null } = { } ;
289
331
@@ -306,7 +348,7 @@ function reduceExportMap(
306
348
) ;
307
349
308
350
if ( invalidValues . length ) {
309
- raiseConfigError (
351
+ throw createConfigError (
310
352
'One or more mappings for subpaths defined in "exports" are invalid. ' +
311
353
'All values must begin with "./".' ,
312
354
) ;
@@ -325,7 +367,7 @@ function reduceExportMap(
325
367
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#conditional-exports.
326
368
*/
327
369
function reduceConditionalExport (
328
- subpathValue : ExportMap | string | null ,
370
+ subpathValue : $Values < ExportMap > ,
329
371
conditionNames : $ReadOnlySet < string > ,
330
372
) : string | null | 'no-match' {
331
373
let reducedValue = subpathValue ;
0 commit comments