Skip to content

Commit

Permalink
src,lib: implement import.meta
Browse files Browse the repository at this point in the history
Implement the C++ callback that is required to configure the
`import.meta` object and add one property:
- url: absolute URL of the module

PR-URL: nodejs#18368
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
  • Loading branch information
targos authored and BridgeAR committed Feb 1, 2018
1 parent 02b3bbc commit 9e1a6f8
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 12 deletions.
7 changes: 6 additions & 1 deletion doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
process.emitWarning(
'The ESM module loader is experimental.',
'ExperimentalWarning', undefined);
NativeModule.require('internal/process/modules').setup();
}


Expand Down
17 changes: 17 additions & 0 deletions lib/internal/process/modules.js
Original file line number Diff line number Diff line change
@@ -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
};
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class ModuleWrap;
V(type_string, "type") \
V(uid_string, "uid") \
V(unknown_string, "<unknown>") \
V(url_string, "url") \
V(user_string, "user") \
V(username_string, "username") \
V(valid_from_string, "valid_from") \
Expand Down Expand Up @@ -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) \
Expand Down
64 changes: 53 additions & 11 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
Expand Down Expand Up @@ -64,6 +65,19 @@ ModuleWrap::~ModuleWrap() {
context_.Reset();
}

ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
Local<Module> 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand Down Expand Up @@ -133,9 +147,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
}
}

Local<String> 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;
}

Expand Down Expand Up @@ -361,14 +373,7 @@ MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
return MaybeLocal<Module>();
}

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");
Expand Down Expand Up @@ -728,6 +733,40 @@ void ModuleWrap::SetImportModuleDynamicallyCallback(
iso->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically);
}

void ModuleWrap::HostInitializeImportMetaObjectCallback(
Local<Context> context, Local<Module> module, Local<Object> meta) {
Isolate* isolate = context->GetIsolate();
Environment* env = Environment::GetCurrent(context);
ModuleWrap* module_wrap = ModuleWrap::GetFromModule(env, module);

if (module_wrap == nullptr) {
return;
}

Local<Object> wrap = module_wrap->object();
Local<Function> callback =
env->host_initialize_import_meta_object_callback();
Local<Value> args[] = { wrap, meta };
callback->Call(context, Undefined(isolate), arraysize(args), args)
.ToLocalChecked();
}

void ModuleWrap::SetInitializeImportMetaObjectCallback(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
if (!args[0]->IsFunction()) {
env->ThrowError("first argument is not a function");
return;
}

Local<Function> import_meta_callback = args[0].As<Function>();
env->set_host_initialize_import_meta_object_callback(import_meta_callback);

isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback);
}

void ModuleWrap::Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
Expand All @@ -752,6 +791,9 @@ void ModuleWrap::Initialize(Local<Object> target,
env->SetMethod(target,
"setImportModuleDynamicallyCallback",
node::loader::ModuleWrap::SetImportModuleDynamicallyCallback);
env->SetMethod(target,
"setInitializeImportMetaObjectCallback",
ModuleWrap::SetInitializeImportMetaObjectCallback);

#define V(name) \
target->Set(context, \
Expand Down
8 changes: 8 additions & 0 deletions src/module_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class ModuleWrap : public BaseObject {
static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context);
static void HostInitializeImportMetaObjectCallback(
v8::Local<v8::Context> context,
v8::Local<v8::Module> module,
v8::Local<v8::Object> meta);

private:
ModuleWrap(Environment* env,
Expand All @@ -44,10 +48,14 @@ class ModuleWrap : public BaseObject {
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetImportModuleDynamicallyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetInitializeImportMetaObjectCallback(
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::Persistent<v8::Module> module_;
v8::Persistent<v8::String> url_;
Expand Down
2 changes: 2 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 22 additions & 0 deletions test/es-module/test-esm-import-meta.mjs
Original file line number Diff line number Diff line change
@@ -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));

0 comments on commit 9e1a6f8

Please sign in to comment.