Skip to content

Commit 63d5a96

Browse files
committed
add dynamic import callback to isolate
1 parent 29671ac commit 63d5a96

File tree

2 files changed

+201
-2
lines changed

2 files changed

+201
-2
lines changed

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
1414
},
1515
.v8 = .{
16-
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/dd087771378ea854452bcb010309fa9ffe5a9cac.tar.gz",
17-
.hash = "v8-0.0.0-xddH66e8AwBL3O_A8yWQYQIyfMbKHFNVQr_NqM6YjU11",
16+
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/8ce13e38e8bdf09e33960504a1705d7e1e25c621.tar.gz",
17+
.hash = "v8-0.0.0-xddH64G-AwAsLVLCYET7Vn68ELTSEdJNNRnDa-sKdY1q",
1818
},
1919
//.v8 = .{ .path = "../zig-v8-fork" },
2020
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },

src/runtime/js.zig

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,205 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
195195
var isolate = v8.Isolate.init(params);
196196
errdefer isolate.deinit();
197197

198+
// This is the callback that runs whenever a module is dynamically imported.
199+
isolate.setHostImportModuleDynamicallyCallback(struct {
200+
pub fn callback(
201+
v8_ctx: ?*const v8.c.Context,
202+
host_defined_options: ?*const v8.c.Data,
203+
resource_name: ?*const v8.c.Value,
204+
v8_specifier: ?*const v8.c.String,
205+
import_attrs: ?*const v8.c.FixedArray,
206+
) callconv(.c) ?*v8.c.Promise {
207+
_ = host_defined_options;
208+
_ = import_attrs;
209+
const ctx: v8.Context = .{ .handle = v8_ctx.? };
210+
const context: *JsContext = @ptrFromInt(ctx.getEmbedderData(1).castTo(v8.BigInt).getUint64());
211+
const iso = context.isolate;
212+
const resolver = v8.PromiseResolver.init(ctx);
213+
214+
const specifier: v8.String = .{ .handle = v8_specifier.? };
215+
const specifier_str = jsStringToZig(context.call_arena, specifier, iso) catch {
216+
const error_msg = v8.String.initUtf8(iso, "Failed to parse module specifier");
217+
_ = resolver.reject(ctx, error_msg.toValue());
218+
return @constCast(resolver.getPromise().handle);
219+
};
220+
const resource: v8.String = .{ .handle = resource_name.? };
221+
const resource_str = jsStringToZig(context.call_arena, resource, iso) catch {
222+
const error_msg = v8.String.initUtf8(iso, "Failed to parse module resource");
223+
_ = resolver.reject(ctx, error_msg.toValue());
224+
return @constCast(resolver.getPromise().handle);
225+
};
226+
227+
const referrer_full_url = blk: {
228+
// Search through module_identifier values to find matching resource
229+
var it = context.module_identifier.valueIterator();
230+
while (it.next()) |full_url| {
231+
// Extract just the filename from the full URL
232+
const last_slash = std.mem.lastIndexOfScalar(u8, full_url.*, '/') orelse 0;
233+
const filename = full_url.*[last_slash + 1 ..];
234+
235+
// Compare with our resource string (removing ./ prefix if present)
236+
const resource_clean = if (std.mem.startsWith(u8, resource_str, "./"))
237+
resource_str[2..]
238+
else
239+
resource_str;
240+
241+
if (std.mem.eql(u8, filename, resource_clean)) {
242+
break :blk full_url.*;
243+
}
244+
}
245+
246+
// Fallback - maybe it's already a full URL in some cases?
247+
break :blk resource_str;
248+
};
249+
250+
const normalized_specifier = @import("../url.zig").stitch(
251+
context.context_arena,
252+
specifier_str,
253+
referrer_full_url,
254+
.{ .alloc = .if_needed },
255+
) catch unreachable;
256+
257+
// TODO: we need to resolve the full URL here and normalize it.
258+
// That way we can pass the correct one in the module_loader.
259+
260+
log.info(.js, "dynamic import", .{
261+
.specifier = specifier_str,
262+
.resource = resource_str,
263+
.normalized = normalized_specifier,
264+
});
265+
266+
const module_loader = context.module_loader;
267+
const source = module_loader.func(module_loader.ptr, normalized_specifier) catch {
268+
const error_msg = v8.String.initUtf8(iso, "Failed to load module");
269+
_ = resolver.reject(ctx, error_msg.toValue());
270+
return @constCast(resolver.getPromise().handle);
271+
} orelse {
272+
const error_msg = v8.String.initUtf8(iso, "Module source not available");
273+
_ = resolver.reject(ctx, error_msg.toValue());
274+
return @constCast(resolver.getPromise().handle);
275+
};
276+
277+
var try_catch: TryCatch = undefined;
278+
try_catch.init(context);
279+
defer try_catch.deinit();
280+
281+
const module = compileModule(iso, source, specifier_str) catch {
282+
log.err(.js, "module compilation failed", .{
283+
.specifier = specifier_str,
284+
.exception = try_catch.exception(context.call_arena) catch "unknown error",
285+
.stack = try_catch.stack(context.call_arena) catch null,
286+
});
287+
const error_msg = if (try_catch.hasCaught()) blk: {
288+
const exception_str = try_catch.exception(context.call_arena) catch "Compilation error";
289+
break :blk v8.String.initUtf8(iso, exception_str orelse "Compilation error");
290+
} else v8.String.initUtf8(iso, "Module compilation failed");
291+
292+
_ = resolver.reject(ctx, error_msg.toValue());
293+
return @constCast(resolver.getPromise().handle);
294+
};
295+
296+
context.module_identifier.putNoClobber(context.context_arena, module.getIdentityHash(), normalized_specifier) catch unreachable;
297+
context.module_cache.putNoClobber(context.context_arena, normalized_specifier, v8.Persistent(v8.Module).init(iso, module)) catch unreachable;
298+
299+
const instantiated = module.instantiate(ctx, JsContext.resolveModuleCallback) catch {
300+
log.err(.js, "module instantiation failed", .{
301+
.specifier = specifier_str,
302+
.exception = try_catch.exception(context.call_arena) catch "unknown error",
303+
.stack = try_catch.stack(context.call_arena) catch null,
304+
});
305+
const error_msg = if (try_catch.hasCaught()) blk: {
306+
const exception_str = try_catch.exception(context.call_arena) catch "Instantiation error";
307+
break :blk v8.String.initUtf8(iso, exception_str orelse "Instantiation error");
308+
} else v8.String.initUtf8(iso, "Module instantiation failed");
309+
310+
_ = resolver.reject(ctx, error_msg.toValue());
311+
return @constCast(resolver.getPromise().handle);
312+
};
313+
314+
if (!instantiated) {
315+
const error_msg = v8.String.initUtf8(iso, "Module did not instantiate");
316+
_ = resolver.reject(ctx, error_msg.toValue());
317+
return @constCast(resolver.getPromise().handle);
318+
}
319+
320+
const evaluated = module.evaluate(ctx) catch {
321+
log.err(.js, "module evaluation failed", .{
322+
.specifier = specifier_str,
323+
.exception = try_catch.exception(context.call_arena) catch "unknown error",
324+
.stack = try_catch.stack(context.call_arena) catch null,
325+
.line = try_catch.sourceLineNumber() orelse 0,
326+
});
327+
const error_msg = if (try_catch.hasCaught()) blk: {
328+
const exception_str = try_catch.exception(context.call_arena) catch "Evaluation error";
329+
break :blk v8.String.initUtf8(iso, exception_str orelse "Evaluation error");
330+
} else v8.String.initUtf8(iso, "Module evaluation failed");
331+
332+
_ = resolver.reject(ctx, error_msg.toValue());
333+
return @constCast(resolver.getPromise().handle);
334+
};
335+
336+
if (evaluated.isPromise()) {
337+
const promise = v8.Promise{ .handle = evaluated.handle };
338+
339+
const EvaluationData = struct {
340+
module: v8.Persistent(v8.Module),
341+
resolver: v8.Persistent(v8.PromiseResolver),
342+
};
343+
344+
const ev_data = context.context_arena.create(EvaluationData) catch unreachable;
345+
ev_data.* = .{
346+
.module = v8.Persistent(v8.Module).init(iso, module),
347+
.resolver = v8.Persistent(v8.PromiseResolver).init(iso, resolver),
348+
};
349+
const external = v8.External.init(iso, @ptrCast(ev_data));
350+
351+
const then_callback = v8.Function.initWithData(ctx, struct {
352+
pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
353+
const cb_info = v8.FunctionCallbackInfo{ .handle = info.? };
354+
const cb_isolate = cb_info.getIsolate();
355+
const cb_context = cb_isolate.getCurrentContext();
356+
const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue()));
357+
const cb_module = data.module.castToModule();
358+
const cb_resolver = data.resolver.castToPromiseResolver();
359+
360+
const namespace = cb_module.getModuleNamespace();
361+
log.warn(.js, "module then promise", .{
362+
.namespace = namespace,
363+
});
364+
_ = cb_resolver.resolve(cb_context, namespace);
365+
}
366+
}.callback, external);
367+
368+
const catch_callback = v8.Function.initWithData(ctx, struct {
369+
pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
370+
log.warn(.js, "module catch promise", .{});
371+
const cb_info = v8.FunctionCallbackInfo{ .handle = info.? };
372+
const cb_context = cb_info.getIsolate().getCurrentContext();
373+
const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue()));
374+
const cb_resolver = data.resolver.castToPromiseResolver();
375+
_ = cb_resolver.reject(cb_context, cb_info.getData());
376+
}
377+
}.callback, external);
378+
379+
_ = promise.thenAndCatch(ctx, then_callback, catch_callback) catch {
380+
log.err(.js, "module evaluation is promise", .{
381+
.specifier = specifier_str,
382+
.line = try_catch.sourceLineNumber() orelse 0,
383+
});
384+
const error_msg = v8.String.initUtf8(iso, "Evaluation is a promise");
385+
_ = resolver.reject(ctx, error_msg.toValue());
386+
return @constCast(resolver.getPromise().handle);
387+
};
388+
} else {
389+
const namespace = module.getModuleNamespace();
390+
_ = resolver.resolve(ctx, namespace);
391+
}
392+
393+
return @constCast(resolver.getPromise().handle);
394+
}
395+
}.callback);
396+
198397
isolate.enter();
199398
errdefer isolate.exit();
200399

0 commit comments

Comments
 (0)