Skip to content
23 changes: 14 additions & 9 deletions benchmark/esm/import-meta.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
'use strict';

const path = require('path');
const { pathToFileURL, fileURLToPath } = require('url');
const { pathToFileURL } = require('url');
const common = require('../common');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [1000],
valuesToRead: [
'dirname-and-filename',
'dirname',
'filename',
],
});

const file = pathToFileURL(
path.resolve(__filename, '../../fixtures/esm-dir-file.mjs'),
);
async function load(array, n) {
const fixtureDir = path.resolve(__filename, '../../fixtures');
const fixtureDirURL = pathToFileURL(fixtureDir);
async function load(array, n, valuesToRead) {
for (let i = 0; i < n; i++) {
array[i] = await import(`${file}?i=${i}`);
array[i] = await import(`${fixtureDirURL}/import-meta-${valuesToRead}.mjs?i=${i}`);
}
return array;
}

function main({ n }) {
function main({ n, valuesToRead }) {
const array = [];
for (let i = 0; i < n; ++i) {
array.push({ dirname: '', filename: '', i: 0 });
}

bench.start();
load(array, n).then((arr) => {
load(array, n, valuesToRead).then((arr) => {
bench.end(n);
assert.strictEqual(arr[n - 1].filename, fileURLToPath(file));
if (valuesToRead.includes('dirname')) assert.strictEqual(arr[n - 1].dirname, fixtureDir);
if (valuesToRead.includes('filename')) assert.strictEqual(arr[n - 1].filename, path.join(fixtureDir, `import-meta-${valuesToRead}.mjs`));
});
}
1 change: 1 addition & 0 deletions benchmark/fixtures/import-meta-dirname.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dirname = import.meta.dirname;
1 change: 1 addition & 0 deletions benchmark/fixtures/import-meta-filename.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const filename = import.meta.filename;
14 changes: 7 additions & 7 deletions lib/internal/modules/esm/initialize_import_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ const {
} = primordials;

const { getOptionValue } = require('internal/options');
const { fileURLToPath } = require('internal/url');
const { dirname } = require('path');
const {
setLazyPathHelpers,
} = internalBinding('modules');

const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve');

/**
Expand Down Expand Up @@ -58,11 +60,9 @@ function initializeImportMeta(meta, context, loader) {

// Alphabetical
if (StringPrototypeStartsWith(url, 'file:') === true) {
// These only make sense for locally loaded modules,
// i.e. network modules are not supported.
const filePath = fileURLToPath(url);
meta.dirname = dirname(filePath);
meta.filename = filePath;
// dirname
// filename
setLazyPathHelpers(meta, url);
}

if (!loader || loader.allowImportMetaResolve) {
Expand Down
2 changes: 2 additions & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
V(destroyed_string, "destroyed") \
V(detached_string, "detached") \
V(dh_string, "DH") \
V(dirname_string, "dirname") \
V(divisor_length_string, "divisorLength") \
V(dns_a_string, "A") \
V(dns_aaaa_string, "AAAA") \
Expand Down Expand Up @@ -335,6 +336,7 @@
"export * from 'original'; export { default } from 'original'; export " \
"const __esModule = true;") \
V(require_string, "require") \
V(resolve_string, "resolve") \
V(resource_string, "resource") \
V(result_string, "result") \
V(retry_string, "retry") \
Expand Down
73 changes: 73 additions & 0 deletions src/node_modules.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ using v8::Null;
using v8::Object;
using v8::ObjectTemplate;
using v8::Primitive;
using v8::PropertyCallbackInfo;
using v8::String;
using v8::Undefined;
using v8::Value;
Expand Down Expand Up @@ -594,6 +595,76 @@ void GetCompileCacheEntry(const FunctionCallbackInfo<Value>& args) {
isolate, v8::Null(isolate), names.data(), values.data(), names.size()));
}

static void PathHelpersLazyGetter(Local<v8::Name> name,
const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
// This getter has no JavaScript function representation and is not
// invoked in the creation context.
// When this getter is invoked in a vm context, the `Realm::GetCurrent(info)`
// returns a nullptr and retrieve the creation context via `this` object and
// get the creation Realm.
Local<Value> receiver_val = info.This();
if (!receiver_val->IsObject()) {
THROW_ERR_INVALID_INVOCATION(isolate);
return;
}
Local<Object> receiver = receiver_val.As<Object>();
Local<Context> context;
if (!receiver->GetCreationContext().ToLocal(&context)) {
THROW_ERR_INVALID_INVOCATION(isolate);
return;
}
Environment* env = Environment::GetCurrent(context);

node::Utf8Value url(isolate, info.Data());
auto file_url = ada::parse(url.ToStringView());
CHECK(file_url);
auto file_path = url::FileURLToPath(env, *file_url);
CHECK(file_path.has_value());
std::string_view ret_view = file_path.value();

node::Utf8Value utf8name(isolate, name);
auto plain_name = utf8name.ToStringView();
if (plain_name == "dirname") {
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
auto index = ret_view.rfind(PATH_SEPARATOR);
CHECK(index != std::string_view::npos);
ret_view.remove_suffix(ret_view.size() - index);
#undef PATH_SEPARATOR
}
Local<Value> ret;
if (!ToV8Value(context, ret_view, isolate).ToLocal(&ret)) {
return;
}
info.GetReturnValue().Set(ret);
}
void InitImportMetaPathHelpers(const FunctionCallbackInfo<Value>& args) {
// target, url, shouldSetDirnameAndFilename, resolve
CHECK_GE(args.Length(), 2);
CHECK(args[0]->IsObject());
CHECK(args[1]->IsString());

Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);

auto target = args[0].As<Object>();

// N.B.: Order is important to keep keys in alphabetical order.
if (target
->SetLazyDataProperty(
context, env->dirname_string(), PathHelpersLazyGetter, args[1])
.IsNothing() ||
target
->SetLazyDataProperty(
context, env->filename_string(), PathHelpersLazyGetter, args[1])
.IsNothing())
return;
}
void SaveCompileCacheEntry(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Expand Down Expand Up @@ -627,6 +698,7 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "flushCompileCache", FlushCompileCache);
SetMethod(isolate, target, "getCompileCacheEntry", GetCompileCacheEntry);
SetMethod(isolate, target, "saveCompileCacheEntry", SaveCompileCacheEntry);
SetMethod(isolate, target, "setLazyPathHelpers", InitImportMetaPathHelpers);
}

void BindingData::CreatePerContextProperties(Local<Object> target,
Expand Down Expand Up @@ -682,6 +754,7 @@ void BindingData::RegisterExternalReferences(
registry->Register(FlushCompileCache);
registry->Register(GetCompileCacheEntry);
registry->Register(SaveCompileCacheEntry);
registry->Register(InitImportMetaPathHelpers);
}

} // namespace modules
Expand Down
Loading