use module() for dynamic imports

This commit is contained in:
Muki Kiboigo
2025-07-10 11:39:46 -07:00
parent 2a87337875
commit dffd8b5fec
2 changed files with 59 additions and 95 deletions

View File

@@ -1018,12 +1018,16 @@ const Script = struct {
.cacheable = cacheable, .cacheable = cacheable,
}); });
const result = switch (self.kind) { const failed = blk: {
.javascript => page.main_context.eval(body, src), switch (self.kind) {
.module => page.main_context.module(body, src, cacheable), .javascript => _ = page.main_context.eval(body, src) catch break :blk true,
// We don't care about waiting for the evaluation here.
.module => _ = page.main_context.module(body, src, cacheable) catch break :blk true,
}
break :blk false;
}; };
result catch { if (failed) {
if (page.delayed_navigation) { if (page.delayed_navigation) {
return error.Terminated; return error.Terminated;
} }
@@ -1038,7 +1042,8 @@ const Script = struct {
try self.executeCallback("onerror", page); try self.executeCallback("onerror", page);
return error.JsErr; return error.JsErr;
}; }
try self.executeCallback("onload", page); try self.executeCallback("onload", page);
} }

View File

@@ -762,17 +762,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
} }
// compile and eval a JS module // compile and eval a JS module
// It doesn't wait for callbacks execution // It returns null if the module is already compiled and in the cache.
pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !void { // It returns a v8.Promise if the module must be evaluated.
if (!cacheable) { pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !?v8.Promise {
return self.moduleNoCache(src, url);
}
const arena = self.context_arena; const arena = self.context_arena;
const gop = try self.module_cache.getOrPut(arena, url); if (cacheable) {
if (gop.found_existing) { const value = self.module_cache.get(url);
return; if (value != null) return null;
} }
errdefer _ = self.module_cache.remove(url); errdefer _ = self.module_cache.remove(url);
@@ -782,8 +779,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url); try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
errdefer _ = self.module_identifier.remove(m.getIdentityHash()); errdefer _ = self.module_identifier.remove(m.getIdentityHash());
gop.key_ptr.* = owned_url; if (cacheable) {
gop.value_ptr.* = PersistentModule.init(self.isolate, m); try self.module_cache.put(arena, owned_url, PersistentModule.init(self.isolate, m));
}
// resolveModuleCallback loads module's dependencies. // resolveModuleCallback loads module's dependencies.
const v8_context = self.v8_context; const v8_context = self.v8_context;
@@ -791,21 +789,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return error.ModuleInstantiationError; return error.ModuleInstantiationError;
} }
_ = try m.evaluate(v8_context); const evaluated = try m.evaluate(v8_context);
} // https://v8.github.io/api/head/classv8_1_1Module.html#a1f1758265a4082595757c3251bb40e0f
// Must be a promise that gets returned here.
fn moduleNoCache(self: *JsContext, src: []const u8, url: []const u8) !void { std.debug.assert(evaluated.isPromise());
const m = try compileModule(self.isolate, src, url); const promise = v8.Promise{ .handle = evaluated.handle };
return promise;
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;
if (try m.instantiate(v8_context, resolveModuleCallback) == false) {
return error.ModuleInstantiationError;
}
_ = try m.evaluate(v8_context);
} }
// Wrap a v8.Exception // Wrap a v8.Exception
@@ -1564,32 +1553,32 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
.{ .alloc = .if_needed }, .{ .alloc = .if_needed },
) catch unreachable; ) catch unreachable;
_dynamicModuleCallback(context, resource_str, normalized_specifier, &resolver); log.debug(.js, "dynamic import", .{
.specifier = specifier_str,
.resource = resource_str,
.referrer_full = referrer_full_url,
.normalized_specifier = normalized_specifier,
});
_dynamicModuleCallback(context, normalized_specifier, &resolver) catch |err| {
log.err(.js, "dynamic module callback", .{
.err = err,
});
// Must be rejected at this point
// otherwise, we will just wait on a pending promise.
std.debug.assert(resolver.getPromise().getState() == .kRejected);
};
return @constCast(resolver.getPromise().handle); return @constCast(resolver.getPromise().handle);
} }
fn _dynamicModuleCallback( fn _dynamicModuleCallback(
context: *JsContext, context: *JsContext,
resource: []const u8,
specifier: []const u8, specifier: []const u8,
resolver: *const v8.PromiseResolver, resolver: *const v8.PromiseResolver,
) void { ) !void {
const iso = context.isolate; const iso = context.isolate;
const ctx = context.v8_context; const ctx = context.v8_context;
// Check module cache first.
if (context.module_cache.get(specifier)) |cached_module| {
const namespace = cached_module.castToModule().getModuleNamespace();
_ = resolver.resolve(ctx, namespace);
return;
}
log.info(.js, "dynamic import", .{
.specifier = specifier,
.resource = resource,
.normalized = specifier,
});
const module_loader = context.module_loader; const module_loader = context.module_loader;
const source = module_loader.func(module_loader.ptr, specifier) catch { const source = module_loader.func(module_loader.ptr, specifier) catch {
const error_msg = v8.String.initUtf8(iso, "Failed to load module"); const error_msg = v8.String.initUtf8(iso, "Failed to load module");
@@ -1605,71 +1594,30 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
try_catch.init(context); try_catch.init(context);
defer try_catch.deinit(); defer try_catch.deinit();
const new_module = compileModule(iso, source, specifier) catch { const maybe_promise = context.module(source, specifier, true) catch {
log.err(.js, "module compilation failed", .{ log.err(.js, "module compilation failed", .{
.specifier = specifier, .specifier = specifier,
.exception = try_catch.exception(context.call_arena) catch "unknown error", .exception = try_catch.exception(context.call_arena) catch "unknown error",
.stack = try_catch.stack(context.call_arena) catch null, .stack = try_catch.stack(context.call_arena) catch null,
});
const error_msg = if (try_catch.hasCaught()) blk: {
const exception_str = try_catch.exception(context.call_arena) catch "Compilation error";
break :blk v8.String.initUtf8(iso, exception_str orelse "Compilation error");
} else v8.String.initUtf8(iso, "Module compilation failed");
_ = resolver.reject(ctx, error_msg.toValue());
return;
};
// Insert into Module Cache.
context.module_identifier.putNoClobber(context.context_arena, new_module.getIdentityHash(), specifier) catch unreachable;
context.module_cache.putNoClobber(context.context_arena, specifier, v8.Persistent(v8.Module).init(iso, new_module)) catch unreachable;
const instantiated = new_module.instantiate(ctx, JsContext.resolveModuleCallback) catch {
log.err(.js, "module instantiation failed", .{
.specifier = specifier,
.exception = try_catch.exception(context.call_arena) catch "unknown error",
.stack = try_catch.stack(context.call_arena) catch null,
});
const error_msg = if (try_catch.hasCaught()) blk: {
const exception_str = try_catch.exception(context.call_arena) catch "Instantiation error";
break :blk v8.String.initUtf8(iso, exception_str orelse "Instantiation error");
} else v8.String.initUtf8(iso, "Module instantiation failed");
_ = resolver.reject(ctx, error_msg.toValue());
return;
};
if (!instantiated) {
const error_msg = v8.String.initUtf8(iso, "Module did not instantiate");
_ = resolver.reject(ctx, error_msg.toValue());
return;
}
const evaluated = new_module.evaluate(ctx) catch {
log.err(.js, "module evaluation failed", .{
.specifier = specifier,
.exception = try_catch.exception(context.call_arena) catch "unknown error",
.stack = try_catch.stack(context.call_arena) catch null,
.line = try_catch.sourceLineNumber() orelse 0, .line = try_catch.sourceLineNumber() orelse 0,
}); });
const error_msg = if (try_catch.hasCaught()) blk: { const error_msg = if (try_catch.hasCaught()) blk: {
const exception_str = try_catch.exception(context.call_arena) catch "Evaluation error"; const exception_str = try_catch.exception(context.call_arena) catch "Evaluation error";
break :blk v8.String.initUtf8(iso, exception_str orelse "Evaluation error"); break :blk v8.String.initUtf8(iso, exception_str orelse "Evaluation error");
} else v8.String.initUtf8(iso, "Module evaluation failed"); } else v8.String.initUtf8(iso, "Module evaluation failed");
_ = resolver.reject(ctx, error_msg.toValue()); _ = resolver.reject(ctx, error_msg.toValue());
return; return;
}; };
const new_module = context.module_cache.get(specifier).?.castToModule();
if (evaluated.isPromise()) { if (maybe_promise) |promise| {
const promise = v8.Promise{ .handle = evaluated.handle }; // This means we must wait for the evaluation.
const EvaluationData = struct { const EvaluationData = struct {
module: v8.Persistent(v8.Module), module: v8.Persistent(v8.Module),
resolver: v8.Persistent(v8.PromiseResolver), resolver: v8.Persistent(v8.PromiseResolver),
}; };
const ev_data = context.context_arena.create(EvaluationData) catch unreachable; const ev_data = try context.context_arena.create(EvaluationData);
ev_data.* = .{ ev_data.* = .{
.module = v8.Persistent(v8.Module).init(iso, new_module), .module = v8.Persistent(v8.Module).init(iso, new_module),
.resolver = v8.Persistent(v8.PromiseResolver).init(iso, resolver.*), .resolver = v8.Persistent(v8.PromiseResolver).init(iso, resolver.*),
@@ -1686,7 +1634,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const cb_resolver = data.resolver.castToPromiseResolver(); const cb_resolver = data.resolver.castToPromiseResolver();
const namespace = cb_module.getModuleNamespace(); const namespace = cb_module.getModuleNamespace();
log.warn(.js, "module then promise", .{ log.info(.js, "dynamic import complete", .{
.module = cb_module,
.namespace = namespace, .namespace = namespace,
}); });
_ = cb_resolver.resolve(cb_context, namespace); _ = cb_resolver.resolve(cb_context, namespace);
@@ -1695,11 +1644,15 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const catch_callback = v8.Function.initWithData(ctx, struct { const catch_callback = v8.Function.initWithData(ctx, struct {
pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
log.warn(.js, "module catch promise", .{});
const cb_info = v8.FunctionCallbackInfo{ .handle = info.? }; const cb_info = v8.FunctionCallbackInfo{ .handle = info.? };
const cb_context = cb_info.getIsolate().getCurrentContext(); const cb_context = cb_info.getIsolate().getCurrentContext();
const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue())); const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue()));
const cb_module = data.module.castToModule();
const cb_resolver = data.resolver.castToPromiseResolver(); const cb_resolver = data.resolver.castToPromiseResolver();
log.err(.js, "dynamic import failed", .{
.module = cb_module,
});
_ = cb_resolver.reject(cb_context, cb_info.getData()); _ = cb_resolver.reject(cb_context, cb_info.getData());
} }
}.callback, external); }.callback, external);
@@ -1714,8 +1667,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return; return;
}; };
} else { } else {
// This means it is already present in the cache.
const namespace = new_module.getModuleNamespace(); const namespace = new_module.getModuleNamespace();
log.info(.js, "dynamic import complete", .{
.module = new_module,
.namespace = namespace,
});
_ = resolver.resolve(ctx, namespace); _ = resolver.resolve(ctx, namespace);
return;
} }
} }
}; };