[!NOTE]
See #30 for an alternative. Differences between the two PRs are marked with π
export defer { foo, bar } from "./there.js"
This PR extends the πdefer keyword to named re-exports, with the following properties:
deferred re-exports are only loaded if they are imported
deferred exports are always evaluated later than the module that imported them (modulo cycles)
this guarantees that a module's behavior does not depend on any of its deferred re-exports running. This is important because which deferred re-exports run and which do not doesn't depend by the module itself, but by its consumers
export * from cannot be deferred
this is because deferred re-exports must explicitly define which bindings they are exporting, so that the re-exported file doesn't need
π when combining the defer keyword with export * as x from "x", it modifies both the export (by only loading "x" if the consumer imports the x binding) and the implicit import (by creating a deferred namespace object, and only evaluating "x" when accessing properties on it)
π export defer source x from "..."could be supported for the three-shaking semantics, but it might be confusing because defer and source in all other places represent two alternative phases of the same load->link->execute pipeline.
An optional re-export is considered to be used if the consumer of the module is:
importing with import * as
importing with import { ... } and listing the optional binding
importing with import(...)
π If the consumer module is importing with import defer * as or import.defer(...), optional re-exports are preloaded but not evaluated until when the corresponding binding is accessed on the namespace object (async subgraphs are still pre-evaluated).
A bundler/transpiler can re-write deferred re-exports to these exact semantics by moving them to the consumer module:
Original
Rewritten
```js
// main.js
import { val, bar } from "./dep.js";
import "other";
// dep.js
export let val = 2;
export { foo } from "a";
export defer { bar } from "b";
export defer { baz } from "c";
```
```js
// main.js
import { val } from "./dep.js";
import { bar } from "b";
import "other";
// dep.js
export let val = 2;
export { foo } from "a";
```
```js
// main.js
import * as ns from "./dep.js";
// dep.js
export let val = 2;
export { foo } from "a";
export defer { bar } from "b";
export defer { baz } from "c";
```
```js
// main.js
import * as _n1 from "./dep.js";
import * as _n2 from "b";
import * as _n2 from "c";
const ns = Object.freeze({
__proto__: null,
get val() { return _n1.val; },
get foo() { return _n1.foo; },
get bar() { return _n2.bar; },
get baz() { return _n3.baz; },
})
// dep.js
export let val = 2;
export { foo } from "a";
```
π
```js
// main.js
import defer * as ns from "./dep.js";
// dep.js
export let val = 2;
export { foo } from "a";
export optional { bar } from "b";
export optional { baz } from "c";
```
```js
// main.js
import defer * as _n1 from "./dep.js";
import defer * as _n2 from "b";
import defer * as _n2 from "c";
const _run = () => { _n1.val; }
const ns = Object.freeze({
__proto__: null,
get val() { _run(); return _n1.val; },
get foo() { _run(); return _n1.foo; },
get bar() { _run(); return _n2.bar; },
get baz() { _run(); return _n3.baz; },
})
// dep.js
export let val = 2;
export { foo } from "a";
```
This PR extends the π
defer
keyword to named re-exports, with the following properties:export * from
cannot be deferreddefer
keyword withexport * as x from "x"
, it modifies both theexport
(by only loading"x"
if the consumer imports thex
binding) and the implicitimport
(by creating a deferred namespace object, and only evaluating"x"
when accessing properties on it)export defer source x from "..."
could be supported for the three-shaking semantics, but it might be confusing becausedefer
andsource
in all other places represent two alternative phases of the same load->link->execute pipeline.An optional re-export is considered to be used if the consumer of the module is:
import * as
import { ... }
and listing the optional bindingimport(...)
π If the consumer module is importing with
import defer * as
orimport.defer(...)
, optional re-exports are preloaded but not evaluated until when the corresponding binding is accessed on the namespace object (async subgraphs are still pre-evaluated).A bundler/transpiler can re-write deferred re-exports to these exact semantics by moving them to the consumer module:
Rendered preview: https://nicolo-ribaudo.github.io/proposal-defer-import-eval/branch/deferred-exports.html