|
2 | 2 |
|
3 | 3 | Status: draft |
4 | 4 |
|
5 | | -## Pretty Output Mode |
| 5 | +## Idiomatic Exports Mode (aka `pretty`) |
6 | 6 |
|
7 | | -Goal: Add an opt-in `pretty` (aka `idiomaticExports`) mode to reduce synthesized helper bags when converting between CJS and ESM. |
| 7 | +Goal: Add an opt-in `idiomaticExports` (current shorthand: `pretty`) mode to reduce synthesized helper bags when converting between CJS and ESM. Primary motivation: produce more idiomatic ESM output that is easier for bundlers to tree-shake when inputs qualify for the “safe” path. |
8 | 8 |
|
9 | 9 | ### CJS → ESM |
10 | 10 |
|
11 | | -- Option: `pretty: 'safe' | 'aggressive'` (default: off). |
| 11 | +- Option: `pretty: 'safe' | 'aggressive'` (default: off). Consider exposing the option as `idiomaticExports` to better signal the intent and tree-shaking upside. |
12 | 12 | - Safe mode rules (emit direct exports, avoid `__exports` when all are true): |
13 | 13 | - Only top-level `exports.*` writes or a single `module.exports = { ... }` / `module.exports = fn`. |
14 | 14 | - No reassignments after initial writes; no getters/setters; no computed/non-identifier keys; no mixed `exports` + `module.exports` unless we can rewrite deterministically. |
15 | 15 | - No shadowed `module`/`exports`; no top-level `return`; no `require.cache/extensions`; no dynamic require inside export initializers; no TDZ hazards. |
16 | 16 | - Aggressive mode: allow mixed exports + `module.exports` if we can derive both default and named exports; allow identifier-safe computed keys; allow a single reassignment. |
17 | | -- Emission strategy: |
| 17 | +- Emission strategy (tree-shake-friendly when rules pass): |
18 | 18 | - Named writes → `export const foo = ...` or `export { local as foo }`. |
19 | 19 | - `module.exports = { ... }` → `export default { ... }` (+ optional named re-exports for plain identifiers if a sub-option is enabled). |
20 | 20 | - `module.exports = fn` → `export default fn`. |
21 | 21 | - Fallback to `__exports` when rules fail. |
| 22 | +- Tree shaking note: Safe mode outputs static `export` forms, which typical bundlers can eliminate when unused. Aggressive mode may reintroduce helper bags or conservative shapes, so shaking benefits are best-effort there. |
22 | 23 | - Diagnostics: warn when `pretty` requested but fell back; warn when live-binding fidelity may differ in aggressive mode. |
23 | 24 | - Tests: fixture matrix (safe object, safe function default, mixed exports+module.exports, computed keys, reassignments) with assertions on generated text (absence/presence of `__exports`) and runtime behavior. |
24 | 25 |
|
25 | 26 | ### ESM → CJS |
26 | 27 |
|
27 | | -- Option: same `pretty` flag, but constrained by live bindings and TLA. |
| 28 | +- Option: same `pretty`/`idiomaticExports` flag, but constrained by live bindings and TLA. |
28 | 29 | - Preconditions for pretty CJS: |
29 | 30 | - `topLevelAwait === 'error'` or known-wrap path; `liveBindings !== 'strict'` (or accept relaxed semantics in aggressive mode). |
30 | 31 | - No namespace exports requiring live getters; no export-all with live needs unless we accept relaxed semantics. |
31 | 32 | - Emission strategy when safe: |
32 | 33 | - Direct `exports.foo = foo;` and `module.exports = default` without namespace helpers. |
33 | 34 | - Avoid namespace helper when `export * as ns` can map to `const ns = require(...); exports.ns = ns;` under relaxed live-binding semantics. |
34 | 35 | - Keep helpers for TLA wrap and strict live bindings. |
| 36 | +- Tree shaking note: CJS output is inherently not statically tree-shakeable; “pretty” here is mostly about readability/minimal helpers rather than true shakeability. |
35 | 37 | - Tests: fixtures verifying helper-free output under safe conditions and fallback when constraints are present. |
36 | 38 |
|
37 | 39 | ## Documentation & UX |
|
0 commit comments