You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+34-4Lines changed: 34 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,12 @@ Node.js utility for transforming a JavaScript or TypeScript file from an ES modu
9
9
- ES module ➡️ CommonJS
10
10
- CommonJS ➡️ ES module
11
11
12
+
Highlights
13
+
14
+
- Defaults to safe CommonJS output: strict live bindings, import.meta shims, and specifier preservation.
15
+
- Opt into stricter/looser behaviors: live binding enforcement, import.meta.main gating, and top-level await strategies.
16
+
- Can optionally rewrite relative specifiers and write transformed output to disk.
17
+
12
18
> [!IMPORTANT]
13
19
> All parsing logic is applied under the assumption the code is in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) which [modules run under by default](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_classic_scripts).
14
20
@@ -18,9 +24,15 @@ By default `@knighted/module` transforms the one-to-one [differences between ES
You can transform it to the equivalent CommonJS module
55
+
Transform it to CommonJS:
44
56
45
57
```js
46
58
import { transform } from'@knighted/module'
@@ -51,7 +63,7 @@ await transform('./file.js', {
51
63
})
52
64
```
53
65
54
-
Which produces
66
+
Which produces:
55
67
56
68
**file.cjs**
57
69
@@ -99,6 +111,7 @@ type ModuleOptions = {
99
111
| ((value:string) => string |null|undefined)
100
112
dirFilename?:'inject'|'preserve'|'error'
101
113
importMeta?:'preserve'|'shim'|'error'
114
+
importMetaMain?:'shim'|'warn'|'error'
102
115
requireSource?:'builtin'|'create-require'
103
116
cjsDefault?:'module-exports'|'auto'|'none'
104
117
topLevelAwait?:'error'|'wrap'|'preserve'
@@ -107,7 +120,24 @@ type ModuleOptions = {
107
120
}
108
121
```
109
122
123
+
Behavior notes (defaults in parentheses)
124
+
125
+
- `target` (`commonjs`): output module system.
126
+
- `transformSyntax` (true): enable/disable the ESM↔CJS lowering pass.
127
+
- `liveBindings` (`strict`): getter-based live bindings, or snapshot (`loose`/`off`).
128
+
- `dirFilename` (`inject`): inject `__dirname`/`__filename`, preserve existing, or throw.
129
+
- `importMeta` (`shim`): rewrite `import.meta.*` to CommonJS equivalents.
130
+
- `importMetaMain` (`shim`): gate `import.meta.main` with shimming/warning/error when Node support is too old.
131
+
- `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output.
132
+
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback.
133
+
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
134
+
- `cjsDefault` (`auto`): bundler-style default interop vs direct `module.exports`.
135
+
- `out`/`inPlace`: write the transformed code to a file; otherwise the function returns the transformed string only.
136
+
137
+
See [docs/esm-to-cjs.md](docs/esm-to-cjs.md) for deeper notes on live bindings, interop helpers, top-level await behavior, and `import.meta.main` handling.
138
+
110
139
## Roadmap
111
140
112
141
- Remove `@knighted/specifier` and avoid double parsing.
113
-
- Flesh out live-binding and top-level await handling.
142
+
- Emit source maps and clearer diagnostics for transform choices.
143
+
- Broaden fixtures covering live-binding and top-level await edge cases across Node versions.
-`import` statements become `require` calls; side-effect imports become bare `require()` calls.
14
+
- Default imports use the bundler-style helper `__interopDefault = mod => (mod && mod.__esModule ? mod.default : mod)` when `cjsDefault: 'auto'`; otherwise they can target `module.exports` or `.default` directly per option.
15
+
- Namespace imports bind to the full `require` result; named imports destructure from the same binding.
16
+
- Re-exporting a default (`export { default as x } from`) routes through the interop helper to match bundler behavior for CJS sources.
17
+
- When the interop helper is emitted, `exports.__esModule = true` is also seeded for downstream consumers.
18
+
19
+
## Exports and live bindings
20
+
21
+
- Local named exports emit to `exports.<name>` (or bracket access) with `Object.defineProperty` getters when `liveBindings: 'strict'`; snapshot assignment is used for `loose`/`off`.
22
+
-`export default` writes to `module.exports = <expr>` in the common case; when TLA is present and allowed (`wrap`/`preserve`), default exports write to `exports.default = <expr>` so the exports object stays stable for async wrapping.
23
+
-`export * from` lowers to a `for...in` over the required module, skipping `default`, guarding on `hasOwnProperty`, and defining getters to mirror live bindings.
24
+
- Named re-exports from another module (`export { foo as bar } from`) use getters when `liveBindings: 'strict'`, otherwise snapshot assignment; `default` re-exports still use the interop helper.
25
+
26
+
## Top-level await
27
+
28
+
-`topLevelAwait: 'error'` (default) throws during transform when a TLA is found while targeting CommonJS.
29
+
-`topLevelAwait: 'wrap'` wraps the entire CJS output in `const __tla = (async () => { ...; return module.exports; })();` and attaches `__tla` to both `module.exports` and the resolved value. Consumers can await `require('./file').__tla` for readiness.
30
+
-`topLevelAwait: 'preserve'` runs the lowered code inside an immediately-invoked async function but does not attach a promise; consumers must rely on the natural scheduling of async effects.
31
+
- In both allowed modes, default exports write to `exports.default` instead of replacing `module.exports` to keep the exports object consistent while async initialization completes.
32
+
- Fixture: `test/fixtures/topLevelAwait.mjs` plus tests in `test/module.ts` cover both `wrap` and `preserve` behaviors.
-`import.meta.main` → configurable: default shim to `process.argv[1] === __filename`; with `importMetaMain: 'warn'` a warning is embedded for Node <22.18/24.2; with `importMetaMain: 'error'` the transform throws on those runtimes.
41
+
- Bare `import.meta` becomes `module` so property accesses stay valid in CJS output.
42
+
43
+
## Fixtures for this phase
44
+
45
+
-`test/fixtures/esmDefault.mjs`: default import from the CJS provider, re-exported as default for interop checks (default import yields the CJS module object).
46
+
-`test/fixtures/esmNamed.mjs`: named import and aliasing from the CJS provider, re-exported for assertions.
47
+
-`test/fixtures/esmNamespace.mjs`: namespace import shape from the CJS provider, re-exported as `ns`.
48
+
-`test/fixtures/esmReexport.mjs`: named + star re-exports from the CJS provider (includes live binding passthrough).
49
+
-`test/fixtures/liveReexport.mjs`: re-export from a mutating CJS source; exercised when `liveBindings: 'strict'` to verify getter-based live bindings.
50
+
-`test/fixtures/esmProvider.cjs`: CJS source exposing default/named exports plus a mutating `live` counter for binding checks (interval is `unref()`ed; tests wait ≥30ms to observe changes).
0 commit comments