@@ -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