Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for "defer" re-exports #31

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

nicolo-ribaudo
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo commented Mar 22, 2024

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:

OriginalRewritten
// 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";
// main.js
import { val } from "./dep.js";
import { bar } from "b";
import "other";

// dep.js
export let val = 2;
export { foo } from "a";
// 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";
// 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";

馃攷

// 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";
// 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";

Rendered preview: https://nicolo-ribaudo.github.io/proposal-defer-import-eval/branch/deferred-exports.html

nicolo-ribaudo added a commit to nicolo-ribaudo/proposal-defer-import-eval that referenced this pull request Mar 22, 2024
tc39#30 and tc39#31, that implement more general "optional/deferred
re-exports" with tree-shaking capabilities, give two different
meaning to `export defer * as x from "x"`:
- in tc39#30, `export defer * as x from "x"` unconditionally loads  `"x"`,
  and defers it's execution until when the namespace is used
- in tc39#31, it only loads `x` if some module is actually importing `{ x }`
  from this one, and then defers its execution

Due to this difference, for now it's better to remove `export defer *`
until its semantics are settlet, together with the other `export defer`/
`export optional` cases. I will include a revert for this commit in
those two PRs.
nicolo-ribaudo added a commit to nicolo-ribaudo/proposal-defer-import-eval that referenced this pull request Mar 22, 2024
\tc39#30 and tc39#31, that implement more general "optional/deferred
re-exports" with tree-shaking capabilities, give two different
meaning to `export defer * as x from "x"`:
- in tc39#30, `export defer * as x from "x"` unconditionally loads  `"x"`,
  and defers it's execution until when the namespace is used
- in tc39#31, it only loads `x` if some module is actually importing `{ x }`
  from this one, and then defers its execution

Due to this difference, for now it's better to remove `export defer *`
until its semantics are settlet, together with the other `export defer`/
`export optional` cases. I will include a revert for this commit in
those two PRs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant