Skip to content

src: port initializeImportMeta to native #57286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 29, 2025
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