@@ -515,6 +515,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
515515 };
516516
517517 const PersistentObject = v8 .Persistent (v8 .Object );
518+ const PersistentModule = v8 .Persistent (v8 .Module );
518519 const PersistentFunction = v8 .Persistent (v8 .Function );
519520
520521 // Loosely maps to a Browser Page.
@@ -572,6 +573,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
572573 // Some Zig types have code to execute when the call scope ends
573574 call_scope_end_callbacks : std .ArrayListUnmanaged (CallScopeEndCallback ) = .empty ,
574575
576+ // Our module cache: normalized module specifier => module.
577+ module_cache : std .StringHashMapUnmanaged (PersistentModule ) = .empty ,
578+
579+ // Module => Path. The key is the module hashcode (module.getIdentityHash)
580+ // and the value is the full path to the module. We need to capture this
581+ // so that when we're asked to resolve a dependent module, and all we're
582+ // given is the specifier, we can form the full path. The full path is
583+ // necessary to lookup/store the dependent module in the module_cache.
584+ module_identifier : std .AutoHashMapUnmanaged (u32 , []const u8 ) = .empty ,
585+
575586 const ModuleLoader = struct {
576587 ptr : * anyopaque ,
577588 func : * const fn (ptr : * anyopaque , specifier : []const u8 ) anyerror ! ? []const u8 ,
@@ -605,6 +616,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
605616 }
606617 }
607618
619+ {
620+ var it = self .module_cache .valueIterator ();
621+ while (it .next ()) | p | {
622+ p .deinit ();
623+ }
624+ }
625+
608626 for (self .callbacks .items ) | * cb | {
609627 cb .deinit ();
610628 }
@@ -646,6 +664,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
646664 }
647665
648666 // Executes the src
667+ pub fn eval (self : * JsContext , src : []const u8 , name : ? []const u8 ) ! void {
668+ _ = try self .exec (src , name );
669+ }
670+
649671 pub fn exec (self : * JsContext , src : []const u8 , name : ? []const u8 ) ! Value {
650672 const isolate = self .isolate ;
651673 const v8_context = self .v8_context ;
@@ -669,25 +691,31 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
669691
670692 // compile and eval a JS module
671693 // It doesn't wait for callbacks execution
672- pub fn module (self : * JsContext , src : []const u8 , name : []const u8 ) ! union (enum ) { value : Value , exception : Exception } {
673- const v8_context = self .v8_context ;
674- const m = try compileModule (self .isolate , src , name );
694+ pub fn module (self : * JsContext , src : []const u8 , url : []const u8 ) ! void {
695+ const arena = self .context_arena ;
675696
676- // instantiate
677- // resolveModuleCallback loads module's dependencies.
678- const ok = m .instantiate (v8_context , resolveModuleCallback ) catch {
679- return error .ExecutionError ;
680- };
697+ const gop = try self .module_cache .getOrPut (arena , url );
698+ if (gop .found_existing ) {
699+ return ;
700+ }
701+ errdefer _ = self .module_cache .remove (url );
702+
703+ const m = try compileModule (self .isolate , src , url );
704+
705+ const owned_url = try arena .dupe (u8 , url );
706+ try self .module_identifier .putNoClobber (arena , m .getIdentityHash (), owned_url );
707+ errdefer _ = self .module_identifier .remove (m .getIdentityHash ());
708+
709+ gop .key_ptr .* = owned_url ;
710+ gop .value_ptr .* = PersistentModule .init (self .isolate , m );
681711
682- if (! ok ) {
712+ // resolveModuleCallback loads module's dependencies.
713+ const v8_context = self .v8_context ;
714+ if (try m .instantiate (v8_context , resolveModuleCallback ) == false ) {
683715 return error .ModuleInstantiationError ;
684716 }
685717
686- // evaluate
687- const value = m .evaluate (v8_context ) catch {
688- return .{ .exception = self .createException (m .getException ()) };
689- };
690- return .{ .value = self .createValue (value ) };
718+ _ = try m .evaluate (v8_context );
691719 }
692720
693721 // Wrap a v8.Exception
@@ -1234,52 +1262,74 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12341262 c_context : ? * const v8.C_Context ,
12351263 c_specifier : ? * const v8.C_String ,
12361264 import_attributes : ? * const v8.C_FixedArray ,
1237- referrer : ? * const v8.C_Module ,
1265+ c_referrer : ? * const v8.C_Module ,
12381266 ) callconv (.C ) ? * const v8.C_Module {
12391267 _ = import_attributes ;
1240- _ = referrer ;
12411268
1242- std .debug .assert (c_context != null );
12431269 const v8_context = v8.Context { .handle = c_context .? };
1244-
12451270 const self : * JsContext = @ptrFromInt (v8_context .getEmbedderData (1 ).castTo (v8 .BigInt ).getUint64 ());
12461271
1247- // build the specifier value.
1248- const specifier = valueToString (
1249- self .call_arena ,
1250- .{ .handle = c_specifier .? },
1251- self .isolate ,
1252- v8_context ,
1253- ) catch | e | {
1254- log .err (.js , "resolve module specifier" , .{ .err = e });
1272+ const specifier = jsStringToZig (self .call_arena , .{ .handle = c_specifier .? }, self .isolate ) catch | err | {
1273+ log .err (.js , "resolve module" , .{ .err = err });
12551274 return null ;
12561275 };
1276+ const referrer = v8.Module { .handle = c_referrer .? };
12571277
1258- // not currently needed
1259- // const referrer_module = if (referrer) |ref| v8.Module{ .handle = ref } else null;
1260- const module_loader = self .module_loader ;
1261- const source = module_loader .func (module_loader .ptr , specifier ) catch | err | {
1262- log .err (.js , "resolve module fetch" , .{
1278+ return self ._resolveModuleCallback (referrer , specifier ) catch | err | {
1279+ log .err (.js , "resolve module" , .{
12631280 .err = err ,
12641281 .specifier = specifier ,
12651282 });
12661283 return null ;
1267- } orelse return null ;
1284+ };
1285+ }
1286+
1287+ fn _resolveModuleCallback (
1288+ self : * JsContext ,
1289+ referrer : v8.Module ,
1290+ specifier : []const u8 ,
1291+ ) ! ? * const v8.C_Module {
1292+ const referrer_path = self .module_identifier .get (referrer .getIdentityHash ()) orelse {
1293+ // Shouldn't be possible.
1294+ return error .UnknownModuleReferrer ;
1295+ };
1296+
1297+ const normalized_specifier = try @import ("../url.zig" ).stitch (
1298+ self .call_arena ,
1299+ specifier ,
1300+ referrer_path ,
1301+ .{ .alloc = .if_needed },
1302+ );
1303+
1304+ if (self .module_cache .get (normalized_specifier )) | pm | {
1305+ return pm .handle ;
1306+ }
1307+
1308+ const module_loader = self .module_loader ;
1309+ const source = try module_loader .func (module_loader .ptr , normalized_specifier ) orelse return null ;
12681310
12691311 var try_catch : TryCatch = undefined ;
12701312 try_catch .init (self );
12711313 defer try_catch .deinit ();
12721314
12731315 const m = compileModule (self .isolate , source , specifier ) catch | err | {
1274- log .err (.js , "resolve module compile " , .{
1316+ log .warn (.js , "compile resolved module " , .{
12751317 .specifier = specifier ,
1276- .stack = try_catch .stack (self .context_arena ) catch null ,
1277- .src = try_catch .sourceLine (self .context_arena ) catch "err" ,
1318+ .stack = try_catch .stack (self .call_arena ) catch null ,
1319+ .src = try_catch .sourceLine (self .call_arena ) catch "err" ,
12781320 .line = try_catch .sourceLineNumber () orelse 0 ,
1279- .exception = (try_catch .exception (self .context_arena ) catch @errorName (err )) orelse @errorName (err ),
1321+ .exception = (try_catch .exception (self .call_arena ) catch @errorName (err )) orelse @errorName (err ),
12801322 });
12811323 return null ;
12821324 };
1325+
1326+ // We were hoping to find the module in our cache, and thus used
1327+ // the short-lived call_arena to create the normalized_specifier.
1328+ // But now this'll live for the lifetime of the context.
1329+ const arena = self .context_arena ;
1330+ const owned_specifier = try arena .dupe (u8 , normalized_specifier );
1331+ try self .module_cache .put (arena , owned_specifier , PersistentModule .init (self .isolate , m ));
1332+ try self .module_identifier .putNoClobber (arena , m .getIdentityHash (), owned_specifier );
12831333 return m .handle ;
12841334 }
12851335 };
0 commit comments