@@ -222,63 +222,54 @@ pub fn exec(self: *Context, src: []const u8, name: ?[]const u8) !js.Value {
222222}
223223
224224pub fn module (self : * Context , comptime want_result : bool , src : []const u8 , url : []const u8 , cacheable : bool ) ! (if (want_result ) ModuleEntry else void ) {
225- if (cacheable ) {
226- if (self .module_cache .get (url )) | entry | {
227- // The dynamic import will create an entry without the
228- // module to prevent multiple calls from asynchronously
229- // loading the same module. If we're here, without the
230- // module, then it's time to load it.
231- if (entry .module != null ) {
232- return if (comptime want_result ) entry else {};
225+ const mod , const owned_url = blk : {
226+ const arena = self .arena ;
227+
228+ // gop will _always_ initiated if cacheable == true
229+ var gop : std .StringHashMapUnmanaged (ModuleEntry ).GetOrPutResult = undefined ;
230+ if (cacheable ) {
231+ gop = try self .module_cache .getOrPut (arena , url );
232+ if (gop .found_existing ) {
233+ if (gop .value_ptr .module != null ) {
234+ return if (comptime want_result ) gop .value_ptr .* else {};
235+ }
236+ } else {
237+ // first time seing this
238+ gop .value_ptr .* = .{};
233239 }
234240 }
235- }
236-
237- const m = try compileModule (self .isolate , src , url );
238241
239- const arena = self .arena ;
240- const owned_url = try arena .dupe (u8 , url );
242+ const m = try compileModule ( self .isolate , src , url ) ;
243+ const owned_url = try arena .dupeZ (u8 , url );
241244
242- try self .module_identifier .putNoClobber (arena , m .getIdentityHash (), owned_url );
243- errdefer _ = self .module_identifier .remove (m .getIdentityHash ());
245+ if (cacheable ) {
246+ // compileModule is synchronous - nothing can modify the cache during compilation
247+ std .debug .assert (gop .value_ptr .module == null );
244248
245- const v8_context = self .v8_context ;
246- {
247- // Non-async modules are blocking. We can download them in
248- // parallel, but they need to be processed serially. So we
249- // want to get the list of dependent modules this module has
250- // and start downloading them asap.
251- const requests = m .getModuleRequests ();
252- for (0.. requests .length ()) | i | {
253- const req = requests .get (v8_context , @intCast (i )).castTo (v8 .ModuleRequest );
254- const specifier = try self .jsStringToZig (req .getSpecifier (), .{});
255- const normalized_specifier = try self .script_manager .? .resolveSpecifier (
256- self .call_arena ,
257- specifier ,
258- owned_url ,
259- );
260- const gop = try self .module_cache .getOrPut (self .arena , normalized_specifier );
249+ gop .value_ptr .module = PersistentModule .init (self .isolate , m );
261250 if (! gop .found_existing ) {
262- const owned_specifier = try self .arena .dupeZ (u8 , normalized_specifier );
263- gop .key_ptr .* = owned_specifier ;
264- gop .value_ptr .* = .{};
265- try self .script_manager .? .preloadImport (owned_specifier , url );
251+ gop .key_ptr .* = owned_url ;
266252 }
267253 }
268- }
269254
270- if (try m .instantiate (v8_context , resolveModuleCallback ) == false ) {
255+ break :blk .{ m , owned_url };
256+ };
257+
258+ try self .postCompileModule (mod , owned_url );
259+
260+ const v8_context = self .v8_context ;
261+ if (try mod .instantiate (v8_context , resolveModuleCallback ) == false ) {
271262 return error .ModuleInstantiationError ;
272263 }
273264
274- const evaluated = m .evaluate (v8_context ) catch {
275- std .debug .assert (m .getStatus () == .kErrored );
265+ const evaluated = mod .evaluate (v8_context ) catch {
266+ std .debug .assert (mod .getStatus () == .kErrored );
276267
277268 // Some module-loading errors aren't handled by TryCatch. We need to
278269 // get the error from the module itself.
279270 log .warn (.js , "evaluate module" , .{
280271 .specifier = owned_url ,
281- .message = self .valueToString (m .getException (), .{}) catch "???" ,
272+ .message = self .valueToString (mod .getException (), .{}) catch "???" ,
282273 });
283274 return error .EvaluationError ;
284275 };
@@ -301,28 +292,46 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url:
301292 // be cached
302293 std .debug .assert (cacheable );
303294
304- const persisted_module = PersistentModule .init (self .isolate , m );
305- const persisted_promise = PersistentPromise .init (self .isolate , .{ .handle = evaluated .handle });
306-
307- var gop = try self .module_cache .getOrPut (arena , owned_url );
308- if (gop .found_existing ) {
309- // If we're here, it's because we had a cache entry, but no
310- // module. This happens because both our synch and async
311- // module loaders create the entry to prevent concurrent
312- // loads of the same resource (like Go's Singleflight).
313- std .debug .assert (gop .value_ptr .module == null );
314- std .debug .assert (gop .value_ptr .module_promise == null );
315-
316- gop .value_ptr .module = persisted_module ;
317- gop .value_ptr .module_promise = persisted_promise ;
318- } else {
319- gop .value_ptr .* = ModuleEntry {
320- .module = persisted_module ,
321- .module_promise = persisted_promise ,
322- .resolver_promise = null ,
323- };
295+ // entry has to have been created atop this function
296+ const entry = self .module_cache .getPtr (owned_url ).? ;
297+
298+ // and the module must have been set after we compiled it
299+ std .debug .assert (entry .module != null );
300+ std .debug .assert (entry .module_promise == null );
301+
302+ entry .module_promise = PersistentPromise .init (self .isolate , .{ .handle = evaluated .handle });
303+ return if (comptime want_result ) entry .* else {};
304+ }
305+
306+ // After we compile a module, whether it's a top-level one, or a nested one,
307+ // we always want to track its identity (so that, if this module imports other
308+ // modules, we can resolve the full URL), and preload any dependent modules.
309+ fn postCompileModule (self : * Context , mod : v8.Module , url : [:0 ]const u8 ) ! void {
310+ try self .module_identifier .putNoClobber (self .arena , mod .getIdentityHash (), url );
311+
312+ const v8_context = self .v8_context ;
313+
314+ // Non-async modules are blocking. We can download them in parallel, but
315+ // they need to be processed serially. So we want to get the list of
316+ // dependent modules this module has and start downloading them asap.
317+ const requests = mod .getModuleRequests ();
318+ const script_manager = self .script_manager .? ;
319+ for (0.. requests .length ()) | i | {
320+ const req = requests .get (v8_context , @intCast (i )).castTo (v8 .ModuleRequest );
321+ const specifier = try self .jsStringToZig (req .getSpecifier (), .{});
322+ const normalized_specifier = try script_manager .resolveSpecifier (
323+ self .call_arena ,
324+ specifier ,
325+ url ,
326+ );
327+ const nested_gop = try self .module_cache .getOrPut (self .arena , normalized_specifier );
328+ if (! nested_gop .found_existing ) {
329+ const owned_specifier = try self .arena .dupeZ (u8 , normalized_specifier );
330+ nested_gop .key_ptr .* = owned_specifier ;
331+ nested_gop .value_ptr .* = .{};
332+ try script_manager .preloadImport (owned_specifier , url );
333+ }
324334 }
325- return if (comptime want_result ) gop .value_ptr .* else {};
326335}
327336
328337// == Creators ==
@@ -1189,31 +1198,14 @@ fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: []cons
11891198 };
11901199
11911200 const normalized_specifier = try self .script_manager .? .resolveSpecifier (
1192- self .arena , // might need to survive until the module is loaded
1201+ self .arena ,
11931202 specifier ,
11941203 referrer_path ,
11951204 );
11961205
1197- const gop = try self .module_cache .getOrPut (self .arena , normalized_specifier );
1198- if (gop .found_existing ) {
1199- if (gop .value_ptr .module ) | m | {
1200- return m .handle ;
1201- }
1202- // We don't have a module, but we do have a cache entry for it
1203- // That means we're already trying to load it. We just have
1204- // to wait for it to be done.
1205- } else {
1206- // I don't think it's possible for us to be here. This is
1207- // only ever called by v8 when we evaluate a module. But
1208- // before evaluating, we should have already started
1209- // downloading all of the module's nested modules. So it
1210- // should be impossible that this is the first time we've
1211- // heard about this module.
1212- // But, I'm not confident enough in that, and ther's little
1213- // harm in handling this case.
1214- @branchHint (.unlikely );
1215- gop .value_ptr .* = .{};
1216- try self .script_manager .? .preloadImport (normalized_specifier , referrer_path );
1206+ const entry = self .module_cache .getPtr (normalized_specifier ).? ;
1207+ if (entry .module ) | m | {
1208+ return m .castToModule ().handle ;
12171209 }
12181210
12191211 var source = try self .script_manager .? .waitForImport (normalized_specifier );
@@ -1223,26 +1215,10 @@ fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: []cons
12231215 try_catch .init (self );
12241216 defer try_catch .deinit ();
12251217
1226- const entry = self .module (true , source .src (), normalized_specifier , true ) catch | err | {
1227- switch (err ) {
1228- error .EvaluationError = > {
1229- // This is a sentinel value telling us that the error was already
1230- // logged. Some module-loading errors aren't captured by Try/Catch.
1231- // We need to handle those errors differently, where the module
1232- // exists.
1233- },
1234- else = > log .warn (.js , "compile resolved module" , .{
1235- .specifier = normalized_specifier ,
1236- .stack = try_catch .stack (self .call_arena ) catch null ,
1237- .src = try_catch .sourceLine (self .call_arena ) catch "err" ,
1238- .line = try_catch .sourceLineNumber () orelse 0 ,
1239- .exception = (try_catch .exception (self .call_arena ) catch @errorName (err )) orelse @errorName (err ),
1240- }),
1241- }
1242- return null ;
1243- };
1244- // entry.module is always set when returning from self.module()
1245- return entry .module .? .handle ;
1218+ const mod = try compileModule (self .isolate , source .src (), normalized_specifier );
1219+ try self .postCompileModule (mod , normalized_specifier );
1220+ entry .module = PersistentModule .init (self .isolate , mod );
1221+ return entry .module .? .castToModule ().handle ;
12461222}
12471223
12481224// Will get passed to ScriptManager and then passed back to us when
@@ -1317,7 +1293,31 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
13171293 // `dynamicModuleSourceCallback`, but we can skip some steps
13181294 // since the module is alrady loaded,
13191295 std .debug .assert (gop .value_ptr .module != null );
1320- std .debug .assert (gop .value_ptr .module_promise != null );
1296+
1297+ // If the module hasn't been evaluated yet (it was only instantiated
1298+ // as a static import dependency), we need to evaluate it now.
1299+ if (gop .value_ptr .module_promise == null ) {
1300+ const mod = gop .value_ptr .module .? .castToModule ();
1301+ if (mod .getStatus () == .kEvaluated ) {
1302+ // Module was already evaluated (shouldn't normally happen, but handle it).
1303+ // Create a pre-resolved promise with the module namespace.
1304+ const persisted_module_resolver = v8 .Persistent (v8 .PromiseResolver ).init (isolate , v8 .PromiseResolver .init (self .v8_context ));
1305+ try self .persisted_promise_resolvers .append (self .arena , persisted_module_resolver );
1306+ var module_resolver = persisted_module_resolver .castToPromiseResolver ();
1307+ _ = module_resolver .resolve (self .v8_context , mod .getModuleNamespace ());
1308+ gop .value_ptr .module_promise = PersistentPromise .init (self .isolate , module_resolver .getPromise ());
1309+ } else {
1310+ // the module was loaded, but not evaluated, we _have_ to evaluate it now
1311+ const evaluated = mod .evaluate (self .v8_context ) catch {
1312+ std .debug .assert (mod .getStatus () == .kErrored );
1313+ const error_msg = v8 .String .initUtf8 (isolate , "Module evaluation failed" );
1314+ _ = resolver .reject (self .v8_context , error_msg .toValue ());
1315+ return promise ;
1316+ };
1317+ std .debug .assert (evaluated .isPromise ());
1318+ gop .value_ptr .module_promise = PersistentPromise .init (self .isolate , .{ .handle = evaluated .handle });
1319+ }
1320+ }
13211321
13221322 // like before, we want to set this up so that if anything else
13231323 // tries to load this module, it can just return our promise
0 commit comments