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' ) ;
@@ -53,10 +59,12 @@ const { compileFunction } = internalBinding('contextify');
5359const {
5460 ERR_INVALID_ARG_VALUE ,
5561 ERR_INVALID_OPT_VALUE ,
62+ ERR_PATH_NOT_EXPORTED ,
5663 ERR_REQUIRE_ESM
5764} = require ( 'internal/errors' ) . codes ;
5865const { validateString } = require ( 'internal/validators' ) ;
5966const pendingDeprecation = getOptionValue ( '--pending-deprecation' ) ;
67+ const experimentalExports = getOptionValue ( '--experimental-exports' ) ;
6068
6169module . exports = { wrapSafe, Module } ;
6270
@@ -182,12 +190,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
182190
183191// Check if the directory is a package.json dir.
184192const packageMainCache = Object . create ( null ) ;
193+ // Explicit exports from package.json files
194+ const packageExportsCache = new SafeMap ( ) ;
185195
186- function readPackage ( requestPath ) {
187- const entry = packageMainCache [ requestPath ] ;
188- if ( entry )
189- return entry ;
190-
196+ function readPackageRaw ( requestPath ) {
191197 const jsonPath = path . resolve ( requestPath , 'package.json' ) ;
192198 const json = internalModuleReadJSON ( path . toNamespacedPath ( jsonPath ) ) ;
193199
@@ -201,14 +207,44 @@ function readPackage(requestPath) {
201207 }
202208
203209 try {
204- return packageMainCache [ requestPath ] = JSON . parse ( json ) . main ;
210+ const parsed = JSON . parse ( json ) ;
211+ packageMainCache [ requestPath ] = parsed . main ;
212+ if ( experimentalExports ) {
213+ packageExportsCache . set ( requestPath , parsed . exports ) ;
214+ }
215+ return parsed ;
205216 } catch ( e ) {
206217 e . path = jsonPath ;
207218 e . message = 'Error parsing ' + jsonPath + ': ' + e . message ;
208219 throw e ;
209220 }
210221}
211222
223+ function readPackage ( requestPath ) {
224+ const entry = packageMainCache [ requestPath ] ;
225+ if ( entry )
226+ return entry ;
227+
228+ const pkg = readPackageRaw ( requestPath ) ;
229+ if ( pkg === false ) return false ;
230+
231+ return pkg . main ;
232+ }
233+
234+ function readExports ( requestPath ) {
235+ if ( packageExportsCache . has ( requestPath ) ) {
236+ return packageExportsCache . get ( requestPath ) ;
237+ }
238+
239+ const pkg = readPackageRaw ( requestPath ) ;
240+ if ( ! pkg ) {
241+ packageExportsCache . set ( requestPath , null ) ;
242+ return null ;
243+ }
244+
245+ return pkg . exports ;
246+ }
247+
212248function tryPackage ( requestPath , exts , isMain , originalPath ) {
213249 const pkg = readPackage ( requestPath ) ;
214250
@@ -297,8 +333,59 @@ function findLongestRegisteredExtension(filename) {
297333 return '.js' ;
298334}
299335
336+ // This only applies to requests of a specific form:
337+ // 1. name/.*
338+ // 2. @scope/name/.*
339+ const EXPORTS_PATTERN = / ^ ( (?: @ [ ^ . / @ \\ ] [ ^ / @ \\ ] * \/ ) ? [ ^ @ . / \\ ] [ ^ / \\ ] * ) ( \/ .* ) $ / ;
340+ function resolveExports ( nmPath , request , absoluteRequest ) {
341+ // The implementation's behavior is meant to mirror resolution in ESM.
342+ if ( experimentalExports && ! absoluteRequest ) {
343+ const [ , name , expansion ] =
344+ StringPrototype . match ( request , EXPORTS_PATTERN ) || [ ] ;
345+ if ( ! name ) {
346+ return path . resolve ( nmPath , request ) ;
347+ }
348+
349+ const basePath = path . resolve ( nmPath , name ) ;
350+ const pkgExports = readExports ( basePath ) ;
351+
352+ if ( pkgExports != null ) {
353+ const mappingKey = `.${ expansion } ` ;
354+ const mapping = pkgExports [ mappingKey ] ;
355+ if ( typeof mapping === 'string' ) {
356+ return fileURLToPath ( new URL ( mapping , `${ pathToFileURL ( basePath ) } /` ) ) ;
357+ }
358+
359+ let dirMatch = '' ;
360+ for ( const [ candidateKey , candidateValue ] of Object . entries ( pkgExports ) ) {
361+ if ( candidateKey [ candidateKey . length - 1 ] !== '/' ) continue ;
362+ if ( candidateValue [ candidateValue . length - 1 ] !== '/' ) continue ;
363+ if ( candidateKey . length > dirMatch . length &&
364+ StringPrototype . startsWith ( mappingKey , candidateKey ) ) {
365+ dirMatch = candidateKey ;
366+ }
367+ }
368+
369+ if ( dirMatch !== '' ) {
370+ const dirMapping = pkgExports [ dirMatch ] ;
371+ const remainder = StringPrototype . slice ( mappingKey , dirMatch . length ) ;
372+ const expectedPrefix =
373+ new URL ( dirMapping , `${ pathToFileURL ( basePath ) } /` ) ;
374+ const resolved = new URL ( remainder , expectedPrefix ) . href ;
375+ if ( StringPrototype . startsWith ( resolved , expectedPrefix . href ) ) {
376+ return fileURLToPath ( resolved ) ;
377+ }
378+ }
379+ throw new ERR_PATH_NOT_EXPORTED ( basePath , mappingKey ) ;
380+ }
381+ }
382+
383+ return path . resolve ( nmPath , request ) ;
384+ }
385+
300386Module . _findPath = function ( request , paths , isMain ) {
301- if ( path . isAbsolute ( request ) ) {
387+ const absoluteRequest = path . isAbsolute ( request ) ;
388+ if ( absoluteRequest ) {
302389 paths = [ '' ] ;
303390 } else if ( ! paths || paths . length === 0 ) {
304391 return false ;
@@ -322,7 +409,7 @@ Module._findPath = function(request, paths, isMain) {
322409 // Don't search further if path doesn't exist
323410 const curPath = paths [ i ] ;
324411 if ( curPath && stat ( curPath ) < 1 ) continue ;
325- var basePath = path . resolve ( curPath , request ) ;
412+ var basePath = resolveExports ( curPath , request , absoluteRequest ) ;
326413 var filename ;
327414
328415 var rc = stat ( basePath ) ;
0 commit comments