tc39 / proposal-defer-import-eval

A proposal for introducing a way to defer evaluate of a module
https://tc39.es/proposal-defer-import-eval
MIT License
208 stars 12 forks source link

Add support for "optional" re-exports #30

Open nicolo-ribaudo opened 5 months ago

nicolo-ribaudo commented 5 months ago

[!NOTE] See #31 for an alternative. Differences between the two PRs are marked with πŸ”Ž

export optional { foo, bar } from "./there.js"

This PR adds a new keyword for exports, πŸ”Žoptional, to mark export ... from statements as three-shakeable. It has the following properties:

An optional re-export is considered to be used if the consumer of the module is:

A follow-up proposal might introduce some syntax to mark which bindings will be used in namespace imports, such as import { foo, bar } as partialNamespace from "x" and import("x", { bindings: ["foo", "bar"] }).

A bundler/transpiler can re-write optional re-exports to these exact semantics by moving them to the consumer module:

OriginalRewritten
```js // main.js import { val, bar } from "./dep.js"; import "other"; // dep.js export let val = 2; export { foo } from "a"; export optional { bar } from "b"; export optional { 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 optional { bar } from "b"; export optional { 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, _n2.val, _n3.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/optional-exports.html

guybedford commented 5 months ago

I really like this framing of a new keyword for optional, I think point (4) in https://github.com/tc39/proposal-defer-import-eval/pull/31 already speaks to why this makes more sense semantically and in how it might compose with the other phases.

The idea of a partial deferred namespace is very interesting and I remember Yulia wanting to see something like this in the past. Would be interesting to explore that design space further as well!