From 21ce2c5606df86a08b073f975825bd7b208e0c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sun, 24 Dec 2017 16:26:24 +0100 Subject: [PATCH] src,lib: implement import.meta Implement the C++ callback that is required to configure the `import.meta` object and add one property: - url: absolute URL of the module --- doc/api/esm.md | 7 ++- lib/internal/bootstrap_node.js | 1 + lib/internal/process/modules.js | 17 +++++++ node.gyp | 1 + src/env.h | 2 + src/module_wrap.cc | 64 ++++++++++++++++++++----- src/module_wrap.h | 8 ++++ src/node.cc | 2 + test/es-module/test-esm-import-meta.mjs | 22 +++++++++ 9 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 lib/internal/process/modules.js create mode 100644 test/es-module/test-esm-import-meta.mjs 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 e269d7dc153a4d..a90ec3abee18a2 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -104,6 +104,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 d73be8156ecebf..87cf27c2aecd69 100644 --- a/src/env.h +++ b/src/env.h @@ -249,6 +249,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") \ @@ -278,6 +279,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 e86e48060c3ce8..4c9868980f00c4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3680,6 +3680,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));