Implement ImportMeta callback

The first time `import.meta` is called within a module, this callback is called
and we can populate it with whatever fields we want. For WebAPI, the important
field is `url`:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta

Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/80
This commit is contained in:
Karl Seguin
2025-07-01 15:59:24 +08:00
parent f5a58c1ff0
commit b50b96bd1d
5 changed files with 56 additions and 9 deletions

View File

@@ -13,8 +13,8 @@
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd", .hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
}, },
.v8 = .{ .v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/23718cdb99aaa45105d0a7e0ca22587bf77d3b5f.tar.gz", .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/46ddf8c0a2861a4e5717e6f8d0d81a17e42fa0c9.tar.gz",
.hash = "v8-0.0.0-xddH68m2AwDf42Qp2Udz4wTMVH8p71si7yLlUoZkPeEz", .hash = "v8-0.0.0-xddH6865AwDiDnu-HjMsqm9wXvP9OZOh_4clh_67iQsq",
}, },
//.v8 = .{ .path = "../zig-v8-fork" }, //.v8 = .{ .path = "../zig-v8-fork" },
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" }, //.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },

View File

@@ -1001,7 +1001,11 @@ const Script = struct {
try_catch.init(page.main_context); try_catch.init(page.main_context);
defer try_catch.deinit(); defer try_catch.deinit();
const src = self.src orelse page.url.raw; const src: []const u8 = blk: {
const s = self.src orelse break :blk page.url.raw;
break :blk try URL.stitch(page.arena, s, page.url.raw, .{.alloc = .if_needed});
};
// if self.src is null, then this is an inline script, and it should // if self.src is null, then this is an inline script, and it should
// not be cached. // not be cached.
const cacheable = self.src != null; const cacheable = self.src != null;

View File

@@ -580,7 +580,7 @@ fn parseCommonArg(
var it = std.mem.splitScalar(u8, str, ','); var it = std.mem.splitScalar(u8, str, ',');
while (it.next()) |part| { while (it.next()) |part| {
try arr.append(allocator, std.meta.stringToEnum(log.Scope, part) orelse { try arr.append(allocator, std.meta.stringToEnum(log.Scope, part) orelse {
log.fatal(.app, "invalid option choice", .{ .arg = "--log_scope_filter", .value = part }); log.fatal(.app, "invalid option choice", .{ .arg = "--log_filter_scopes", .value = part });
return false; return false;
}); });
} }

View File

@@ -197,6 +197,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
isolate.enter(); isolate.enter();
errdefer isolate.exit(); errdefer isolate.exit();
isolate.setHostInitializeImportMetaObjectCallback(struct {
fn callback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_meta: ?*v8.C_Value) callconv(.C) void {
const v8_context = v8.Context{.handle = c_context.?};
const js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
js_context.initializeImportMeta(v8.Module{.handle = c_module.?}, v8.Object{.handle = c_meta.?}) catch |err| {
log.err(.js, "import meta", .{ .err = err });
};
}
}.callback);
var temp_scope: v8.HandleScope = undefined; var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate); v8.HandleScope.init(&temp_scope, isolate);
defer temp_scope.deinit(); defer temp_scope.deinit();
@@ -726,13 +736,15 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
fn moduleNoCache(self: *JsContext, src: []const u8, url: []const u8) !void { fn moduleNoCache(self: *JsContext, src: []const u8, url: []const u8) !void {
const m = try compileModule(self.isolate, src, url); const m = try compileModule(self.isolate, src, url);
const arena = self.context_arena;
const owned_url = try arena.dupe(u8, url);
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
const v8_context = self.v8_context; const v8_context = self.v8_context;
if (try m.instantiate(v8_context, resolveModuleCallback) == false) { if (try m.instantiate(v8_context, resolveModuleCallback) == false) {
return error.ModuleInstantiationError; return error.ModuleInstantiationError;
} }
const arena = self.context_arena;
const owned_url = try arena.dupe(u8, url);
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
_ = try m.evaluate(v8_context); _ = try m.evaluate(v8_context);
} }
@@ -1279,6 +1291,20 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return self.createException(js_value); return self.createException(js_value);
} }
fn initializeImportMeta(self: *JsContext, m: v8.Module, meta: v8.Object) !void {
const url = self.module_identifier.get(m.getIdentityHash()) orelse {
// Shouldn't be possible.
return error.UnknownModuleReferrer;
};
const js_key = v8.String.initUtf8(self.isolate, "url");
const js_value = try self.zigValueToJs(url);
const res = meta.defineOwnProperty(self.v8_context, js_key.toName(), js_value, 0) orelse false;
if (!res) {
return error.FailedToSet;
}
}
// Callback from V8, asking us to load a module. The "specifier" is // Callback from V8, asking us to load a module. The "specifier" is
// the src of the module to load. // the src of the module to load.
fn resolveModuleCallback( fn resolveModuleCallback(
@@ -1335,7 +1361,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
try_catch.init(self); try_catch.init(self);
defer try_catch.deinit(); defer try_catch.deinit();
const m = compileModule(self.isolate, source, specifier) catch |err| { const m = compileModule(self.isolate, source, normalized_specifier) catch |err| {
log.warn(.js, "compile resolved module", .{ log.warn(.js, "compile resolved module", .{
.specifier = specifier, .specifier = specifier,
.stack = try_catch.stack(self.call_arena) catch null, .stack = try_catch.stack(self.call_arena) catch null,

View File

@@ -111,7 +111,7 @@ pub const URL = struct {
return src; return src;
} }
var normalized_src = if (std.mem.startsWith(u8, src, "/")) src[1..] else src; var normalized_src = src;
while (std.mem.startsWith(u8, normalized_src, "./")) { while (std.mem.startsWith(u8, normalized_src, "./")) {
normalized_src = normalized_src[2..]; normalized_src = normalized_src[2..];
} }
@@ -131,6 +131,13 @@ pub const URL = struct {
} }
}; };
if (normalized_src[0] == '/') {
if (std.mem.indexOfScalarPos(u8, base, protocol_end, '/')) |pos| {
return std.fmt.allocPrint(allocator, "{s}{s}", .{ base[0..pos], normalized_src });
}
// not sure what to do here...error? Just let it fallthrough for now.
}
if (std.mem.lastIndexOfScalar(u8, base[protocol_end..], '/')) |index| { if (std.mem.lastIndexOfScalar(u8, base[protocol_end..], '/')) |index| {
const last_slash_pos = index + protocol_end; const last_slash_pos = index + protocol_end;
if (last_slash_pos == base.len - 1) { if (last_slash_pos == base.len - 1) {
@@ -257,6 +264,16 @@ test "URL: Stitching Base & Src URLs (No Ending Slash)" {
try testing.expectString("https://lightpanda.io/something.js", result); try testing.expectString("https://lightpanda.io/something.js", result);
} }
test "URL: Stitching Base with absolute src" {
const allocator = testing.allocator;
const base = "https://lightpanda.io/hello";
const src = "/abc/something.js";
const result = try URL.stitch(allocator, src, base, .{});
defer allocator.free(result);
try testing.expectString("https://lightpanda.io/abc/something.js", result);
}
test "URL: Stiching Base & Src URLs (Both Local)" { test "URL: Stiching Base & Src URLs (Both Local)" {
const allocator = testing.allocator; const allocator = testing.allocator;