summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/internal/bootstrap/loaders.js30
-rw-r--r--lib/internal/modules/cjs/loader.js28
-rw-r--r--lib/internal/modules/esm/loader.js9
-rw-r--r--lib/internal/modules/esm/module_job.js6
-rw-r--r--lib/internal/modules/esm/translators.js42
-rw-r--r--src/module_wrap.cc147
-rw-r--r--src/module_wrap.h7
7 files changed, 173 insertions, 96 deletions
diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js
index dbc21a9697..0cad5209c4 100644
--- a/lib/internal/bootstrap/loaders.js
+++ b/lib/internal/bootstrap/loaders.js
@@ -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));
}
}
};
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 862b149e5a..479044a26a 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -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'));
}
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index 9800e8a550..138cf8b5ec 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -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))
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 6f265ed460..ef11e2ec83 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -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)
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index e8eddcfd21..b4d41685ed 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -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;
});
diff --git a/src/module_wrap.cc b/src/module_wrap.cc
index 2d0829860c..67e0cbc618 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -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);
diff --git a/src/module_wrap.h b/src/module_wrap.h
index 9e2aa3b948..ef20d255e9 100644
--- a/src/module_wrap.h
+++ b/src/module_wrap.h
@@ -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;