@@ -53,10 +53,12 @@ const { compileFunction } = internalBinding('contextify');
5353const {
5454 ERR_INVALID_ARG_VALUE ,
5555 ERR_INVALID_OPT_VALUE ,
56+ ERR_PATH_NOT_EXPORTED ,
5657 ERR_REQUIRE_ESM
5758} = require ( 'internal/errors' ) . codes ;
5859const { validateString } = require ( 'internal/validators' ) ;
5960const pendingDeprecation = getOptionValue ( '--pending-deprecation' ) ;
61+ const experimentalExports = getOptionValue ( '--experimental-exports' ) ;
6062
6163module . exports = { wrapSafe, Module } ;
6264
@@ -182,12 +184,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
182184
183185// Check if the directory is a package.json dir.
184186const packageMainCache = Object . create ( null ) ;
187+ // Explicit exports from package.json files
188+ const packageExportsCache = new Map ( ) ;
185189
186- function readPackage ( requestPath ) {
187- const entry = packageMainCache [ requestPath ] ;
188- if ( entry )
189- return entry ;
190-
190+ function readPackageRaw ( requestPath ) {
191191 const jsonPath = path . resolve ( requestPath , 'package.json' ) ;
192192 const json = internalModuleReadJSON ( path . toNamespacedPath ( jsonPath ) ) ;
193193
@@ -201,14 +201,44 @@ function readPackage(requestPath) {
201201 }
202202
203203 try {
204- return packageMainCache [ requestPath ] = JSON . parse ( json ) . main ;
204+ const parsed = JSON . parse ( json ) ;
205+ packageMainCache [ requestPath ] = parsed . main ;
206+ if ( experimentalExports ) {
207+ packageExportsCache . set ( requestPath , parsed . exports ) ;
208+ }
209+ return parsed ;
205210 } catch ( e ) {
206211 e . path = jsonPath ;
207212 e . message = 'Error parsing ' + jsonPath + ': ' + e . message ;
208213 throw e ;
209214 }
210215}
211216
217+ function readPackage ( requestPath ) {
218+ const entry = packageMainCache [ requestPath ] ;
219+ if ( entry )
220+ return entry ;
221+
222+ const pkg = readPackageRaw ( requestPath ) ;
223+ if ( pkg === false ) return false ;
224+
225+ return pkg . main ;
226+ }
227+
228+ function readExports ( requestPath ) {
229+ if ( packageExportsCache . has ( requestPath ) ) {
230+ return packageExportsCache . get ( requestPath ) ;
231+ }
232+
233+ const pkg = readPackageRaw ( requestPath ) ;
234+ if ( ! pkg ) {
235+ packageExportsCache . set ( requestPath , null ) ;
236+ return null ;
237+ }
238+
239+ return pkg . exports ;
240+ }
241+
212242function tryPackage ( requestPath , exts , isMain , originalPath ) {
213243 const pkg = readPackage ( requestPath ) ;
214244
@@ -297,8 +327,58 @@ function findLongestRegisteredExtension(filename) {
297327 return '.js' ;
298328}
299329
330+ // This only applies to requests of a specific form:
331+ // 1. name/.*
332+ // 2. @scope/name/.*
333+ const EXPORTS_PATTERN = / ^ ( (?: @ [ ^ . / @ \\ ] [ ^ / @ \\ ] * \/ ) ? [ ^ @ . / \\ ] [ ^ / \\ ] * ) ( \/ .* ) $ / ;
334+ function resolveExports ( nmPath , request , absoluteRequest ) {
335+ // The implementation's behavior is meant to mirror resolution in ESM.
336+ if ( experimentalExports && ! absoluteRequest ) {
337+ const [ , name , expansion ] =
338+ request . match ( EXPORTS_PATTERN ) || [ ] ;
339+ if ( ! name ) {
340+ return path . resolve ( nmPath , request ) ;
341+ }
342+
343+ const basePath = path . resolve ( nmPath , name ) ;
344+ const pkgExports = readExports ( basePath ) ;
345+
346+ if ( pkgExports != null ) {
347+ const mappingKey = `.${ expansion } ` ;
348+ const mapping = pkgExports [ mappingKey ] ;
349+ if ( typeof mapping === 'string' ) {
350+ return path . resolve ( basePath , mapping ) ;
351+ }
352+
353+ let dirMatch = '' ;
354+ for ( const [ candidateKey , candidateValue ] of Object . entries ( pkgExports ) ) {
355+ if ( candidateKey [ candidateKey . length - 1 ] !== '/' ) continue ;
356+ if ( candidateValue [ candidateValue . length - 1 ] !== '/' ) continue ;
357+ if ( candidateKey . length > dirMatch . length &&
358+ mappingKey . startsWith ( candidateKey ) ) {
359+ dirMatch = candidateKey ;
360+ }
361+ }
362+
363+ if ( dirMatch !== '' ) {
364+ const dirMapping = pkgExports [ dirMatch ] ;
365+ const remainder = mappingKey . slice ( dirMatch . length ) ;
366+ const expectedPrefix = path . resolve ( basePath , dirMapping ) ;
367+ const resolved = path . resolve ( expectedPrefix , remainder ) ;
368+ if ( resolved . startsWith ( expectedPrefix ) ) {
369+ return resolved ;
370+ }
371+ }
372+ throw new ERR_PATH_NOT_EXPORTED ( basePath , mappingKey ) ;
373+ }
374+ }
375+
376+ return path . resolve ( nmPath , request ) ;
377+ }
378+
300379Module . _findPath = function ( request , paths , isMain ) {
301- if ( path . isAbsolute ( request ) ) {
380+ const absoluteRequest = path . isAbsolute ( request ) ;
381+ if ( absoluteRequest ) {
302382 paths = [ '' ] ;
303383 } else if ( ! paths || paths . length === 0 ) {
304384 return false ;
@@ -322,7 +402,7 @@ Module._findPath = function(request, paths, isMain) {
322402 // Don't search further if path doesn't exist
323403 const curPath = paths [ i ] ;
324404 if ( curPath && stat ( curPath ) < 1 ) continue ;
325- var basePath = path . resolve ( curPath , request ) ;
405+ var basePath = resolveExports ( curPath , request , absoluteRequest ) ;
326406 var filename ;
327407
328408 var rc = stat ( basePath ) ;
0 commit comments