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
@@ -193,12 +201,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
193201
194202// Check if the directory is a package.json dir.
195203const packageMainCache = Object . create ( null ) ;
204+ // Explicit exports from package.json files
205+ const packageExportsCache = new SafeMap ( ) ;
196206
197- function readPackage ( requestPath ) {
198- const entry = packageMainCache [ requestPath ] ;
199- if ( entry )
200- return entry ;
201-
207+ function readPackageRaw ( requestPath ) {
202208 const jsonPath = path . resolve ( requestPath , 'package.json' ) ;
203209 const json = internalModuleReadJSON ( path . toNamespacedPath ( jsonPath ) ) ;
204210
@@ -212,14 +218,44 @@ function readPackage(requestPath) {
212218 }
213219
214220 try {
215- return packageMainCache [ requestPath ] = JSON . parse ( json ) . main ;
221+ const parsed = JSON . parse ( json ) ;
222+ packageMainCache [ requestPath ] = parsed . main ;
223+ if ( experimentalExports ) {
224+ packageExportsCache . set ( requestPath , parsed . exports ) ;
225+ }
226+ return parsed ;
216227 } catch ( e ) {
217228 e . path = jsonPath ;
218229 e . message = 'Error parsing ' + jsonPath + ': ' + e . message ;
219230 throw e ;
220231 }
221232}
222233
234+ function readPackage ( requestPath ) {
235+ const entry = packageMainCache [ requestPath ] ;
236+ if ( entry )
237+ return entry ;
238+
239+ const pkg = readPackageRaw ( requestPath ) ;
240+ if ( pkg === false ) return false ;
241+
242+ return pkg . main ;
243+ }
244+
245+ function readExports ( requestPath ) {
246+ if ( packageExportsCache . has ( requestPath ) ) {
247+ return packageExportsCache . get ( requestPath ) ;
248+ }
249+
250+ const pkg = readPackageRaw ( requestPath ) ;
251+ if ( ! pkg ) {
252+ packageExportsCache . set ( requestPath , null ) ;
253+ return null ;
254+ }
255+
256+ return pkg . exports ;
257+ }
258+
223259function tryPackage ( requestPath , exts , isMain , originalPath ) {
224260 const pkg = readPackage ( requestPath ) ;
225261
@@ -308,8 +344,59 @@ function findLongestRegisteredExtension(filename) {
308344 return '.js' ;
309345}
310346
347+ // This only applies to requests of a specific form:
348+ // 1. name/.*
349+ // 2. @scope/name/.*
350+ const EXPORTS_PATTERN = / ^ ( (?: @ [ ^ . / @ \\ ] [ ^ / @ \\ ] * \/ ) ? [ ^ @ . / \\ ] [ ^ / \\ ] * ) ( \/ .* ) $ / ;
351+ function resolveExports ( nmPath , request , absoluteRequest ) {
352+ // The implementation's behavior is meant to mirror resolution in ESM.
353+ if ( experimentalExports && ! absoluteRequest ) {
354+ const [ , name , expansion ] =
355+ StringPrototype . match ( request , EXPORTS_PATTERN ) || [ ] ;
356+ if ( ! name ) {
357+ return path . resolve ( nmPath , request ) ;
358+ }
359+
360+ const basePath = path . resolve ( nmPath , name ) ;
361+ const pkgExports = readExports ( basePath ) ;
362+
363+ if ( pkgExports != null ) {
364+ const mappingKey = `.${ expansion } ` ;
365+ const mapping = pkgExports [ mappingKey ] ;
366+ if ( typeof mapping === 'string' ) {
367+ return fileURLToPath ( new URL ( mapping , `${ pathToFileURL ( basePath ) } /` ) ) ;
368+ }
369+
370+ let dirMatch = '' ;
371+ for ( const [ candidateKey , candidateValue ] of Object . entries ( pkgExports ) ) {
372+ if ( candidateKey [ candidateKey . length - 1 ] !== '/' ) continue ;
373+ if ( candidateValue [ candidateValue . length - 1 ] !== '/' ) continue ;
374+ if ( candidateKey . length > dirMatch . length &&
375+ StringPrototype . startsWith ( mappingKey , candidateKey ) ) {
376+ dirMatch = candidateKey ;
377+ }
378+ }
379+
380+ if ( dirMatch !== '' ) {
381+ const dirMapping = pkgExports [ dirMatch ] ;
382+ const remainder = StringPrototype . slice ( mappingKey , dirMatch . length ) ;
383+ const expectedPrefix =
384+ new URL ( dirMapping , `${ pathToFileURL ( basePath ) } /` ) ;
385+ const resolved = new URL ( remainder , expectedPrefix ) . href ;
386+ if ( StringPrototype . startsWith ( resolved , expectedPrefix . href ) ) {
387+ return fileURLToPath ( resolved ) ;
388+ }
389+ }
390+ throw new ERR_PATH_NOT_EXPORTED ( basePath , mappingKey ) ;
391+ }
392+ }
393+
394+ return path . resolve ( nmPath , request ) ;
395+ }
396+
311397Module . _findPath = function ( request , paths , isMain ) {
312- if ( path . isAbsolute ( request ) ) {
398+ const absoluteRequest = path . isAbsolute ( request ) ;
399+ if ( absoluteRequest ) {
313400 paths = [ '' ] ;
314401 } else if ( ! paths || paths . length === 0 ) {
315402 return false ;
@@ -333,7 +420,7 @@ Module._findPath = function(request, paths, isMain) {
333420 // Don't search further if path doesn't exist
334421 const curPath = paths [ i ] ;
335422 if ( curPath && stat ( curPath ) < 1 ) continue ;
336- var basePath = path . resolve ( curPath , request ) ;
423+ var basePath = resolveExports ( curPath , request , absoluteRequest ) ;
337424 var filename ;
338425
339426 var rc = stat ( basePath ) ;
0 commit comments