@@ -70,6 +70,10 @@ async_module_pool: std.heap.MemoryPool(AsyncModule),
7070// and can be requested as needed.
7171sync_modules : std .StringHashMapUnmanaged (* SyncModule ),
7272
73+ // Mapping between module specifier and resolution.
74+ // see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap
75+ importmap : std .StringHashMapUnmanaged ([]const u8 ),
76+
7377const OrderList = std .DoublyLinkedList ;
7478
7579pub fn init (browser : * Browser , page : * Page ) ScriptManager {
@@ -80,6 +84,7 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
8084 .asyncs = .{},
8185 .scripts = .{},
8286 .deferreds = .{},
87+ .importmap = .empty ,
8388 .sync_modules = .empty ,
8489 .is_evaluating = false ,
8590 .allocator = allocator ,
@@ -106,6 +111,8 @@ pub fn deinit(self: *ScriptManager) void {
106111 self .async_module_pool .deinit ();
107112
108113 self .sync_modules .deinit (self .allocator );
114+ // we don't deinit self.importmap b/c we use the page's arena for its
115+ // allocations.
109116}
110117
111118pub fn reset (self : * ScriptManager ) void {
@@ -115,6 +122,7 @@ pub fn reset(self: *ScriptManager) void {
115122 self .sync_module_pool .destroy (value_ptr .* );
116123 }
117124 self .sync_modules .clearRetainingCapacity ();
125+ self .importmap .clearRetainingCapacity ();
118126
119127 self .clearList (& self .asyncs );
120128 self .clearList (& self .scripts );
@@ -164,6 +172,9 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element, comptime c
164172 if (std .ascii .eqlIgnoreCase (script_type , "module" )) {
165173 break :blk .module ;
166174 }
175+ if (std .ascii .eqlIgnoreCase (script_type , "importmap" )) {
176+ break :blk .importmap ;
177+ }
167178
168179 // "type" could be anything, but only the above are ones we need to process.
169180 // Common other ones are application/json, application/ld+json, text/template
@@ -249,7 +260,13 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element, comptime c
249260}
250261
251262// Resolve a module specifier to an valid URL.
252- pub fn resolveSpecifier (_ : * ScriptManager , arena : Allocator , specifier : []const u8 , base : []const u8 ) ! [:0 ]const u8 {
263+ pub fn resolveSpecifier (self : * ScriptManager , arena : Allocator , _specifier : []const u8 , base : []const u8 ) ! [:0 ]const u8 {
264+ var specifier = _specifier ;
265+ // If the specifier is mapped in the importmap, use it to resolve the path.
266+ if (self .importmap .get (specifier )) | s | {
267+ specifier = s ;
268+ }
269+
253270 return URL .stitch (
254271 arena ,
255272 specifier ,
@@ -462,6 +479,28 @@ fn errorCallback(ctx: *anyopaque, err: anyerror) void {
462479 script .errorCallback (err );
463480}
464481
482+ fn parseImportmap (self : * ScriptManager , script : * const Script ) ! void {
483+ const content = script .source .content ();
484+
485+ const Imports = struct {
486+ imports : std .json .ArrayHashMap ([]const u8 ),
487+ };
488+
489+ const imports = try std .json .parseFromSliceLeaky (
490+ Imports ,
491+ self .page .arena ,
492+ content ,
493+ .{ .allocate = .alloc_always },
494+ );
495+
496+ var iter = imports .imports .map .iterator ();
497+ while (iter .next ()) | entry | {
498+ try self .importmap .put (self .page .arena , entry .key_ptr .* , entry .value_ptr .* );
499+ }
500+
501+ return ;
502+ }
503+
465504// A script which is pending execution.
466505// It could be pending because:
467506// (a) we're still downloading its content or
@@ -591,6 +630,7 @@ const Script = struct {
591630 const Kind = enum {
592631 module ,
593632 javascript ,
633+ importmap ,
594634 };
595635
596636 const Callback = union (enum ) {
@@ -631,6 +671,23 @@ const Script = struct {
631671 .cacheable = cacheable ,
632672 });
633673
674+ // Handle importmap special case here: the content is a JSON containing
675+ // imports.
676+ if (self .kind == .importmap ) {
677+ page .script_manager .parseImportmap (self ) catch | err | {
678+ log .err (.browser , "parse importmap script" , .{
679+ .err = err ,
680+ .src = url ,
681+ .kind = self .kind ,
682+ .cacheable = cacheable ,
683+ });
684+ self .executeCallback ("onerror" , page );
685+ return ;
686+ };
687+ self .executeCallback ("onload" , page );
688+ return ;
689+ }
690+
634691 const js_context = page .js ;
635692 var try_catch : js.TryCatch = undefined ;
636693 try_catch .init (js_context );
@@ -644,6 +701,7 @@ const Script = struct {
644701 // We don't care about waiting for the evaluation here.
645702 js_context .module (false , content , url , cacheable ) catch break :blk false ;
646703 },
704+ .importmap = > unreachable , // handled before the try/catch.
647705 }
648706 break :blk true ;
649707 };
0 commit comments