2121
2222'use strict' ;
2323
24- const { JSON , Object, Reflect } = primordials ;
24+ const {
25+ JSON ,
26+ Object,
27+ Reflect,
28+ SafeMap,
29+ StringPrototype,
30+ } = primordials ;
2531
2632const { NativeModule } = require ( 'internal/bootstrap/loaders' ) ;
2733const { pathToFileURL, fileURLToPath, URL } = require ( 'internal/url' ) ;
@@ -54,10 +60,12 @@ const { compileFunction } = internalBinding('contextify');
5460const {
5561 ERR_INVALID_ARG_VALUE ,
5662 ERR_INVALID_OPT_VALUE ,
63+ ERR_PATH_NOT_EXPORTED ,
5764 ERR_REQUIRE_ESM
5865} = require ( 'internal/errors' ) . codes ;
5966const { validateString } = require ( 'internal/validators' ) ;
6067const pendingDeprecation = getOptionValue ( '--pending-deprecation' ) ;
68+ const experimentalExports = getOptionValue ( '--experimental-exports' ) ;
6169
6270module . exports = Module ;
6371
@@ -183,12 +191,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
183191
184192// Check if the directory is a package.json dir.
185193const packageMainCache = Object . create ( null ) ;
194+ // Explicit exports from package.json files
195+ const packageExportsCache = new SafeMap ( ) ;
186196
187- function readPackage ( requestPath ) {
188- const entry = packageMainCache [ requestPath ] ;
189- if ( entry )
190- return entry ;
191-
197+ function readPackageRaw ( requestPath ) {
192198 const jsonPath = path . resolve ( requestPath , 'package.json' ) ;
193199 const json = internalModuleReadJSON ( path . toNamespacedPath ( jsonPath ) ) ;
194200
@@ -202,14 +208,44 @@ function readPackage(requestPath) {
202208 }
203209
204210 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 ;
206217 } catch ( e ) {
207218 e . path = jsonPath ;
208219 e . message = 'Error parsing ' + jsonPath + ': ' + e . message ;
209220 throw e ;
210221 }
211222}
212223
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+
213249function tryPackage ( requestPath , exts , isMain , originalPath ) {
214250 const pkg = readPackage ( requestPath ) ;
215251
@@ -298,8 +334,59 @@ function findLongestRegisteredExtension(filename) {
298334 return '.js' ;
299335}
300336
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+
301387Module . _findPath = function ( request , paths , isMain ) {
302- if ( path . isAbsolute ( request ) ) {
388+ const absoluteRequest = path . isAbsolute ( request ) ;
389+ if ( absoluteRequest ) {
303390 paths = [ '' ] ;
304391 } else if ( ! paths || paths . length === 0 ) {
305392 return false ;
@@ -323,7 +410,7 @@ Module._findPath = function(request, paths, isMain) {
323410 // Don't search further if path doesn't exist
324411 const curPath = paths [ i ] ;
325412 if ( curPath && stat ( curPath ) < 1 ) continue ;
326- var basePath = path . resolve ( curPath , request ) ;
413+ var basePath = resolveExports ( curPath , request , absoluteRequest ) ;
327414 var filename ;
328415
329416 var rc = stat ( basePath ) ;
0 commit comments