Skip to content

Commit 1f3329b

Browse files
Yoav Weisschromium-wpt-export-bot
Yoav Weiss
authored andcommitted
Multiple import maps
Import maps currently have to load before any ES module and there can only be a single import map per document. That makes them fragile and potentially slow to use in real-life scenarios: Any module that loads before them breaks the entire app, and in apps with many modules the become a large blocking resource, as the entire map for all possible modules needs to load first. This implements whatwg/html#10528 to solve that. Change-Id: I54e1b9cdfe989d61c85d73a5fd384f860273ad9a Bug: 358379381 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5776262 Commit-Queue: Yoav Weiss (@Shopify) <[email protected]> Reviewed-by: Kouhei Ueno <[email protected]> Cr-Commit-Position: refs/heads/main@{#1378943}
1 parent 9e160bd commit 1f3329b

27 files changed

+637
-41
lines changed

import-maps/acquiring/dynamic-import.html

+3-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
<script src="/resources/testharness.js"></script>
44
<script src="/resources/testharnessreport.js"></script>
55
<script>
6-
const t = async_test(
7-
'After dynamic imports, import maps should fire error events');
86
const log = [];
97
// To ensure we are testing that the flag is cleared at the beginning of module
108
// script loading unconditionally, not at the end of loading or not at the
@@ -14,7 +12,7 @@
1412
promise_test(() => import('../resources/empty.js?pipe=trickle(d1)'),
1513
"A dynamic import succeeds");
1614
</script>
17-
<script type="importmap" onload="t.assert_unreached('onload')" onerror="t.done()">
15+
<script type="importmap">
1816
{
1917
"imports": {
2018
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
@@ -24,7 +22,6 @@
2422
<script>
2523
promise_test(() => {
2624
return import("../resources/log.js?pipe=sub&name=A")
27-
.then(() => assert_array_equals(log, ["log:A"]))
28-
},
29-
'After a dynamic import(), import maps are not effective');
25+
.then(() => assert_array_equals(log, ["log:B"]))
26+
}, 'After a dynamic import(), import maps work fine');
3027
</script>

import-maps/acquiring/modulepreload-link-header.html

+3-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
<script src="/resources/testharness.js"></script>
44
<script src="/resources/testharnessreport.js"></script>
55
<script>
6-
const t = async_test(
7-
'With modulepreload link header, import maps should fire error events');
86
const log = [];
97
</script>
10-
<script type="importmap" onerror="t.done()">
8+
<script type="importmap">
119
{
1210
"imports": {
1311
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
@@ -17,7 +15,7 @@
1715
<script>
1816
promise_test(() => {
1917
return import("../resources/log.js?pipe=sub&name=A")
20-
.then(() => assert_array_equals(log, ["log:A"]))
18+
.then(() => assert_array_equals(log, ["log:B"]))
2119
},
22-
'With modulepreload link header, import maps are not effective');
20+
'With modulepreload link header, import maps work fine');
2321
</script>

import-maps/acquiring/modulepreload.html

+3-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
<script src="/resources/testharness.js"></script>
44
<script src="/resources/testharnessreport.js"></script>
55
<script>
6-
const t = async_test(
7-
'After <link rel=modulepreload> import maps should fire error events');
86
const log = [];
97
</script>
108
<link rel="modulepreload" href="../resources/empty.js?pipe=trickle(d1)"></link>
11-
<script type="importmap" onerror="t.done()">
9+
<script type="importmap">
1210
{
1311
"imports": {
1412
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
@@ -18,7 +16,7 @@
1816
<script>
1917
promise_test(() => {
2018
return import("../resources/log.js?pipe=sub&name=A")
21-
.then(() => assert_array_equals(log, ["log:A"]))
19+
.then(() => assert_array_equals(log, ["log:B"]))
2220
},
23-
'After <link rel=modulepreload> import maps are not effective');
21+
'After <link rel=modulepreload> import maps should work fine');
2422
</script>

import-maps/acquiring/script-tag-inline.html

+3-10
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,11 @@
33
<script src="/resources/testharness.js"></script>
44
<script src="/resources/testharnessreport.js"></script>
55
<script>
6-
const t = async_test(
7-
'After inline <script type="module"> import maps should fire error events');
86
const log = [];
97
</script>
108
<script type="module">
11-
// While this inline module script doesn't have any specifiers and doesn't fetch
12-
// anything, this still disables subsequent import maps, because
13-
// https://wicg.github.io/import-maps/#wait-for-import-maps
14-
// is anyway called at the beginning of
15-
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph
169
</script>
17-
<script type="importmap" onerror="t.done()">
10+
<script type="importmap">
1811
{
1912
"imports": {
2013
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
@@ -24,7 +17,7 @@
2417
<script>
2518
promise_test(() => {
2619
return import("../resources/log.js?pipe=sub&name=A")
27-
.then(() => assert_array_equals(log, ["log:A"]))
20+
.then(() => assert_array_equals(log, ["log:B"]))
2821
},
29-
'After inline <script type="module"> import maps are not effective');
22+
'After inline <script type="module"> import maps work fine');
3023
</script>

import-maps/acquiring/script-tag.html

+3-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
<script src="/resources/testharness.js"></script>
44
<script src="/resources/testharnessreport.js"></script>
55
<script>
6-
const t = async_test(
7-
'After <script type="module"> import maps should fire error events');
86
const log = [];
97
</script>
108
<script type="module" src="../resources/empty.js?pipe=trickle(d1)"></script>
11-
<script type="importmap" onerror="t.done()">
9+
<script type="importmap">
1210
{
1311
"imports": {
1412
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
@@ -18,7 +16,7 @@
1816
<script>
1917
promise_test(() => {
2018
return import("../resources/log.js?pipe=sub&name=A")
21-
.then(() => assert_array_equals(log, ["log:A"]))
19+
.then(() => assert_array_equals(log, ["log:B"]))
2220
},
23-
'After <script type="module"> import maps are not effective');
21+
'After <script type="module"> import maps work fine');
2422
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<script src="/resources/testharness.js"></script>
4+
<script src="/resources/testharnessreport.js"></script>
5+
<script src="/import-maps/resources/test-helper.js"></script>
6+
<script>
7+
// Simulate resolving a module before import maps are processed
8+
import("../resources/log.js?pipe=sub&name=ModuleA").catch(() => {});
9+
</script>
10+
<script type="importmap">
11+
{
12+
"imports": {
13+
"../resources/log.js?pipe=sub&name=ModuleA": "../resources/log.js?pipe=sub&name=ModuleB",
14+
"http:/": "../resources/log.js?pipe=sub&name=scheme",
15+
"https:/": "../resources/log.js?pipe=sub&name=scheme"
16+
}
17+
}
18+
</script>
19+
<script>
20+
test_loaded(
21+
"../resources/log.js?pipe=sub&name=ModuleA",
22+
["log:ModuleA"],
23+
"Rules for already resolved modules are dropped"
24+
);
25+
</script>
26+
</html>

import-maps/multiple-import-maps/basic.html

+2-4
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@
2222
}
2323
</script>
2424
<script>
25-
// Currently the spec doesn't allow multiple import maps, by setting acquiring
26-
// import maps to false on preparing the first import map.
2725
promise_test(() => {
2826
return import("../resources/log.js?pipe=sub&name=A1")
2927
.then(() => import("../resources/log.js?pipe=sub&name=A2"))
3028
.then(() => import("../resources/log.js?pipe=sub&name=A3"))
3129
.then(() => assert_array_equals(
3230
log,
33-
["onerror 2", "log:B1", "log:B2", "log:A3"]))
31+
["log:B1", "log:B2", "log:C3"]))
3432
},
35-
"Second import map should be rejected");
33+
"Second import map should be used for resolution");
3634
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<script src="/resources/testharness.js"></script>
4+
<script src="/resources/testharnessreport.js"></script>
5+
<script type="importmap">
6+
{
7+
"imports": {
8+
"module-a": "../resources/log.js?pipe=sub&name=ModuleA",
9+
"module-b/something": "../resources/log.js?pipe=sub&name=ModuleB"
10+
}
11+
}
12+
</script>
13+
<script type="importmap">
14+
{
15+
"imports": {
16+
"module-a": "../resources/log.js?pipe=sub&name=OtherModuleA",
17+
"module-b/": "../resources/log.js?pipe=sub&name=PrefixModuleB",
18+
"module-b": "../resources/log.js?pipe=sub&name=OtherModuleB"
19+
}
20+
}
21+
</script>
22+
<script>
23+
const test_loaded = (specifier, expected_log, description) => {
24+
promise_test(async t => {
25+
log = [];
26+
await import(specifier);
27+
assert_array_equals(log, expected_log);
28+
}, description);
29+
};
30+
31+
test_loaded(
32+
"module-a",
33+
["log:ModuleA"],
34+
"First defined rule persists in case of conflict"
35+
);
36+
37+
test_loaded(
38+
"module-b/something",
39+
["log:ModuleB"],
40+
"First defined rule persists in case of conflict - prefixed bare specifiers"
41+
);
42+
43+
test_loaded(
44+
"module-b",
45+
["log:OtherModuleB"],
46+
"First defined rule persists in case of conflict - non-prefix bare specifier"
47+
);
48+
</script>
49+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
</head>
7+
<body>
8+
<script>
9+
function waitForBool(boolName) {
10+
return new Promise((resolve) => {
11+
const checkVariable = setInterval(() => {
12+
if (window[boolName]) {
13+
clearInterval(checkVariable);
14+
resolve();
15+
}
16+
}, 0);
17+
});
18+
}
19+
20+
step_timeout(() => {
21+
const importMapScript = document.createElement('script');
22+
importMapScript.type = 'importmap';
23+
importMapScript.textContent = JSON.stringify({
24+
imports: {
25+
"../resources/log.sub.js?name=A": "../resources/log.sub.js?name=B"
26+
}
27+
});
28+
document.head.appendChild(importMapScript);
29+
}, 100);
30+
const log = [];
31+
</script>
32+
<script type="module">
33+
import "../resources/importer.sub.js?pipe=trickle(d0.5)&name=..%2Fresources%2Flog.sub.js%3Fname%3DA";
34+
</script>
35+
<script type="module">
36+
test(() => {
37+
assert_array_equals(log, ["log:B"], "Import should use the new import map");
38+
}, "Module tree that started to download before a new import map should still take it into account");
39+
</script>
40+
</body>
41+
</html>
42+
43+
44+
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
</head>
7+
<body>
8+
<script type="module" async>
9+
step_timeout(() => {
10+
const importMapScript = document.createElement('script');
11+
importMapScript.type = 'importmap';
12+
importMapScript.textContent = JSON.stringify({
13+
imports: {
14+
"../resources/log.sub.js?name=A": "../resources/log.sub.js?name=B"
15+
}
16+
});
17+
document.head.appendChild(importMapScript);
18+
}, 100);
19+
</script>
20+
<script>
21+
const log = [];
22+
</script>
23+
<script type="module" src="../resources/importer.sub.js?pipe=trickle(d0.5)&name=..%2Fresources%2Flog.sub.js%3Fname%3DA"></script>
24+
<script type="module">
25+
test(() => {
26+
assert_array_equals(log, ["log:B"], "Import should use the new import map");
27+
}, "Module tree that started to download before a new import map should still take it into account");
28+
</script>
29+
</body>
30+
</html>
31+
32+
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<script src="/resources/testharness.js"></script>
4+
<script src="/resources/testharnessreport.js"></script>
5+
<script>
6+
const log = [];
7+
</script>
8+
<script type="importmap">
9+
{
10+
"scopes": {
11+
"/": {
12+
"../resources/../resources/app.js": "../resources/log.js?pipe=sub&name=first"
13+
}
14+
}
15+
}
16+
</script>
17+
<script type="importmap">
18+
{
19+
"scopes": {
20+
"/": {
21+
"../resources/app.js": "../resources/log.js?pipe=sub&name=second"
22+
}
23+
}
24+
}
25+
</script>
26+
<script type="module">
27+
promise_test(async () => {
28+
await import("../resources/app.js");
29+
assert_array_equals(log, ["log:first"]);
30+
},
31+
"Second import map should not override same resolved URL");
32+
</script>

import-maps/multiple-import-maps/with-errors.html

+2-6
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,11 @@
1818
}
1919
</script>
2020
<script>
21-
// Currently the spec doesn't allow multiple import maps, by setting acquiring
22-
// import maps to false on preparing the first import map.
23-
// Even the first import map has errors and thus Document's import map is not
24-
// updated, the second import map is still rejected at preparationg.
2521
promise_test(() => {
2622
return import("../resources/log.js?pipe=sub&name=A")
2723
.then(() => assert_array_equals(
2824
log,
29-
["onerror 2", "log:A"]))
25+
["log:C"]))
3026
},
31-
"Second import map should be rejected after an import map with errors");
27+
"Second import map should be used for resolution even after an import map with errors");
3228
</script>
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
</head>
7+
<body>
8+
<script>
9+
const log = [];
10+
11+
// First, use a module specifier
12+
promise_test(() => {
13+
return import("../resources/log.js?pipe=sub&name=A")
14+
.then(() => {
15+
assert_array_equals(log, ["log:A"], "First import should use original module");
16+
});
17+
}, "Initial import before import map");
18+
19+
// Now try to redefine it with multiple import map rules
20+
</script>
21+
<script type="importmap">
22+
{
23+
"imports": {
24+
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B",
25+
"http:/": "../resources/log.js?pipe=sub&name=scheme",
26+
"https:/": "../resources/log.js?pipe=sub&name=scheme"
27+
}
28+
}
29+
</script>
30+
<script type="module">
31+
// Testing that the resolution is correct using `resolve`, as you can't import
32+
// the same module twice.
33+
test(() => {
34+
assert_true(import.meta.resolve("../resources/log.js?pipe=sub&name=A")
35+
.endsWith("/resources/log.js?pipe=sub&name=A"));
36+
}, "Resolution after import map should not be redefined");
37+
</script>
38+
</body>
39+
</html>
40+

0 commit comments

Comments
 (0)