Merge pull request #2541 from lightpanda-io/import_meta.resolve

implement import.meta.resolve
This commit is contained in:
Pierre Tachoire
2026-05-26 10:04:00 +02:00
committed by GitHub
2 changed files with 74 additions and 1 deletions

View File

@@ -610,7 +610,6 @@ pub fn dynamicModuleCallback(
}
pub fn metaObjectCallback(c_context: ?*v8.Context, c_module: ?*v8.Module, c_meta: ?*v8.Value) callconv(.c) void {
// @HandleScope implement this without a fat context/local..
const self = fromC(c_context.?).?;
var local = js.Local{
.ctx = self,
@@ -636,6 +635,66 @@ pub fn metaObjectCallback(c_context: ?*v8.Context, c_module: ?*v8.Module, c_meta
if (!res) {
log.err(.js, "import meta", .{ .err = error.FailedToSet });
}
// import.meta.resolve(specifier) resolves against this module's URL,
// applying the document's importmap. The base is bound per-module so the
// function keeps working even when detached from import.meta.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve
const resolve_data = self.arena.create(ImportMetaResolveData) catch {
log.err(.js, "import meta", .{ .err = error.OutOfMemory });
return;
};
resolve_data.* = .{ .context = self, .base = url };
const resolve_fn = newFunctionWithData(&local, importMetaResolveCallback, @ptrCast(resolve_data));
const resolve_value = js.Value{ .local = &local, .handle = @ptrCast(resolve_fn.handle) };
const resolve_res = meta.defineOwnProperty("resolve", resolve_value, 0) orelse false;
if (!resolve_res) {
log.err(.js, "import meta", .{ .err = error.FailedToSet });
}
}
const ImportMetaResolveData = struct {
context: *Context,
base: [:0]const u8,
};
// Implements import.meta.resolve(specifier): resolves the specifier against the
// module's base URL (applying the document's importmap) and returns the
// absolute URL.
fn importMetaResolveCallback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
var c: Caller = undefined;
if (!c.initFromHandle(callback_handle)) {
return;
}
defer c.deinit();
const l = &c.local;
const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
const data: *ImportMetaResolveData = @ptrCast(@alignCast(info.getData() orelse return));
const ctx = data.context;
const isolate = ctx.isolate;
if (info.length() == 0) {
_ = isolate.throwException(isolate.createTypeError("import.meta.resolve requires a specifier"));
return;
}
const specifier = info.getArg(0, l).toStringSliceZ() catch {
_ = isolate.throwException(isolate.createTypeError("invalid specifier"));
return;
};
const resolved = ctx.script_manager.resolveSpecifier(ctx.call_arena, data.base, specifier) catch {
_ = isolate.throwException(isolate.createTypeError("failed to resolve module specifier"));
return;
};
const result = l.zigValueToJs(resolved, .{}) catch {
_ = isolate.throwException(isolate.createTypeError("failed to resolve module specifier"));
return;
};
info.getReturnValue().set(result);
}
fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]const u8, local: *const js.Local) !?*const v8.Module {

View File

@@ -5,6 +5,20 @@
testing.expectEqual('/src/browser/tests/page/module.html', new URL(import.meta.url).pathname)
</script>
<script id=meta-resolve type=module>
// Resolves relative to this module's URL.
testing.expectEqual('/src/browser/tests/page/mod1.js', new URL(import.meta.resolve('./mod1.js')).pathname);
testing.expectEqual('/src/browser/tests/page/modules/importer.js', new URL(import.meta.resolve('./modules/importer.js')).pathname);
testing.expectEqual('/src/browser/tests/testing.js', new URL(import.meta.resolve('../testing.js')).pathname);
</script>
<script id=meta-resolve-detached type=module>
// The base is bound to the module, so resolve still works when detached
// from import.meta.
const resolve = import.meta.resolve;
testing.expectEqual('/src/browser/tests/page/mod1.js', new URL(resolve('./mod1.js')).pathname, {script_id: 'meta-resolve-detached'});
</script>
<script id=basic-import type=module>
import { "val1" as val1 } from "./mod1.js";
testing.expectEqual('value-1', val1);