Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit612b870

Browse files
committed
module: support require()ing synchronous ESM graphs
This patch adds `require()` support for synchronous ESM graphs underthe flag --experimental-require-module.This is based on the the following design aspect of ESM:- The resolution can be synchronous (up to the host)- The evaluation of a synchronous graph (without top-level await) is also synchronous, and, by the time the module graph is instantiated (before evaluation starts), this is is already known.When the module being require()ed has .mjs extension or there areother explicit indicators that it's an ES module, we load it as anES module. If the graph is synchronous, we return the module namespaceas the exports. If the graph contains top-level await, we throw anerror before evaluating the module. If an additional flag--print-pending-tla is passed, we proceeds to evaluation but do notrun the microtasks, only to find out where the TLA is and printtheir location to help users fix them.If there are not explicit indicators whether a .js file is CJS or ESM,we parse it as CJS first. If the parse error indicates that it containsESM syntax, we parse it again as ESM. If the second parsing succeeds,we continue to treat it as ESM.
1 parentff4fb7e commit612b870

File tree

15 files changed

+431
-134
lines changed

15 files changed

+431
-134
lines changed

‎.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ doc/changelogs/CHANGELOG_v1*.md
1313
!doc/changelogs/CHANGELOG_v18.md
1414
!doc/api_assets/*.js
1515
!.eslintrc.js
16+
test/es-module/test-require-module-entry-point.js

‎doc/api/cli.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,22 @@ added: v11.8.0
871871

872872
Use the specified file as a security policy.
873873

874+
###`--experimental-require-module`
875+
876+
<!-- YAML
877+
added: REPLACEME
878+
-->
879+
880+
>Stability: 1.1 - Active Developement
881+
882+
Supports loading a synchronous ES module graph in`require()`. If the module
883+
graph is not synchronous (contains top-level await), it throws an error.
884+
885+
By default, a`.js` file will be parsed as a CommonJS module first. If it
886+
contains ES module syntax, Node.js will try to parse and evaluate the module
887+
again as an ES module. If it turns out to be synchronous and can be evaluated
888+
successfully, the module namespace object will be returned by`require()`.
889+
874890
###`--experimental-sea-config`
875891

876892
<!-- YAML
@@ -2523,6 +2539,7 @@ Node.js options that are allowed are:
25232539
*`--experimental-network-imports`
25242540
*`--experimental-permission`
25252541
*`--experimental-policy`
2542+
*`--experimental-require-module`
25262543
*`--experimental-shadow-realm`
25272544
*`--experimental-specifier-resolution`
25282545
*`--experimental-top-level-await`

‎lib/internal/modules/cjs/loader.js

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const {
6060
StringPrototypeSlice,
6161
StringPrototypeSplit,
6262
StringPrototypeStartsWith,
63+
Symbol,
6364
}=primordials;
6465

6566
// Map used to store CJS parsing data.
@@ -107,7 +108,6 @@ const { safeGetenv } = internalBinding('credentials');
107108
const{
108109
privateSymbols:{
109110
require_private_symbol,
110-
host_defined_option_symbol,
111111
},
112112
}=internalBinding('util');
113113
const{
@@ -161,6 +161,8 @@ let requireDepth = 0;
161161
letisPreloading=false;
162162
letstatCache=null;
163163

164+
constis_main_symbol=Symbol('is-main-module');
165+
164166
/**
165167
* Our internal implementation of `require`.
166168
*@param {Module} module Parent module of what is being required
@@ -271,6 +273,7 @@ function Module(id = '', parent) {
271273
setOwnProperty(this.__proto__,'require',makeRequireFunction(this,redirects));
272274
}
273275
this[require_private_symbol]=internalRequire;
276+
this[is_main_symbol]=false;// Set to true by the entry point handler.
274277
}
275278

276279
/**@type {Record<string, Module>} */
@@ -396,6 +399,10 @@ function initializeCJS() {
396399
// TODO(joyeecheung): deprecate this in favor of a proper hook?
397400
Module.runMain=
398401
require('internal/modules/run_main').executeUserEntryPoint;
402+
403+
if(getOptionValue('--experimental-require-module')){
404+
Module._extensions['.mjs']=loadESMFromCJS;
405+
}
399406
}
400407

401408
// Given a module name, and a list of paths to test, returns the first
@@ -1010,6 +1017,7 @@ Module._load = function(request, parent, isMain) {
10101017
setOwnProperty(process,'mainModule',module);
10111018
setOwnProperty(module.require,'main',process.mainModule);
10121019
module.id='.';
1020+
module[is_main_symbol]=true;
10131021
}
10141022

10151023
reportModuleToWatchMode(filename);
@@ -1270,46 +1278,58 @@ 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-
returnrunScriptInThisContext(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-
constresult=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+
constresult=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-
returnresult.function;
1297-
}catch(err){
1298-
if(process.mainModule===cjsModuleInstance){
1299-
const{ enrichCJSError}=require('internal/modules/esm/translators');
1300-
enrichCJSError(err,content,filename);
1301-
}
1302-
throwerr;
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+
returnresult;
1309+
}
1310+
1311+
// Resolve and evaluate as ESM, synchronously.
1312+
functionloadESMFromCJS(mod,filename){
1313+
constcascadedLoader=require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
1314+
// Note that we are still using the CJS's path resolution here.
1315+
constparent=moduleParentCache.get(mod)?.filename;
1316+
constbase=parent ?pathToFileURL(parent) :parent;
1317+
// console.log('loadESMFromCJS', mod, filename, base);
1318+
constspecifier=mod[is_main_symbol] ?pathToFileURL(mod.filename) :mod.id;
1319+
constjob=cascadedLoader.getModuleJobSync(specifier,base,kEmptyObject,'from-cjs-error');
1320+
const{ namespace}=job.runSync();
1321+
// TODO(joyeecheung): maybe we can do some special handling for default here. Maybe we don't.
1322+
mod.exports=namespace;
13041323
}
13051324

13061325
/**
13071326
* Run the file contents in the correct scope or sandbox. Expose the correct helper variables (`require`, `module`,
13081327
* `exports`) to the file. Returns exception, if any.
13091328
*@param {string} content The source code of the module
13101329
*@param {string} filename The file path of the module
1330+
*@param {boolean} loadAsESM Whether it's known to be ESM - i.e. suffix is .mjs.
13111331
*/
1312-
Module.prototype._compile=function(content,filename){
1332+
Module.prototype._compile=function(content,filename,loadAsESM=false){
13131333
letmoduleURL;
13141334
letredirects;
13151335
constmanifest=policy()?.manifest;
@@ -1319,8 +1339,21 @@ Module.prototype._compile = function(content, filename) {
13191339
manifest.assertIntegrity(moduleURL,content);
13201340
}
13211341

1322-
constcompiledWrapper=wrapSafe(filename,content,this);
1342+
letcompiledWrapper;
1343+
if(!loadAsESM){
1344+
constresult=wrapSafe(filename,content,this);
1345+
compiledWrapper=result.function;
1346+
loadAsESM=result.retryAsESM;
1347+
}
1348+
1349+
if(loadAsESM){
1350+
loadESMFromCJS(this);
1351+
return;
1352+
}
13231353

1354+
// TODO(joyeecheung): the detection below is unnecessarily complex. Maybe just
1355+
// use the is_main_symbol, or a break_on_start_symbol that gets passed from
1356+
// higher level instead of doing hacky detecion here.
13241357
letinspectorWrapper=null;
13251358
if(getOptionValue('--inspect-brk')&&process._eval==null){
13261359
if(!resolvedArgv){
@@ -1344,6 +1377,7 @@ Module.prototype._compile = function(content, filename) {
13441377
inspectorWrapper=internalBinding('inspector').callAndPauseOnStart;
13451378
}
13461379
}
1380+
13471381
constdirname=path.dirname(filename);
13481382
constrequire=makeRequireFunction(this,redirects);
13491383
letresult;
@@ -1370,6 +1404,7 @@ Module.prototype._compile = function(content, filename) {
13701404
*/
13711405
Module._extensions['.js']=function(module,filename){
13721406
// If already analyzed the source, then it will be cached.
1407+
// TODO(joyeecheung): pass as buffer.
13731408
constcached=cjsParseCache.get(module);
13741409
letcontent;
13751410
if(cached?.source){
@@ -1378,7 +1413,8 @@ Module._extensions['.js'] = function(module, filename) {
13781413
}else{
13791414
content=fs.readFileSync(filename,'utf8');
13801415
}
1381-
if(StringPrototypeEndsWith(filename,'.js')){
1416+
if(!getOptionValue('--experimental-require-module')&&
1417+
StringPrototypeEndsWith(filename,'.js')){
13821418
constpkg=packageJsonReader.getNearestParentPackageJSON(filename);
13831419
// Function require shouldn't be used in ES modules.
13841420
if(pkg?.data.type==='module'){
@@ -1414,7 +1450,8 @@ Module._extensions['.js'] = function(module, filename) {
14141450
throwerr;
14151451
}
14161452
}
1417-
module._compile(content,filename);
1453+
1454+
module._compile(content,filename,false);
14181455
};
14191456

14201457
/**

‎lib/internal/modules/esm/loader.js

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
hardenRegExp,
1616
}=primordials;
1717

18+
constassert=require('internal/assert');
1819
const{
1920
ERR_REQUIRE_ESM,
2021
ERR_UNKNOWN_MODULE_FORMAT,
@@ -228,12 +229,12 @@ class ModuleLoader {
228229
returnthis.getJobFromResolveResult(resolveResult,parentURL,importAttributes);
229230
}
230231

231-
getModuleJobSync(specifier,parentURL,importAttributes){
232-
constresolveResult=this.resolveSync(specifier,parentURL,importAttributes);
233-
returnthis.getJobFromResolveResult(resolveResult,parentURL,importAttributes,true);
232+
getModuleJobSync(specifier,parentURL,importAttributes,requireESMHint){
233+
constresolveResult=this.resolveSync(specifier,parentURL,importAttributes,requireESMHint);
234+
returnthis.getJobFromResolveResult(resolveResult,parentURL,importAttributes,true,requireESMHint);
234235
}
235236

236-
getJobFromResolveResult(resolveResult,parentURL,importAttributes,sync){
237+
getJobFromResolveResult(resolveResult,parentURL,importAttributes,sync,requireESMHint){
237238
const{ url, format}=resolveResult;
238239
constresolvedImportAttributes=resolveResult.importAttributes??importAttributes;
239240
letjob=this.loadCache.get(url,resolvedImportAttributes.type);
@@ -244,7 +245,7 @@ class ModuleLoader {
244245
}
245246

246247
if(job===undefined){
247-
job=this.#createModuleJob(url,resolvedImportAttributes,parentURL,format,sync);
248+
job=this.#createModuleJob(url,resolvedImportAttributes,parentURL,format,sync,requireESMHint);
248249
}
249250

250251
returnjob;
@@ -261,7 +262,7 @@ class ModuleLoader {
261262
* `resolve` hook
262263
*@returns {Promise<ModuleJob>} The (possibly pending) module job
263264
*/
264-
#createModuleJob(url,importAttributes,parentURL,format,sync){
265+
#createModuleJob(url,importAttributes,parentURL,format,sync,requireESMHint){
265266
constcallTranslator=({format:finalFormat, responseURL, source},isMain)=>{
266267
consttranslator=getTranslators().get(finalFormat);
267268

@@ -274,7 +275,7 @@ class ModuleLoader {
274275
constcontext={ format, importAttributes};
275276

276277
constmoduleProvider=sync ?
277-
(url,isMain)=>callTranslator(this.loadSync(url,context),isMain) :
278+
(url,isMain)=>callTranslator(this.loadSync(url,context,requireESMHint),isMain) :
278279
async(url,isMain)=>callTranslator(awaitthis.load(url,context),isMain);
279280

280281
constinspectBrk=(
@@ -358,26 +359,30 @@ class ModuleLoader {
358359
* Just like `resolve` except synchronous. This is here specifically to support
359360
* `import.meta.resolve` which must happen synchronously.
360361
*/
361-
resolveSync(originalSpecifier,parentURL,importAttributes){
362-
if(this.#customizations){
362+
resolveSync(originalSpecifier,parentURL,importAttributes,requireESMHint){
363+
// If this comes from the require(esm) fallback, don't apply loader hooks which are on
364+
// a separate thread. This is ignored by require(cjs) already anyway.
365+
// TODO(joyeecheung): add support in hooks for this?
366+
if(this.#customizations&&!requireESMHint){
363367
returnthis.#customizations.resolveSync(originalSpecifier,parentURL,importAttributes);
364368
}
365-
returnthis.defaultResolve(originalSpecifier,parentURL,importAttributes);
369+
returnthis.defaultResolve(originalSpecifier,parentURL,importAttributes,requireESMHint);
366370
}
367371

368372
/**
369373
* Our `defaultResolve` is synchronous and can be used in both
370374
* `resolve` and `resolveSync`. This function is here just to avoid
371375
* repeating the same code block twice in those functions.
372376
*/
373-
defaultResolve(originalSpecifier,parentURL,importAttributes){
377+
defaultResolve(originalSpecifier,parentURL,importAttributes,requireESMHint){
374378
defaultResolve??=require('internal/modules/esm/resolve').defaultResolve;
375379

376380
constcontext={
377381
__proto__:null,
378382
conditions:this.#defaultConditions,
379383
importAttributes,
380384
parentURL,
385+
requireESMHint,
381386
};
382387

383388
returndefaultResolve(originalSpecifier,context);
@@ -398,14 +403,23 @@ class ModuleLoader {
398403
returnresult;
399404
}
400405

401-
loadSync(url,context){
406+
loadSync(url,context,requireESMHint){
402407
defaultLoadSync??=require('internal/modules/esm/load').defaultLoadSync;
403-
404-
letresult=this.#customizations ?
408+
constisRequireModuleAllowed=getOptionValue('--experimental-require-module');
409+
if(requireESMHint==='from-cjs-error'){
410+
assert(isRequireModuleAllowed);
411+
context.format='module';
412+
}
413+
// If this comes from the require(esm) fallback, don't apply loader hooks which are on
414+
// a separate thread. This is ignored by require(cjs) already anyway.
415+
// TODO(joyeecheung): add support in hooks for this?
416+
letresult=this.#customizations&&!requireESMHint ?
405417
this.#customizations.loadSync(url,context) :
406418
defaultLoadSync(url,context);
419+
420+
// TODO(joyeecheung): we need a better way to detect the format and cache the result.
407421
letformat=result?.format;
408-
if(format==='module'){
422+
if(format==='module'&&!isRequireModuleAllowed){
409423
thrownewERR_REQUIRE_ESM(url,true);
410424
}
411425
if(format==='commonjs'){

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp