diff --git a/doc/api/esm.md b/doc/api/esm.md index 2a37040798d209..10f20958593b59 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -36,12 +36,17 @@ Only the CLI argument for the main entry point to the program can be an entry point into an ESM graph. Dynamic import can also be used to create entry points into ESM graphs at runtime. +#### `import.meta` + +The `import.meta` metaproperty is an `Object` that contains the following +property: +* `url` {string} The absolute `file:` URL of the module + ### Unsupported | Feature | Reason | | --- | --- | | `require('./foo.mjs')` | ES Modules have differing resolution and timing, use dynamic import | -| `import.meta` | pending V8 implementation | ## Notable differences between `import` and `require` diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index c08e708164152b..33de5ae4be2f0d 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -100,6 +100,7 @@ process.emitWarning( 'The ESM module loader is experimental.', 'ExperimentalWarning', undefined); + NativeModule.require('internal/process/modules').setup(); } diff --git a/lib/internal/process/modules.js b/lib/internal/process/modules.js new file mode 100644 index 00000000000000..eda47f80cddeb4 --- /dev/null +++ b/lib/internal/process/modules.js @@ -0,0 +1,17 @@ +'use strict'; + +const { + setInitializeImportMetaObjectCallback +} = internalBinding('module_wrap'); + +function initializeImportMetaObject(wrap, meta) { + meta.url = wrap.url; +} + +function setupModules() { + setInitializeImportMetaObjectCallback(initializeImportMetaObject); +} + +module.exports = { + setup: setupModules +}; diff --git a/node.gyp b/node.gyp index a781cbf1c0f167..33642e90ac91e2 100644 --- a/node.gyp +++ b/node.gyp @@ -112,6 +112,7 @@ 'lib/internal/net.js', 'lib/internal/module.js', 'lib/internal/os.js', + 'lib/internal/process/modules.js', 'lib/internal/process/next_tick.js', 'lib/internal/process/promises.js', 'lib/internal/process/stdio.js', diff --git a/src/env.h b/src/env.h index 734261359565c4..79fc848386cc82 100644 --- a/src/env.h +++ b/src/env.h @@ -250,6 +250,7 @@ class ModuleWrap; V(type_string, "type") \ V(uid_string, "uid") \ V(unknown_string, "") \ + V(url_string, "url") \ V(user_string, "user") \ V(username_string, "username") \ V(valid_from_string, "valid_from") \ @@ -279,6 +280,7 @@ class ModuleWrap; V(context, v8::Context) \ V(domain_callback, v8::Function) \ V(host_import_module_dynamically_callback, v8::Function) \ + V(host_initialize_import_meta_object_callback, v8::Function) \ V(http2ping_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2settings_constructor_template, v8::ObjectTemplate) \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index daa7f9036a5abb..0fda1250d701c0 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -37,6 +37,7 @@ using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::TryCatch; +using v8::Undefined; using v8::Value; static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; @@ -64,6 +65,19 @@ ModuleWrap::~ModuleWrap() { context_.Reset(); } +ModuleWrap* ModuleWrap::GetFromModule(Environment* env, + Local module) { + ModuleWrap* ret = nullptr; + auto range = env->module_map.equal_range(module->GetIdentityHash()); + for (auto it = range.first; it != range.second; ++it) { + if (it->second->module_ == module) { + ret = it->second; + break; + } + } + return ret; +} + void ModuleWrap::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -133,9 +147,7 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { } } - Local url_str = FIXED_ONE_BYTE_STRING(isolate, "url"); - - if (!that->Set(context, url_str, url).FromMaybe(false)) { + if (!that->Set(context, env->url_string(), url).FromMaybe(false)) { return; } @@ -361,14 +373,7 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, return MaybeLocal(); } - ModuleWrap* dependent = nullptr; - auto range = env->module_map.equal_range(referrer->GetIdentityHash()); - for (auto it = range.first; it != range.second; ++it) { - if (it->second->module_ == referrer) { - dependent = it->second; - break; - } - } + ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer); if (dependent == nullptr) { env->ThrowError("linking error, null dep"); @@ -728,6 +733,40 @@ void ModuleWrap::SetImportModuleDynamicallyCallback( iso->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); } +void ModuleWrap::HostInitializeImportMetaObjectCallback( + Local context, Local module, Local meta) { + Isolate* isolate = context->GetIsolate(); + Environment* env = Environment::GetCurrent(context); + ModuleWrap* module_wrap = ModuleWrap::GetFromModule(env, module); + + if (module_wrap == nullptr) { + return; + } + + Local wrap = module_wrap->object(); + Local callback = + env->host_initialize_import_meta_object_callback(); + Local args[] = { wrap, meta }; + callback->Call(context, Undefined(isolate), arraysize(args), args) + .ToLocalChecked(); +} + +void ModuleWrap::SetInitializeImportMetaObjectCallback( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + if (!args[0]->IsFunction()) { + env->ThrowError("first argument is not a function"); + return; + } + + Local import_meta_callback = args[0].As(); + env->set_host_initialize_import_meta_object_callback(import_meta_callback); + + isolate->SetHostInitializeImportMetaObjectCallback( + HostInitializeImportMetaObjectCallback); +} + void ModuleWrap::Initialize(Local target, Local unused, Local context) { @@ -752,6 +791,9 @@ void ModuleWrap::Initialize(Local target, env->SetMethod(target, "setImportModuleDynamicallyCallback", node::loader::ModuleWrap::SetImportModuleDynamicallyCallback); + env->SetMethod(target, + "setInitializeImportMetaObjectCallback", + ModuleWrap::SetInitializeImportMetaObjectCallback); #define V(name) \ target->Set(context, \ diff --git a/src/module_wrap.h b/src/module_wrap.h index bedf665165c8f6..5950c5a1be0177 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -23,6 +23,10 @@ class ModuleWrap : public BaseObject { static void Initialize(v8::Local target, v8::Local unused, v8::Local context); + static void HostInitializeImportMetaObjectCallback( + v8::Local context, + v8::Local module, + v8::Local meta); private: ModuleWrap(Environment* env, @@ -44,10 +48,14 @@ class ModuleWrap : public BaseObject { static void Resolve(const v8::FunctionCallbackInfo& args); static void SetImportModuleDynamicallyCallback( const v8::FunctionCallbackInfo& args); + static void SetInitializeImportMetaObjectCallback( + const v8::FunctionCallbackInfo& args); static v8::MaybeLocal ResolveCallback( v8::Local context, v8::Local specifier, v8::Local referrer); + static ModuleWrap* GetFromModule(node::Environment*, v8::Local); + v8::Persistent module_; v8::Persistent url_; diff --git a/src/node.cc b/src/node.cc index 50bba93f1778c7..6b14169e913d8b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3682,6 +3682,8 @@ static void ParseArgs(int* argc, config_experimental_modules = true; new_v8_argv[new_v8_argc] = "--harmony-dynamic-import"; new_v8_argc += 1; + new_v8_argv[new_v8_argc] = "--harmony-import-meta"; + new_v8_argc += 1; } else if (strcmp(arg, "--experimental-vm-modules") == 0) { config_experimental_vm_modules = true; } else if (strcmp(arg, "--loader") == 0) { diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs new file mode 100644 index 00000000000000..c17e0e20d49b4d --- /dev/null +++ b/test/es-module/test-esm-import-meta.mjs @@ -0,0 +1,22 @@ +// Flags: --experimental-modules + +import '../common'; +import assert from 'assert'; + +assert.strictEqual(Object.getPrototypeOf(import.meta), null); + +const keys = ['url']; +assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys); + +const descriptors = Object.getOwnPropertyDescriptors(import.meta); +for (const descriptor of Object.values(descriptors)) { + delete descriptor.value; // Values are verified below. + assert.deepStrictEqual(descriptor, { + enumerable: true, + writable: true, + configurable: true + }); +} + +const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; +assert(import.meta.url.match(urlReg));