Deferred import evaluation�for Stage 2.7
Nicolò Ribaudo (Igalia, in partnership with Bloomberg)
June 2024
The import defer proposal
import defer allows importing modules while deferring their evaluation until when it's actually needed.
2
The import defer proposal
import defer allows importing modules while deferring their evaluation until when it's actually needed.
import defer * as dep from "dep";
3
This doesn't execute dep
The import defer proposal
import defer allows importing modules while deferring their evaluation until when it's actually needed.
import defer * as dep from "dep";
onClick(() => {� console.log(dep.theAnswer);� });
4
It happens here!
This doesn't execute dep
The import defer proposal
import defer allows importing modules while deferring their evaluation until when it's actually needed.
import defer * as dep from "dep";
onClick(() => {� console.log(dep.theAnswer);� });
Why? We are looking at large mature codebases, where a significant part of the startup cost is module evaluation.
5
It happens here!
This doesn't execute dep
Top-level await
Modules using top-level await cannot be evaluated synchronously. What happens when a deferred module subgraph contains one of them?
6
import defer is a best-effort optimization
Top-level await
7
1
1
2
4
3
await
This is our entrypoint
Assume then that something triggers the evaluation of this module
Already evaluated!
"Classic" import
Deferred import
Initial evaluation
Deferred evaluation
2
await
Top-level await
8
await
"Classic" import
Deferred import
main.js
a
b
c
d
e
f
import defer * as a from "a";
import "f";
is equivalent to
preload-and-link "a";
import "c";
import "f";
const a = new Proxy({}, {
get(_, key) {
const m = evaluateSync("a");
return m.[[Namespace]][key];
}
});
Top-level await
Discarded approaches
Problem: prevents from deferring a lot of work that could be deferred
Problem: racy
9
Why not import()?
10
Why not dynamic import?
Dynamic import() is great!
onClick(async () => {
const dep = await import("dep");
console.log(dep.theAnswer);
});
11
This avoids any cost related to "dep" until when it's actually necessary.
Why not dynamic import?
Dynamic import() is great!
onClick(async () => {
const dep = await import("dep");
console.log(dep.theAnswer);
});
Problem: dynamic import makes your code asynchronous, and thus adopting it has high friction.
12
This avoids any cost related to "dep" until when it's actually necessary.
Why not dynamic import?
Dynamic import() is great!
onClick(async () => {
const dep = await import("dep");
console.log(dep.theAnswer);
});
Problem: dynamic import makes your code asynchronous, and thus adopting it has high friction.
We are looking for a solution that can be easily adopted at large scale.
13
This avoids any cost related to "dep" until when it's actually necessary.
Debugging experience
14
Debugging experience
Debugging module graphs is already complex:
15
Example: Webpack Visualizer
16
Example: deno info
➜ deno info ./components/Icons.tsx
local:./components/Icons.tsx
type: TSX
dependencies: 7 unique
size: 108.72KB
file:///[...]/github.com/nicolo-ribaudo/train-trips/components/Icons.tsx (486B)
├─┬ https://esm.sh/preact@10.19.2/jsx-runtime (159B)
│ ├─┬ https://esm.sh/v128/preact@10.19.2/jsx-runtime/src/index.d.ts (1.52KB)
│ │ ├─┬ https://esm.sh/v128/preact@10.19.2/src/index.d.ts (10.76KB)
│ │ │ └─┬ https://esm.sh/v128/preact@10.19.2/src/jsx.d.ts (83.08KB)
│ │ │ └── https://esm.sh/v128/preact@10.19.2/src/index.d.ts *
│ │ └── https://esm.sh/v128/preact@10.19.2/src/jsx.d.ts *
│ ├── https://esm.sh/stable/preact@10.19.2/denonext/preact.mjs (10.83KB)
│ └─┬ https://esm.sh/stable/preact@10.19.2/denonext/jsx-runtime.js (1.82KB)
│ └── https://esm.sh/stable/preact@10.19.2/denonext/preact.mjs *
└─┬ https://esm.sh/preact@10.19.2 (90B)
├── https://esm.sh/v128/preact@10.19.2/src/index.d.ts *
└── https://esm.sh/stable/preact@10.19.2/denonext/preact.mjs *
17
Debugging experience
Debugging module graphs is already complex …� … and this proposal is making it even more complex.
18
There can be tools that help debugging these questions! Maybe built-in in browsers?
Example: https://github.com/nicolo-ribaudo/import-defer-polyfill
Changes since last presentation
19
The module evaluation algorithm relies on the invariant that its synchronous part is not re-entrant: when it starts, no modules in the to-be-evaluated graph can be in the EVALUATING state.
20
"Classic" import
Deferred import
b
c
d
a
⇒ Trying to evaluate a module that is being evaluated is an error.
Coordinated with Node.js for their require(esm) implementation.
evals ...c…
Disallow reads from not-succesfully-eval'd modules (#43)
Problem: Reading from a namespace of a module that threw is currently allowed, but in practice incredibly rare to notice.
import defer * as ns from "./module-that-throws.js";
// ...
ns.foo;
Solution: Accessing properties from a namespace obtained through import defer throws if the module cannot be (or already is) successfully evaluated.
Consequence: There are two namespace objects per module.
import defer * as x1 from "x";
import * as x2 from "x";
x1 !== x2;
21
Will this throw?
Disallow reads from not-succesfully-eval'd modules (#43)
Problem: Reading from a namespace of a module that threw is currently allowed, but in practice incredibly rare to notice.
import defer * as ns from "./module-that-throws.js";
// ...
ns.foo;
Solution: Accessing properties from a namespace obtained through import defer throws if the module cannot be successfully evaluated (or already failed).
Consequence: There are two namespace objects per module.
import defer * as x1 from "x";
import * as x2 from "x";
x1 !== x2;
22
Disallow reads from not-succesfully-eval'd modules (#43)
Problem: Reading from a namespace of a module that threw is currently allowed, but in practice incredibly rare to notice.
import defer * as ns from "./module-that-throws.js";
// ...
ns.foo;
Solution: Accessing properties from a namespace obtained through import defer throws if the module cannot be successfully evaluated (or already failed).
Consequence: There are up to two namespace objects per module.
import defer * as x1 from "x";
import * as x2 from "x";
x1 !== x2;
23
Stage 2.7?
24
Stage 2.7
✅ Complete spec text
✅ Reviewers approved the spec text
🟡 ECMA-262 editors approved the spec text
25