v156 · comparison · startup cost

Eager vs deferred

The same expensive module, imported two ways. A plain import * as pays its top-level cost the moment the module is imported; import defer * as pays nothing until the first property access — and nothing at all if that access never happens. Run each side and compare the timelines.

Checking whether this browser parses import defer

Two imports, two timelines

Eager real

Plain import * as ns from "./expensive.js". Works in every browser.

  • nothing yet

Deferred

import defer * as ns from "./expensive.js".

  • nothing yet
Import the eager side and the deferred side, then compare when each one runs its top-level code.

Why not just use dynamic import()?

Dynamic import() also defers work, but it defers loading as well as evaluation, so it is asynchronous and usually needs a preload step to avoid a request waterfall — and it forces every caller onto a Promise. import defer keeps the module fetched and parsed during normal graph loading, so the deferred first-use is synchronous: existing synchronous callers do not change. It targets execution cost specifically, not loading.

One caveat from the proposal: a deferred subtree containing top-level await cannot be deferred (property access is synchronous), so those async modules are evaluated eagerly during loading and only the synchronous parts are deferred.

see also