@@ -60,10 +60,11 @@ const {
6060 StringPrototypeSlice,
6161 StringPrototypeSplit,
6262 StringPrototypeStartsWith,
63+ Symbol,
6364} = primordials ;
6465
65- // Map used to store CJS parsing data.
66- const cjsParseCache = new SafeWeakMap ( ) ;
66+ // Map used to store CJS parsing data or for ESM loading .
67+ const cjsSourceCache = new SafeWeakMap ( ) ;
6768/**
6869 * Map of already-loaded CJS modules to use.
6970 */
@@ -72,12 +73,14 @@ const cjsExportsCache = new SafeWeakMap();
7273// Set first due to cycle with ESM loader functions.
7374module . exports = {
7475 cjsExportsCache,
75- cjsParseCache ,
76+ cjsSourceCache ,
7677 initializeCJS,
7778 Module,
7879 wrapSafe,
7980} ;
8081
82+ const is_main_symbol = Symbol ( 'is_main_symbol' ) ;
83+
8184const { BuiltinModule } = require ( 'internal/bootstrap/realm' ) ;
8285const {
8386 maybeCacheSourceMap,
@@ -98,7 +101,6 @@ const {
98101 containsModuleSyntax,
99102 compileFunctionForCJSLoader,
100103} = internalBinding ( 'contextify' ) ;
101-
102104const assert = require ( 'internal/assert' ) ;
103105const fs = require ( 'fs' ) ;
104106const path = require ( 'path' ) ;
@@ -107,7 +109,6 @@ const { safeGetenv } = internalBinding('credentials');
107109const {
108110 privateSymbols : {
109111 require_private_symbol,
110- host_defined_option_symbol,
111112 } ,
112113} = internalBinding ( 'util' ) ;
113114const {
@@ -396,6 +397,10 @@ function initializeCJS() {
396397 // TODO(joyeecheung): deprecate this in favor of a proper hook?
397398 Module . runMain =
398399 require ( 'internal/modules/run_main' ) . executeUserEntryPoint ;
400+
401+ if ( getOptionValue ( '--experimental-require-module' ) ) {
402+ Module . _extensions [ '.mjs' ] = loadESMFromCJS ;
403+ }
399404}
400405
401406// Given a module name, and a list of paths to test, returns the first
@@ -988,7 +993,7 @@ Module._load = function(request, parent, isMain) {
988993 if ( cachedModule !== undefined ) {
989994 updateChildren ( parent , cachedModule , true ) ;
990995 if ( ! cachedModule . loaded ) {
991- const parseCachedModule = cjsParseCache . get ( cachedModule ) ;
996+ const parseCachedModule = cjsSourceCache . get ( cachedModule ) ;
992997 if ( ! parseCachedModule || parseCachedModule . loaded ) {
993998 return getExportsForCircularRequire ( cachedModule ) ;
994999 }
@@ -1010,6 +1015,9 @@ Module._load = function(request, parent, isMain) {
10101015 setOwnProperty ( process , 'mainModule' , module ) ;
10111016 setOwnProperty ( module . require , 'main' , process . mainModule ) ;
10121017 module . id = '.' ;
1018+ module [ is_main_symbol ] = true ;
1019+ } else {
1020+ module [ is_main_symbol ] = false ;
10131021 }
10141022
10151023 reportModuleToWatchMode ( filename ) ;
@@ -1270,46 +1278,55 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
12701278 ) ;
12711279
12721280 // Cache the source map for the module if present.
1273- if ( script . sourceMapURL ) {
1274- maybeCacheSourceMap ( filename , content , this , false , undefined , script . sourceMapURL ) ;
1281+ const { sourceMapURL } = script ;
1282+ if ( sourceMapURL ) {
1283+ maybeCacheSourceMap ( filename , content , this , false , undefined , sourceMapURL ) ;
12751284 }
12761285
1277- return runScriptInThisContext ( script , true , false ) ;
1286+ return {
1287+ __proto__ : null ,
1288+ function : runScriptInThisContext ( script , true , false ) ,
1289+ sourceMapURL,
1290+ retryAsESM : false ,
1291+ } ;
12781292 }
12791293
1280- try {
1281- const result = compileFunctionForCJSLoader ( content , filename ) ;
1282- result . function [ host_defined_option_symbol ] = hostDefinedOptionId ;
1283-
1284- // cachedDataRejected is only set for cache coming from SEA.
1285- if ( codeCache &&
1286- result . cachedDataRejected !== false &&
1287- internalBinding ( 'sea' ) . isSea ( ) ) {
1288- process . emitWarning ( 'Code cache data rejected.' ) ;
1289- }
1294+ const result = compileFunctionForCJSLoader ( content , filename ) ;
12901295
1291- // Cache the source map for the module if present.
1292- if ( result . sourceMapURL ) {
1293- maybeCacheSourceMap ( filename , content , this , false , undefined , result . sourceMapURL ) ;
1294- }
1296+ // cachedDataRejected is only set for cache coming from SEA.
1297+ if ( codeCache &&
1298+ result . cachedDataRejected !== false &&
1299+ internalBinding ( 'sea' ) . isSea ( ) ) {
1300+ process . emitWarning ( 'Code cache data rejected.' ) ;
1301+ }
12951302
1296- return result . function ;
1297- } catch ( err ) {
1298- if ( process . mainModule === cjsModuleInstance ) {
1299- const { enrichCJSError } = require ( 'internal/modules/esm/translators' ) ;
1300- enrichCJSError ( err , content , filename ) ;
1301- }
1302- throw err ;
1303+ // Cache the source map for the module if present.
1304+ if ( result . sourceMapURL ) {
1305+ maybeCacheSourceMap ( filename , content , this , false , undefined , result . sourceMapURL ) ;
13031306 }
1307+
1308+ return result ;
1309+ }
1310+
1311+ // Resolve and evaluate as ESM, synchronously.
1312+ function loadESMFromCJS ( mod , filename ) {
1313+ const source = getMaybeCachedSource ( mod , filename ) ;
1314+ const cascadedLoader = require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
1315+ // We are still using the CJS's resolution here.
1316+ const url = pathToFileURL ( filename ) . href ;
1317+ const isMain = mod [ is_main_symbol ] ;
1318+ // TODO(joyeecheung): maybe we can do some special handling for default here. Maybe we don't.
1319+ mod . exports = cascadedLoader . importSyncForRequire ( url , source , isMain ) ;
13041320}
13051321
13061322/**
13071323 * Run the file contents in the correct scope or sandbox. Expose the correct helper variables (`require`, `module`,
13081324 * `exports`) to the file. Returns exception, if any.
13091325 * @param {string } content The source code of the module
13101326 * @param {string } filename The file path of the module
1327+ * @param {boolean } loadAsESM Whether it's known to be ESM - i.e. suffix is .mjs.
13111328 */
1312- Module . prototype . _compile = function ( content , filename ) {
1329+ Module . prototype . _compile = function ( content , filename , loadAsESM = false ) {
13131330 let moduleURL ;
13141331 let redirects ;
13151332 const manifest = policy ( ) ?. manifest ;
@@ -1319,8 +1336,25 @@ Module.prototype._compile = function(content, filename) {
13191336 manifest . assertIntegrity ( moduleURL , content ) ;
13201337 }
13211338
1322- const compiledWrapper = wrapSafe ( filename , content , this ) ;
1339+ // TODO(joyeecheung): when the module is the entry point, consider allowing TLA.
1340+ // Only modules being require()'d really need to avoid TLA.
1341+ let compiledWrapper ;
1342+ if ( ! loadAsESM ) {
1343+ const result = wrapSafe ( filename , content , this ) ;
1344+ compiledWrapper = result . function ;
1345+ loadAsESM = result . retryAsESM ;
1346+ }
13231347
1348+ if ( loadAsESM ) {
1349+ // Pass the source into the .mjs extension handler indirectly through the cache.
1350+ cjsSourceCache . set ( this , content ) ;
1351+ loadESMFromCJS ( this , filename ) ;
1352+ return ;
1353+ }
1354+
1355+ // TODO(joyeecheung): the detection below is unnecessarily complex. Maybe just
1356+ // use the is_main_symbol, or a break_on_start_symbol that gets passed from
1357+ // higher level instead of doing hacky detecion here.
13241358 let inspectorWrapper = null ;
13251359 if ( getOptionValue ( '--inspect-brk' ) && process . _eval == null ) {
13261360 if ( ! resolvedArgv ) {
@@ -1344,6 +1378,7 @@ Module.prototype._compile = function(content, filename) {
13441378 inspectorWrapper = internalBinding ( 'inspector' ) . callAndPauseOnStart ;
13451379 }
13461380 }
1381+
13471382 const dirname = path . dirname ( filename ) ;
13481383 const require = makeRequireFunction ( this , redirects ) ;
13491384 let result ;
@@ -1363,25 +1398,37 @@ Module.prototype._compile = function(content, filename) {
13631398 return result ;
13641399} ;
13651400
1366- /**
1367- * Native handler for `.js` files.
1368- * @param {Module } module The module to compile
1369- * @param {string } filename The file path of the module
1370- */
1371- Module . _extensions [ '.js' ] = function ( module , filename ) {
1372- // If already analyzed the source, then it will be cached.
1373- const cached = cjsParseCache . get ( module ) ;
1401+ function getMaybeCachedSource ( mod , filename ) {
1402+ const cached = cjsSourceCache . get ( mod ) ;
13741403 let content ;
13751404 if ( cached ?. source ) {
13761405 content = cached . source ;
13771406 cached . source = undefined ;
13781407 } else {
1408+ // TODO(joyeecheung): read a buffer.
13791409 content = fs . readFileSync ( filename , 'utf8' ) ;
13801410 }
1411+ return content ;
1412+ }
1413+
1414+ /**
1415+ * Native handler for `.js` files.
1416+ * @param {Module } module The module to compile
1417+ * @param {string } filename The file path of the module
1418+ */
1419+ Module . _extensions [ '.js' ] = function ( module , filename ) {
1420+ // If already analyzed the source, then it will be cached.
1421+ const content = getMaybeCachedSource ( module , filename ) ;
1422+
13811423 if ( StringPrototypeEndsWith ( filename , '.js' ) ) {
13821424 const pkg = packageJsonReader . getNearestParentPackageJSON ( filename ) ;
13831425 // Function require shouldn't be used in ES modules.
13841426 if ( pkg ?. data . type === 'module' ) {
1427+ if ( getOptionValue ( '--experimental-require-module' ) ) {
1428+ module . _compile ( content , filename , true ) ;
1429+ return ;
1430+ }
1431+
13851432 // This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
13861433 const parent = moduleParentCache . get ( module ) ;
13871434 const parentPath = parent ?. filename ;
@@ -1414,7 +1461,8 @@ Module._extensions['.js'] = function(module, filename) {
14141461 throw err ;
14151462 }
14161463 }
1417- module . _compile ( content , filename ) ;
1464+
1465+ module . _compile ( content , filename , false ) ;
14181466} ;
14191467
14201468/**
0 commit comments