21
21
22
22
'use strict' ;
23
23
24
- const { JSON , Object, Reflect } = primordials ;
24
+ const {
25
+ JSON ,
26
+ Object,
27
+ Reflect,
28
+ SafeMap,
29
+ StringPrototype,
30
+ } = primordials ;
25
31
26
32
const { NativeModule } = require ( 'internal/bootstrap/loaders' ) ;
27
33
const { pathToFileURL, fileURLToPath, URL } = require ( 'internal/url' ) ;
@@ -54,10 +60,12 @@ const { compileFunction } = internalBinding('contextify');
54
60
const {
55
61
ERR_INVALID_ARG_VALUE ,
56
62
ERR_INVALID_OPT_VALUE ,
63
+ ERR_PATH_NOT_EXPORTED ,
57
64
ERR_REQUIRE_ESM
58
65
} = require ( 'internal/errors' ) . codes ;
59
66
const { validateString } = require ( 'internal/validators' ) ;
60
67
const pendingDeprecation = getOptionValue ( '--pending-deprecation' ) ;
68
+ const experimentalExports = getOptionValue ( '--experimental-exports' ) ;
61
69
62
70
module . exports = Module ;
63
71
@@ -183,12 +191,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
183
191
184
192
// Check if the directory is a package.json dir.
185
193
const packageMainCache = Object . create ( null ) ;
194
+ // Explicit exports from package.json files
195
+ const packageExportsCache = new SafeMap ( ) ;
186
196
187
- function readPackage ( requestPath ) {
188
- const entry = packageMainCache [ requestPath ] ;
189
- if ( entry )
190
- return entry ;
191
-
197
+ function readPackageRaw ( requestPath ) {
192
198
const jsonPath = path . resolve ( requestPath , 'package.json' ) ;
193
199
const json = internalModuleReadJSON ( path . toNamespacedPath ( jsonPath ) ) ;
194
200
@@ -202,14 +208,44 @@ function readPackage(requestPath) {
202
208
}
203
209
204
210
try {
205
- return packageMainCache [ requestPath ] = JSON . parse ( json ) . main ;
211
+ const parsed = JSON . parse ( json ) ;
212
+ packageMainCache [ requestPath ] = parsed . main ;
213
+ if ( experimentalExports ) {
214
+ packageExportsCache . set ( requestPath , parsed . exports ) ;
215
+ }
216
+ return parsed ;
206
217
} catch ( e ) {
207
218
e . path = jsonPath ;
208
219
e . message = 'Error parsing ' + jsonPath + ': ' + e . message ;
209
220
throw e ;
210
221
}
211
222
}
212
223
224
+ function readPackage ( requestPath ) {
225
+ const entry = packageMainCache [ requestPath ] ;
226
+ if ( entry )
227
+ return entry ;
228
+
229
+ const pkg = readPackageRaw ( requestPath ) ;
230
+ if ( pkg === false ) return false ;
231
+
232
+ return pkg . main ;
233
+ }
234
+
235
+ function readExports ( requestPath ) {
236
+ if ( packageExportsCache . has ( requestPath ) ) {
237
+ return packageExportsCache . get ( requestPath ) ;
238
+ }
239
+
240
+ const pkg = readPackageRaw ( requestPath ) ;
241
+ if ( ! pkg ) {
242
+ packageExportsCache . set ( requestPath , null ) ;
243
+ return null ;
244
+ }
245
+
246
+ return pkg . exports ;
247
+ }
248
+
213
249
function tryPackage ( requestPath , exts , isMain , originalPath ) {
214
250
const pkg = readPackage ( requestPath ) ;
215
251
@@ -298,8 +334,59 @@ function findLongestRegisteredExtension(filename) {
298
334
return '.js' ;
299
335
}
300
336
337
+ // This only applies to requests of a specific form:
338
+ // 1. name/.*
339
+ // 2. @scope/name/.*
340
+ const EXPORTS_PATTERN = / ^ ( (?: @ [ ^ . / @ \\ ] [ ^ / @ \\ ] * \/ ) ? [ ^ @ . / \\ ] [ ^ / \\ ] * ) ( \/ .* ) $ / ;
341
+ function resolveExports ( nmPath , request , absoluteRequest ) {
342
+ // The implementation's behavior is meant to mirror resolution in ESM.
343
+ if ( experimentalExports && ! absoluteRequest ) {
344
+ const [ , name , expansion ] =
345
+ StringPrototype . match ( request , EXPORTS_PATTERN ) || [ ] ;
346
+ if ( ! name ) {
347
+ return path . resolve ( nmPath , request ) ;
348
+ }
349
+
350
+ const basePath = path . resolve ( nmPath , name ) ;
351
+ const pkgExports = readExports ( basePath ) ;
352
+
353
+ if ( pkgExports != null ) {
354
+ const mappingKey = `.${ expansion } ` ;
355
+ const mapping = pkgExports [ mappingKey ] ;
356
+ if ( typeof mapping === 'string' ) {
357
+ return fileURLToPath ( new URL ( mapping , `${ pathToFileURL ( basePath ) } /` ) ) ;
358
+ }
359
+
360
+ let dirMatch = '' ;
361
+ for ( const [ candidateKey , candidateValue ] of Object . entries ( pkgExports ) ) {
362
+ if ( candidateKey [ candidateKey . length - 1 ] !== '/' ) continue ;
363
+ if ( candidateValue [ candidateValue . length - 1 ] !== '/' ) continue ;
364
+ if ( candidateKey . length > dirMatch . length &&
365
+ StringPrototype . startsWith ( mappingKey , candidateKey ) ) {
366
+ dirMatch = candidateKey ;
367
+ }
368
+ }
369
+
370
+ if ( dirMatch !== '' ) {
371
+ const dirMapping = pkgExports [ dirMatch ] ;
372
+ const remainder = StringPrototype . slice ( mappingKey , dirMatch . length ) ;
373
+ const expectedPrefix =
374
+ new URL ( dirMapping , `${ pathToFileURL ( basePath ) } /` ) ;
375
+ const resolved = new URL ( remainder , expectedPrefix ) . href ;
376
+ if ( StringPrototype . startsWith ( resolved , expectedPrefix . href ) ) {
377
+ return fileURLToPath ( resolved ) ;
378
+ }
379
+ }
380
+ throw new ERR_PATH_NOT_EXPORTED ( basePath , mappingKey ) ;
381
+ }
382
+ }
383
+
384
+ return path . resolve ( nmPath , request ) ;
385
+ }
386
+
301
387
Module . _findPath = function ( request , paths , isMain ) {
302
- if ( path . isAbsolute ( request ) ) {
388
+ const absoluteRequest = path . isAbsolute ( request ) ;
389
+ if ( absoluteRequest ) {
303
390
paths = [ '' ] ;
304
391
} else if ( ! paths || paths . length === 0 ) {
305
392
return false ;
@@ -323,7 +410,7 @@ Module._findPath = function(request, paths, isMain) {
323
410
// Don't search further if path doesn't exist
324
411
const curPath = paths [ i ] ;
325
412
if ( curPath && stat ( curPath ) < 1 ) continue ;
326
- var basePath = path . resolve ( curPath , request ) ;
413
+ var basePath = resolveExports ( curPath , request , absoluteRequest ) ;
327
414
var filename ;
328
415
329
416
var rc = stat ( basePath ) ;
0 commit comments