module: use v8 synthetic modules
PR-URL: https://github.com/nodejs/node/pull/29846 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
This commit is contained in:
parent
0521a98fd6
commit
ffd22e8198
@ -150,8 +150,7 @@ function NativeModule(id) {
|
||||
this.filename = `${id}.js`;
|
||||
this.id = id;
|
||||
this.exports = {};
|
||||
this.reflect = undefined;
|
||||
this.esmFacade = undefined;
|
||||
this.module = undefined;
|
||||
this.exportKeys = undefined;
|
||||
this.loaded = false;
|
||||
this.loading = false;
|
||||
@ -240,16 +239,18 @@ NativeModule.prototype.getURL = function() {
|
||||
};
|
||||
|
||||
NativeModule.prototype.getESMFacade = function() {
|
||||
if (this.esmFacade) return this.esmFacade;
|
||||
const createDynamicModule = nativeModuleRequire(
|
||||
'internal/modules/esm/create_dynamic_module');
|
||||
if (this.module) return this.module;
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
const url = this.getURL();
|
||||
return this.esmFacade = createDynamicModule(
|
||||
[], [...this.exportKeys, 'default'], url, (reflect) => {
|
||||
this.reflect = reflect;
|
||||
this.syncExports();
|
||||
reflect.exports.default.set(this.exports);
|
||||
});
|
||||
const nativeModule = this;
|
||||
this.module = new ModuleWrap(function() {
|
||||
nativeModule.syncExports();
|
||||
this.setExport('default', nativeModule.exports);
|
||||
}, [...this.exportKeys, 'default'], url);
|
||||
// Ensure immediate sync execution to capture exports now
|
||||
this.module.instantiate();
|
||||
this.module.evaluate(-1, false);
|
||||
return this.module;
|
||||
};
|
||||
|
||||
// Provide named exports for all builtin libraries so that the libraries
|
||||
@ -258,13 +259,12 @@ NativeModule.prototype.getESMFacade = function() {
|
||||
// called so that APMs and other behavior are supported.
|
||||
NativeModule.prototype.syncExports = function() {
|
||||
const names = this.exportKeys;
|
||||
if (this.reflect) {
|
||||
if (this.module) {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const exportName = names[i];
|
||||
if (exportName === 'default') continue;
|
||||
this.reflect.exports[exportName].set(
|
||||
getOwn(this.exports, exportName, this.exports)
|
||||
);
|
||||
this.module.setExport(exportName,
|
||||
getOwn(this.exports, exportName, this.exports));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -74,9 +74,7 @@ const experimentalExports = getOptionValue('--experimental-exports');
|
||||
|
||||
module.exports = { wrapSafe, Module };
|
||||
|
||||
let asyncESM;
|
||||
let ModuleJob;
|
||||
let createDynamicModule;
|
||||
let asyncESM, ModuleJob, ModuleWrap, kInstantiated;
|
||||
|
||||
const {
|
||||
CHAR_FORWARD_SLASH,
|
||||
@ -819,21 +817,18 @@ Module.prototype.load = function(filename) {
|
||||
const module = ESMLoader.moduleMap.get(url);
|
||||
// Create module entry at load time to snapshot exports correctly
|
||||
const exports = this.exports;
|
||||
if (module !== undefined) { // Called from cjs translator
|
||||
if (module.reflect) {
|
||||
module.reflect.onReady((reflect) => {
|
||||
reflect.exports.default.set(exports);
|
||||
});
|
||||
}
|
||||
// Called from cjs translator
|
||||
if (module !== undefined && module.module !== undefined) {
|
||||
if (module.module.getStatus() >= kInstantiated)
|
||||
module.module.setExport('default', exports);
|
||||
} else { // preemptively cache
|
||||
ESMLoader.moduleMap.set(
|
||||
url,
|
||||
new ModuleJob(ESMLoader, url, async () => {
|
||||
return createDynamicModule(
|
||||
[], ['default'], url, (reflect) => {
|
||||
reflect.exports.default.set(exports);
|
||||
});
|
||||
})
|
||||
new ModuleJob(ESMLoader, url, () =>
|
||||
new ModuleWrap(function() {
|
||||
this.setExport('default', exports);
|
||||
}, ['default'], url)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1150,6 +1145,5 @@ Module.Module = Module;
|
||||
if (experimentalModules) {
|
||||
asyncESM = require('internal/process/esm_loader');
|
||||
ModuleJob = require('internal/modules/esm/module_job');
|
||||
createDynamicModule = require(
|
||||
'internal/modules/esm/create_dynamic_module');
|
||||
({ ModuleWrap, kInstantiated } = internalBinding('module_wrap'));
|
||||
}
|
||||
|
@ -117,12 +117,7 @@ class Loader {
|
||||
source,
|
||||
url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href
|
||||
) {
|
||||
const evalInstance = async (url) => {
|
||||
return {
|
||||
module: new ModuleWrap(source, url),
|
||||
reflect: undefined
|
||||
};
|
||||
};
|
||||
const evalInstance = (url) => new ModuleWrap(source, url);
|
||||
const job = new ModuleJob(this, url, evalInstance, false);
|
||||
this.moduleMap.set(url, job);
|
||||
const { module, result } = await job.run();
|
||||
@ -165,7 +160,7 @@ class Loader {
|
||||
return createDynamicModule([], exports, url, (reflect) => {
|
||||
debug(`Loading dynamic ${url}`);
|
||||
execute(reflect.exports);
|
||||
});
|
||||
}).module;
|
||||
};
|
||||
} else {
|
||||
if (!translators.has(format))
|
||||
|
@ -30,19 +30,17 @@ class ModuleJob {
|
||||
// onto `this` by `link()` below once it has been resolved.
|
||||
this.modulePromise = moduleProvider.call(loader, url, isMain);
|
||||
this.module = undefined;
|
||||
this.reflect = undefined;
|
||||
|
||||
// Wait for the ModuleWrap instance being linked with all dependencies.
|
||||
const link = async () => {
|
||||
({ module: this.module,
|
||||
reflect: this.reflect } = await this.modulePromise);
|
||||
this.module = await this.modulePromise;
|
||||
assert(this.module instanceof ModuleWrap);
|
||||
|
||||
const dependencyJobs = [];
|
||||
const promises = this.module.link(async (specifier) => {
|
||||
const jobPromise = this.loader.getModuleJob(specifier, url);
|
||||
dependencyJobs.push(jobPromise);
|
||||
return (await (await jobPromise).modulePromise).module;
|
||||
return (await jobPromise).modulePromise;
|
||||
});
|
||||
|
||||
if (promises !== undefined)
|
||||
|
@ -32,6 +32,8 @@ const {
|
||||
const readFileAsync = promisify(fs.readFile);
|
||||
const JsonParse = JSON.parse;
|
||||
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
|
||||
const moduleWrap = internalBinding('module_wrap');
|
||||
const { ModuleWrap } = moduleWrap;
|
||||
|
||||
const debug = debuglog('esm');
|
||||
|
||||
@ -77,22 +79,18 @@ translators.set('module', async function moduleStrategy(url) {
|
||||
const source = `${await getSource(url)}`;
|
||||
maybeCacheSourceMap(url, source);
|
||||
debug(`Translating StandardModule ${url}`);
|
||||
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
|
||||
const module = new ModuleWrap(source, url);
|
||||
callbackMap.set(module, {
|
||||
moduleWrap.callbackMap.set(module, {
|
||||
initializeImportMeta,
|
||||
importModuleDynamically,
|
||||
});
|
||||
return {
|
||||
module,
|
||||
reflect: undefined,
|
||||
};
|
||||
return module;
|
||||
});
|
||||
|
||||
// Strategy for loading a node-style CommonJS module
|
||||
const isWindows = process.platform === 'win32';
|
||||
const winSepRegEx = /\//g;
|
||||
translators.set('commonjs', async function commonjsStrategy(url, isMain) {
|
||||
translators.set('commonjs', function commonjsStrategy(url, isMain) {
|
||||
debug(`Translating CJSModule ${url}`);
|
||||
const pathname = internalURLModule.fileURLToPath(new URL(url));
|
||||
const cached = this.cjsCache.get(url);
|
||||
@ -105,17 +103,17 @@ translators.set('commonjs', async function commonjsStrategy(url, isMain) {
|
||||
];
|
||||
if (module && module.loaded) {
|
||||
const exports = module.exports;
|
||||
return createDynamicModule([], ['default'], url, (reflect) => {
|
||||
reflect.exports.default.set(exports);
|
||||
});
|
||||
return new ModuleWrap(function() {
|
||||
this.setExport('default', exports);
|
||||
}, ['default'], url);
|
||||
}
|
||||
return createDynamicModule([], ['default'], url, () => {
|
||||
return new ModuleWrap(function() {
|
||||
debug(`Loading CJSModule ${url}`);
|
||||
// We don't care about the return val of _load here because Module#load
|
||||
// will handle it for us by checking the loader registry and filling the
|
||||
// exports like above
|
||||
CJSModule._load(pathname, undefined, isMain);
|
||||
});
|
||||
}, ['default'], url);
|
||||
});
|
||||
|
||||
// Strategy for loading a node builtin CommonJS module that isn't
|
||||
@ -145,9 +143,9 @@ translators.set('json', async function jsonStrategy(url) {
|
||||
module = CJSModule._cache[modulePath];
|
||||
if (module && module.loaded) {
|
||||
const exports = module.exports;
|
||||
return createDynamicModule([], ['default'], url, (reflect) => {
|
||||
reflect.exports.default.set(exports);
|
||||
});
|
||||
return new ModuleWrap(function() {
|
||||
this.setExport('default', exports);
|
||||
}, ['default'], url);
|
||||
}
|
||||
}
|
||||
const content = `${await getSource(url)}`;
|
||||
@ -158,9 +156,9 @@ translators.set('json', async function jsonStrategy(url) {
|
||||
module = CJSModule._cache[modulePath];
|
||||
if (module && module.loaded) {
|
||||
const exports = module.exports;
|
||||
return createDynamicModule(['default'], url, (reflect) => {
|
||||
reflect.exports.default.set(exports);
|
||||
});
|
||||
return new ModuleWrap(function() {
|
||||
this.setExport('default', exports);
|
||||
}, ['default'], url);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@ -180,10 +178,10 @@ translators.set('json', async function jsonStrategy(url) {
|
||||
if (pathname) {
|
||||
CJSModule._cache[modulePath] = module;
|
||||
}
|
||||
return createDynamicModule([], ['default'], url, (reflect) => {
|
||||
return new ModuleWrap(function() {
|
||||
debug(`Parsing JSONModule ${url}`);
|
||||
reflect.exports.default.set(module.exports);
|
||||
});
|
||||
this.setExport('default', module.exports);
|
||||
}, ['default'], url);
|
||||
});
|
||||
|
||||
// Strategy for loading a wasm module
|
||||
@ -206,5 +204,5 @@ translators.set('wasm', async function(url) {
|
||||
const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
|
||||
for (const expt of Object.keys(exports))
|
||||
reflect.exports[expt].set(exports[expt]);
|
||||
});
|
||||
}).module;
|
||||
});
|
||||
|
@ -98,6 +98,9 @@ ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) {
|
||||
return module_wrap_it->second;
|
||||
}
|
||||
|
||||
// new ModuleWrap(source, url)
|
||||
// new ModuleWrap(source, url, context?, lineOffset, columnOffset)
|
||||
// new ModuleWrap(syntheticExecutionFunction, export_names, url)
|
||||
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
@ -108,12 +111,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
const int argc = args.Length();
|
||||
CHECK_GE(argc, 2);
|
||||
|
||||
CHECK(args[0]->IsString());
|
||||
Local<String> source_text = args[0].As<String>();
|
||||
|
||||
CHECK(args[1]->IsString());
|
||||
Local<String> url = args[1].As<String>();
|
||||
|
||||
Local<Context> context;
|
||||
Local<Integer> line_offset;
|
||||
Local<Integer> column_offset;
|
||||
@ -143,8 +140,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
column_offset = Integer::New(isolate, 0);
|
||||
}
|
||||
|
||||
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
|
||||
TryCatchScope try_catch(env);
|
||||
Local<String> url;
|
||||
Local<Module> module;
|
||||
|
||||
Local<PrimitiveArray> host_defined_options =
|
||||
@ -152,29 +148,60 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
host_defined_options->Set(isolate, HostDefinedOptions::kType,
|
||||
Number::New(isolate, ScriptType::kModule));
|
||||
|
||||
// compile
|
||||
{
|
||||
ScriptOrigin origin(url,
|
||||
line_offset, // line offset
|
||||
column_offset, // column offset
|
||||
True(isolate), // is cross origin
|
||||
Local<Integer>(), // script id
|
||||
Local<Value>(), // source map URL
|
||||
False(isolate), // is opaque (?)
|
||||
False(isolate), // is WASM
|
||||
True(isolate), // is ES Module
|
||||
host_defined_options);
|
||||
Context::Scope context_scope(context);
|
||||
ScriptCompiler::Source source(source_text, origin);
|
||||
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
||||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
||||
CHECK(!try_catch.Message().IsEmpty());
|
||||
CHECK(!try_catch.Exception().IsEmpty());
|
||||
AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(),
|
||||
ErrorHandlingMode::MODULE_ERROR);
|
||||
try_catch.ReThrow();
|
||||
// new ModuleWrap(syntheticExecutionFunction, export_names, url)
|
||||
bool synthetic = args[0]->IsFunction();
|
||||
if (synthetic) {
|
||||
CHECK(args[1]->IsArray());
|
||||
Local<Array> export_names_arr = args[1].As<Array>();
|
||||
|
||||
uint32_t len = export_names_arr->Length();
|
||||
std::vector<Local<String>> export_names(len);
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
Local<Value> export_name_val =
|
||||
export_names_arr->Get(context, i).ToLocalChecked();
|
||||
CHECK(export_name_val->IsString());
|
||||
export_names[i] = export_name_val.As<String>();
|
||||
}
|
||||
|
||||
CHECK(args[2]->IsString());
|
||||
url = args[2].As<String>();
|
||||
|
||||
module = Module::CreateSyntheticModule(isolate, url, export_names,
|
||||
SyntheticModuleEvaluationStepsCallback);
|
||||
// Compile
|
||||
} else {
|
||||
CHECK(args[0]->IsString());
|
||||
Local<String> source_text = args[0].As<String>();
|
||||
|
||||
CHECK(args[1]->IsString());
|
||||
url = args[1].As<String>();
|
||||
|
||||
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
|
||||
TryCatchScope try_catch(env);
|
||||
|
||||
{
|
||||
ScriptOrigin origin(url,
|
||||
line_offset, // line offset
|
||||
column_offset, // column offset
|
||||
True(isolate), // is cross origin
|
||||
Local<Integer>(), // script id
|
||||
Local<Value>(), // source map URL
|
||||
False(isolate), // is opaque (?)
|
||||
False(isolate), // is WASM
|
||||
True(isolate), // is ES Module
|
||||
host_defined_options);
|
||||
Context::Scope context_scope(context);
|
||||
ScriptCompiler::Source source(source_text, origin);
|
||||
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
||||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
||||
CHECK(!try_catch.Message().IsEmpty());
|
||||
CHECK(!try_catch.Exception().IsEmpty());
|
||||
AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(),
|
||||
ErrorHandlingMode::MODULE_ERROR);
|
||||
try_catch.ReThrow();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,6 +210,13 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
|
||||
|
||||
if (synthetic) {
|
||||
obj->synthetic_ = true;
|
||||
obj->synthetic_evaluation_steps_.Reset(
|
||||
env->isolate(), args[0].As<Function>());
|
||||
}
|
||||
|
||||
obj->context_.Reset(isolate, context);
|
||||
|
||||
env->hash_to_module_map.emplace(module->GetIdentityHash(), obj);
|
||||
@ -307,6 +341,10 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
||||
result = module->Evaluate(context);
|
||||
}
|
||||
|
||||
if (result.IsEmpty()) {
|
||||
CHECK(try_catch.HasCaught());
|
||||
}
|
||||
|
||||
// Convert the termination exception into a regular exception.
|
||||
if (timed_out || received_signal) {
|
||||
if (!env->is_main_thread() && env->is_stopping())
|
||||
@ -1295,7 +1333,7 @@ static MaybeLocal<Promise> ImportModuleDynamically(
|
||||
Local<Value> result;
|
||||
if (import_callback->Call(
|
||||
context,
|
||||
v8::Undefined(iso),
|
||||
Undefined(iso),
|
||||
arraysize(import_args),
|
||||
import_args).ToLocal(&result)) {
|
||||
CHECK(result->IsPromise());
|
||||
@ -1355,6 +1393,52 @@ void ModuleWrap::SetInitializeImportMetaObjectCallback(
|
||||
HostInitializeImportMetaObjectCallback);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback(
|
||||
Local<Context> context, Local<Module> module) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
ModuleWrap* obj = GetFromModule(env, module);
|
||||
|
||||
TryCatchScope try_catch(env);
|
||||
Local<Function> synthetic_evaluation_steps =
|
||||
obj->synthetic_evaluation_steps_.Get(isolate);
|
||||
MaybeLocal<Value> ret = synthetic_evaluation_steps->Call(context,
|
||||
obj->object(), 0, nullptr);
|
||||
if (ret.IsEmpty()) {
|
||||
CHECK(try_catch.HasCaught());
|
||||
}
|
||||
obj->synthetic_evaluation_steps_.Reset();
|
||||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
||||
CHECK(!try_catch.Message().IsEmpty());
|
||||
CHECK(!try_catch.Exception().IsEmpty());
|
||||
try_catch.ReThrow();
|
||||
return MaybeLocal<Value>();
|
||||
}
|
||||
return Undefined(isolate);
|
||||
}
|
||||
|
||||
void ModuleWrap::SetSyntheticExport(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Object> that = args.This();
|
||||
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
|
||||
|
||||
CHECK(obj->synthetic_);
|
||||
|
||||
CHECK_EQ(args.Length(), 2);
|
||||
|
||||
CHECK(args[0]->IsString());
|
||||
Local<String> export_name = args[0].As<String>();
|
||||
|
||||
Local<Value> export_value = args[1];
|
||||
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
module->SetSyntheticModuleExport(export_name, export_value);
|
||||
}
|
||||
|
||||
void ModuleWrap::Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
@ -1369,6 +1453,7 @@ void ModuleWrap::Initialize(Local<Object> target,
|
||||
env->SetProtoMethod(tpl, "link", Link);
|
||||
env->SetProtoMethod(tpl, "instantiate", Instantiate);
|
||||
env->SetProtoMethod(tpl, "evaluate", Evaluate);
|
||||
env->SetProtoMethod(tpl, "setExport", SetSyntheticExport);
|
||||
env->SetProtoMethodNoSideEffect(tpl, "getNamespace", GetNamespace);
|
||||
env->SetProtoMethodNoSideEffect(tpl, "getStatus", GetStatus);
|
||||
env->SetProtoMethodNoSideEffect(tpl, "getError", GetError);
|
||||
|
@ -69,12 +69,19 @@ class ModuleWrap : public BaseObject {
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetInitializeImportMetaObjectCallback(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static v8::MaybeLocal<v8::Value> SyntheticModuleEvaluationStepsCallback(
|
||||
v8::Local<v8::Context> context, v8::Local<v8::Module> module);
|
||||
static void SetSyntheticExport(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static v8::MaybeLocal<v8::Module> ResolveCallback(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::String> specifier,
|
||||
v8::Local<v8::Module> referrer);
|
||||
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
|
||||
|
||||
v8::Global<v8::Function> synthetic_evaluation_steps_;
|
||||
bool synthetic_ = false;
|
||||
v8::Global<v8::Module> module_;
|
||||
v8::Global<v8::String> url_;
|
||||
bool linked_ = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user