Skip to content

Commit

Permalink
Multiple import maps
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
Yoav Weiss authored and chromium-wpt-export-bot committed Nov 6, 2024
1 parent 9e160bd commit 1f3329b
Show file tree
Hide file tree
Showing 27 changed files with 637 additions and 41 deletions.
9 changes: 3 additions & 6 deletions import-maps/acquiring/dynamic-import.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const t = async_test(
'After dynamic imports, import maps should fire error events');
const log = [];
// To ensure we are testing that the flag is cleared at the beginning of module
// script loading unconditionally, not at the end of loading or not at the
Expand All @@ -14,7 +12,7 @@
promise_test(() => import('../resources/empty.js?pipe=trickle(d1)'),
"A dynamic import succeeds");
</script>
<script type="importmap" onload="t.assert_unreached('onload')" onerror="t.done()">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
Expand All @@ -24,7 +22,6 @@
<script>
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
},
'After a dynamic import(), import maps are not effective');
.then(() => assert_array_equals(log, ["log:B"]))
}, 'After a dynamic import(), import maps work fine');
</script>
8 changes: 3 additions & 5 deletions import-maps/acquiring/modulepreload-link-header.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const t = async_test(
'With modulepreload link header, import maps should fire error events');
const log = [];
</script>
<script type="importmap" onerror="t.done()">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
Expand All @@ -17,7 +15,7 @@
<script>
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
.then(() => assert_array_equals(log, ["log:B"]))
},
'With modulepreload link header, import maps are not effective');
'With modulepreload link header, import maps work fine');
</script>
8 changes: 3 additions & 5 deletions import-maps/acquiring/modulepreload.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const t = async_test(
'After <link rel=modulepreload> import maps should fire error events');
const log = [];
</script>
<link rel="modulepreload" href="../resources/empty.js?pipe=trickle(d1)"></link>
<script type="importmap" onerror="t.done()">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
Expand All @@ -18,7 +16,7 @@
<script>
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
.then(() => assert_array_equals(log, ["log:B"]))
},
'After <link rel=modulepreload> import maps are not effective');
'After <link rel=modulepreload> import maps should work fine');
</script>
13 changes: 3 additions & 10 deletions import-maps/acquiring/script-tag-inline.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const t = async_test(
'After inline <script type="module"> import maps should fire error events');
const log = [];
</script>
<script type="module">
// While this inline module script doesn't have any specifiers and doesn't fetch
// anything, this still disables subsequent import maps, because
// https://wicg.github.io/import-maps/#wait-for-import-maps
// is anyway called at the beginning of
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph
</script>
<script type="importmap" onerror="t.done()">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
Expand All @@ -24,7 +17,7 @@
<script>
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
.then(() => assert_array_equals(log, ["log:B"]))
},
'After inline <script type="module"> import maps are not effective');
'After inline <script type="module"> import maps work fine');
</script>
8 changes: 3 additions & 5 deletions import-maps/acquiring/script-tag.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const t = async_test(
'After <script type="module"> import maps should fire error events');
const log = [];
</script>
<script type="module" src="../resources/empty.js?pipe=trickle(d1)"></script>
<script type="importmap" onerror="t.done()">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
Expand All @@ -18,7 +16,7 @@
<script>
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
.then(() => assert_array_equals(log, ["log:B"]))
},
'After <script type="module"> import maps are not effective');
'After <script type="module"> import maps work fine');
</script>
26 changes: 26 additions & 0 deletions import-maps/multiple-import-maps/already-resolved-dropped.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/import-maps/resources/test-helper.js"></script>
<script>
// Simulate resolving a module before import maps are processed
import("../resources/log.js?pipe=sub&name=ModuleA").catch(() => {});
</script>
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=ModuleA": "../resources/log.js?pipe=sub&name=ModuleB",
"http:/": "../resources/log.js?pipe=sub&name=scheme",
"https:/": "../resources/log.js?pipe=sub&name=scheme"
}
}
</script>
<script>
test_loaded(
"../resources/log.js?pipe=sub&name=ModuleA",
["log:ModuleA"],
"Rules for already resolved modules are dropped"
);
</script>
</html>
6 changes: 2 additions & 4 deletions import-maps/multiple-import-maps/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@
}
</script>
<script>
// Currently the spec doesn't allow multiple import maps, by setting acquiring
// import maps to false on preparing the first import map.
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A1")
.then(() => import("../resources/log.js?pipe=sub&name=A2"))
.then(() => import("../resources/log.js?pipe=sub&name=A3"))
.then(() => assert_array_equals(
log,
["onerror 2", "log:B1", "log:B2", "log:A3"]))
["log:B1", "log:B2", "log:C3"]))
},
"Second import map should be rejected");
"Second import map should be used for resolution");
</script>
49 changes: 49 additions & 0 deletions import-maps/multiple-import-maps/conflict-first-persists.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="importmap">
{
"imports": {
"module-a": "../resources/log.js?pipe=sub&name=ModuleA",
"module-b/something": "../resources/log.js?pipe=sub&name=ModuleB"
}
}
</script>
<script type="importmap">
{
"imports": {
"module-a": "../resources/log.js?pipe=sub&name=OtherModuleA",
"module-b/": "../resources/log.js?pipe=sub&name=PrefixModuleB",
"module-b": "../resources/log.js?pipe=sub&name=OtherModuleB"
}
}
</script>
<script>
const test_loaded = (specifier, expected_log, description) => {
promise_test(async t => {
log = [];
await import(specifier);
assert_array_equals(log, expected_log);
}, description);
};

test_loaded(
"module-a",
["log:ModuleA"],
"First defined rule persists in case of conflict"
);

test_loaded(
"module-b/something",
["log:ModuleB"],
"First defined rule persists in case of conflict - prefixed bare specifiers"
);

test_loaded(
"module-b",
["log:OtherModuleB"],
"First defined rule persists in case of conflict - non-prefix bare specifier"
);
</script>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
function waitForBool(boolName) {
return new Promise((resolve) => {
const checkVariable = setInterval(() => {
if (window[boolName]) {
clearInterval(checkVariable);
resolve();
}
}, 0);
});
}

step_timeout(() => {
const importMapScript = document.createElement('script');
importMapScript.type = 'importmap';
importMapScript.textContent = JSON.stringify({
imports: {
"../resources/log.sub.js?name=A": "../resources/log.sub.js?name=B"
}
});
document.head.appendChild(importMapScript);
}, 100);
const log = [];
</script>
<script type="module">
import "../resources/importer.sub.js?pipe=trickle(d0.5)&name=..%2Fresources%2Flog.sub.js%3Fname%3DA";
</script>
<script type="module">
test(() => {
assert_array_equals(log, ["log:B"], "Import should use the new import map");
}, "Module tree that started to download before a new import map should still take it into account");
</script>
</body>
</html>




Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script type="module" async>
step_timeout(() => {
const importMapScript = document.createElement('script');
importMapScript.type = 'importmap';
importMapScript.textContent = JSON.stringify({
imports: {
"../resources/log.sub.js?name=A": "../resources/log.sub.js?name=B"
}
});
document.head.appendChild(importMapScript);
}, 100);
</script>
<script>
const log = [];
</script>
<script type="module" src="../resources/importer.sub.js?pipe=trickle(d0.5)&name=..%2Fresources%2Flog.sub.js%3Fname%3DA"></script>
<script type="module">
test(() => {
assert_array_equals(log, ["log:B"], "Import should use the new import map");
}, "Module tree that started to download before a new import map should still take it into account");
</script>
</body>
</html>



32 changes: 32 additions & 0 deletions import-maps/multiple-import-maps/url-resolution-conflict.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const log = [];
</script>
<script type="importmap">
{
"scopes": {
"/": {
"../resources/../resources/app.js": "../resources/log.js?pipe=sub&name=first"
}
}
}
</script>
<script type="importmap">
{
"scopes": {
"/": {
"../resources/app.js": "../resources/log.js?pipe=sub&name=second"
}
}
}
</script>
<script type="module">
promise_test(async () => {
await import("../resources/app.js");
assert_array_equals(log, ["log:first"]);
},
"Second import map should not override same resolved URL");
</script>
8 changes: 2 additions & 6 deletions import-maps/multiple-import-maps/with-errors.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@
}
</script>
<script>
// Currently the spec doesn't allow multiple import maps, by setting acquiring
// import maps to false on preparing the first import map.
// Even the first import map has errors and thus Document's import map is not
// updated, the second import map is still rejected at preparationg.
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(
log,
["onerror 2", "log:A"]))
["log:C"]))
},
"Second import map should be rejected after an import map with errors");
"Second import map should be used for resolution even after an import map with errors");
</script>
40 changes: 40 additions & 0 deletions import-maps/not-overridden/dynamic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
const log = [];

// First, use a module specifier
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => {
assert_array_equals(log, ["log:A"], "First import should use original module");
});
}, "Initial import before import map");

// Now try to redefine it with multiple import map rules
</script>
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B",
"http:/": "../resources/log.js?pipe=sub&name=scheme",
"https:/": "../resources/log.js?pipe=sub&name=scheme"
}
}
</script>
<script type="module">
// Testing that the resolution is correct using `resolve`, as you can't import
// the same module twice.
test(() => {
assert_true(import.meta.resolve("../resources/log.js?pipe=sub&name=A")
.endsWith("/resources/log.js?pipe=sub&name=A"));
}, "Resolution after import map should not be redefined");
</script>
</body>
</html>

Loading

0 comments on commit 1f3329b

Please sign in to comment.