Skip to content

Commit 4bf79e4

Browse files
committed
add importmap support
1 parent 7afecf0 commit 4bf79e4

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/browser/ScriptManager.zig

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ async_module_pool: std.heap.MemoryPool(AsyncModule),
7070
// and can be requested as needed.
7171
sync_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+
7377
const OrderList = std.DoublyLinkedList;
7478

7579
pub 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

111118
pub 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
};

src/tests/html/script/importmap.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
<script src="../../testing.js"></script>
44

5-
<script id=importmap type=importmap>
5+
<script type=importmap>
66
{
77
"imports": {
8-
"core": "./import.js",
8+
"core": "./import.js"
99
}
1010
}
1111
</script>

0 commit comments

Comments
 (0)