@knighted/module 1.0.0-beta.1 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -4
- package/dist/cjs/format.cjs +270 -0
- package/dist/cjs/formatters/metaProperty.cjs +19 -1
- package/dist/cjs/module.cjs +1 -0
- package/dist/cjs/types.d.cts +1 -0
- package/dist/format.js +269 -0
- package/dist/formatters/metaProperty.js +19 -1
- package/dist/module.js +1 -0
- package/dist/src/types.d.ts +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
|
18
24
|
|
|
19
25
|
- Node >= 20.11.0
|
|
20
26
|
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @knighted/module
|
|
31
|
+
```
|
|
32
|
+
|
|
21
33
|
## Example
|
|
22
34
|
|
|
23
|
-
Given an ES module
|
|
35
|
+
Given an ES module:
|
|
24
36
|
|
|
25
37
|
**file.js**
|
|
26
38
|
|
|
@@ -40,7 +52,7 @@ const detectCalledFromCli = async path => {
|
|
|
40
52
|
detectCalledFromCli(argv[1])
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
|
|
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
|
-
-
|
|
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.
|
package/dist/cjs/format.cjs
CHANGED
|
@@ -15,6 +15,240 @@ var _identifiers = require("#utils/identifiers.js");
|
|
|
15
15
|
var _identifier2 = require("#helpers/identifier.js");
|
|
16
16
|
var _walk = require("#walk");
|
|
17
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
const isValidIdent = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
19
|
+
const exportAssignment = (name, expr, live) => {
|
|
20
|
+
const prop = isValidIdent(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
|
|
21
|
+
if (live === 'strict') {
|
|
22
|
+
const key = JSON.stringify(name);
|
|
23
|
+
return `Object.defineProperty(exports, ${key}, { enumerable: true, get: () => ${expr} });`;
|
|
24
|
+
}
|
|
25
|
+
return `exports${prop} = ${expr};`;
|
|
26
|
+
};
|
|
27
|
+
const defaultInteropName = '__interopDefault';
|
|
28
|
+
const interopHelper = `const ${defaultInteropName} = mod => (mod && mod.__esModule ? mod.default : mod);\n`;
|
|
29
|
+
const hasTopLevelAwait = program => {
|
|
30
|
+
let found = false;
|
|
31
|
+
const walkNode = (node, inFunction) => {
|
|
32
|
+
if (found) return;
|
|
33
|
+
switch (node.type) {
|
|
34
|
+
case 'FunctionDeclaration':
|
|
35
|
+
case 'FunctionExpression':
|
|
36
|
+
case 'ArrowFunctionExpression':
|
|
37
|
+
case 'ClassDeclaration':
|
|
38
|
+
case 'ClassExpression':
|
|
39
|
+
inFunction = true;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (!inFunction && node.type === 'AwaitExpression') {
|
|
43
|
+
found = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const keys = Object.keys(node);
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
const value = node[key];
|
|
49
|
+
if (!value) continue;
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
for (const item of value) {
|
|
52
|
+
if (item && typeof item === 'object') {
|
|
53
|
+
walkNode(item, inFunction);
|
|
54
|
+
if (found) return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else if (value && typeof value === 'object') {
|
|
58
|
+
walkNode(value, inFunction);
|
|
59
|
+
if (found) return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walkNode(program, false);
|
|
64
|
+
return found;
|
|
65
|
+
};
|
|
66
|
+
const lowerEsmToCjs = (program, code, opts, containsTopLevelAwait) => {
|
|
67
|
+
const live = opts.liveBindings ?? 'strict';
|
|
68
|
+
const importTransforms = [];
|
|
69
|
+
const exportTransforms = [];
|
|
70
|
+
let needsInterop = false;
|
|
71
|
+
let importIndex = 0;
|
|
72
|
+
for (const node of program.body) {
|
|
73
|
+
if (node.type === 'ImportDeclaration') {
|
|
74
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
75
|
+
const specifiers = node.specifiers ?? [];
|
|
76
|
+
const defaultSpec = specifiers.find(s => s.type === 'ImportDefaultSpecifier');
|
|
77
|
+
const namespaceSpec = specifiers.find(s => s.type === 'ImportNamespaceSpecifier');
|
|
78
|
+
const namedSpecs = specifiers.filter(s => s.type === 'ImportSpecifier');
|
|
79
|
+
|
|
80
|
+
// Side-effect import
|
|
81
|
+
if (!specifiers.length) {
|
|
82
|
+
importTransforms.push({
|
|
83
|
+
start: node.start,
|
|
84
|
+
end: node.end,
|
|
85
|
+
code: `require(${srcLiteral});\n`,
|
|
86
|
+
needsInterop: false
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const modIdent = `__mod${importIndex++}`;
|
|
91
|
+
const lines = [];
|
|
92
|
+
lines.push(`const ${modIdent} = require(${srcLiteral});`);
|
|
93
|
+
if (namespaceSpec) {
|
|
94
|
+
lines.push(`const ${namespaceSpec.local.name} = ${modIdent};`);
|
|
95
|
+
}
|
|
96
|
+
if (defaultSpec) {
|
|
97
|
+
let init = modIdent;
|
|
98
|
+
switch (opts.cjsDefault) {
|
|
99
|
+
case 'module-exports':
|
|
100
|
+
init = modIdent;
|
|
101
|
+
break;
|
|
102
|
+
case 'none':
|
|
103
|
+
init = `${modIdent}.default`;
|
|
104
|
+
break;
|
|
105
|
+
case 'auto':
|
|
106
|
+
default:
|
|
107
|
+
init = `${defaultInteropName}(${modIdent})`;
|
|
108
|
+
needsInterop = true;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
lines.push(`const ${defaultSpec.local.name} = ${init};`);
|
|
112
|
+
}
|
|
113
|
+
if (namedSpecs.length) {
|
|
114
|
+
const pairs = namedSpecs.map(s => {
|
|
115
|
+
const imported = s.imported.name;
|
|
116
|
+
const local = s.local.name;
|
|
117
|
+
return imported === local ? imported : `${imported}: ${local}`;
|
|
118
|
+
});
|
|
119
|
+
lines.push(`const { ${pairs.join(', ')} } = ${modIdent};`);
|
|
120
|
+
}
|
|
121
|
+
importTransforms.push({
|
|
122
|
+
start: node.start,
|
|
123
|
+
end: node.end,
|
|
124
|
+
code: `${lines.join('\n')}\n`,
|
|
125
|
+
needsInterop
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
129
|
+
// Handle declaration exports
|
|
130
|
+
if (node.declaration) {
|
|
131
|
+
const decl = node.declaration;
|
|
132
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
133
|
+
const exportedNames = [];
|
|
134
|
+
if (decl.type === 'VariableDeclaration') {
|
|
135
|
+
for (const d of decl.declarations) {
|
|
136
|
+
if (d.id.type === 'Identifier') {
|
|
137
|
+
exportedNames.push(d.id.name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else if (decl.id?.type === 'Identifier') {
|
|
141
|
+
exportedNames.push(decl.id.name);
|
|
142
|
+
}
|
|
143
|
+
const exportLines = exportedNames.map(name => exportAssignment(name, name, live));
|
|
144
|
+
exportTransforms.push({
|
|
145
|
+
start: node.start,
|
|
146
|
+
end: node.end,
|
|
147
|
+
code: `${declSrc}\n${exportLines.join('\n')}\n`
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Handle re-export or local specifiers
|
|
153
|
+
if (node.specifiers?.length) {
|
|
154
|
+
if (node.source) {
|
|
155
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
156
|
+
const modIdent = `__mod${importIndex++}`;
|
|
157
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`];
|
|
158
|
+
for (const spec of node.specifiers) {
|
|
159
|
+
if (spec.type !== 'ExportSpecifier') continue;
|
|
160
|
+
const exported = spec.exported.name;
|
|
161
|
+
const imported = spec.local.name;
|
|
162
|
+
let rhs = `${modIdent}.${imported}`;
|
|
163
|
+
if (imported === 'default') {
|
|
164
|
+
rhs = `${defaultInteropName}(${modIdent})`;
|
|
165
|
+
needsInterop = true;
|
|
166
|
+
}
|
|
167
|
+
lines.push(exportAssignment(exported, rhs, live));
|
|
168
|
+
}
|
|
169
|
+
exportTransforms.push({
|
|
170
|
+
start: node.start,
|
|
171
|
+
end: node.end,
|
|
172
|
+
code: `${lines.join('\n')}\n`,
|
|
173
|
+
needsInterop
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
const lines = [];
|
|
177
|
+
for (const spec of node.specifiers) {
|
|
178
|
+
if (spec.type !== 'ExportSpecifier') continue;
|
|
179
|
+
const exported = spec.exported.name;
|
|
180
|
+
const local = spec.local.name;
|
|
181
|
+
lines.push(exportAssignment(exported, local, live));
|
|
182
|
+
}
|
|
183
|
+
exportTransforms.push({
|
|
184
|
+
start: node.start,
|
|
185
|
+
end: node.end,
|
|
186
|
+
code: `${lines.join('\n')}\n`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
192
|
+
const decl = node.declaration;
|
|
193
|
+
const useExportsObject = containsTopLevelAwait && opts.topLevelAwait !== 'error';
|
|
194
|
+
if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') {
|
|
195
|
+
if (decl.id?.name) {
|
|
196
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
197
|
+
const assign = useExportsObject ? `exports.default = ${decl.id.name};` : `module.exports = ${decl.id.name};`;
|
|
198
|
+
exportTransforms.push({
|
|
199
|
+
start: node.start,
|
|
200
|
+
end: node.end,
|
|
201
|
+
code: `${declSrc}\n${assign}\n`
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
205
|
+
const assign = useExportsObject ? `exports.default = ${declSrc};` : `module.exports = ${declSrc};`;
|
|
206
|
+
exportTransforms.push({
|
|
207
|
+
start: node.start,
|
|
208
|
+
end: node.end,
|
|
209
|
+
code: `${assign}\n`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
const exprSrc = code.slice(decl.start, decl.end);
|
|
214
|
+
const assign = useExportsObject ? `exports.default = ${exprSrc};` : `module.exports = ${exprSrc};`;
|
|
215
|
+
exportTransforms.push({
|
|
216
|
+
start: node.start,
|
|
217
|
+
end: node.end,
|
|
218
|
+
code: `${assign}\n`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (node.type === 'ExportAllDeclaration') {
|
|
223
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
224
|
+
if (node.exported) {
|
|
225
|
+
const exported = node.exported.name;
|
|
226
|
+
const modIdent = `__mod${importIndex++}`;
|
|
227
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`, exportAssignment(exported, modIdent, live)];
|
|
228
|
+
exportTransforms.push({
|
|
229
|
+
start: node.start,
|
|
230
|
+
end: node.end,
|
|
231
|
+
code: `${lines.join('\n')}\n`
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
const modIdent = `__mod${importIndex++}`;
|
|
235
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`];
|
|
236
|
+
const loop = `for (const k in ${modIdent}) {\n if (k === 'default') continue;\n if (!Object.prototype.hasOwnProperty.call(${modIdent}, k)) continue;\n Object.defineProperty(exports, k, { enumerable: true, get: () => ${modIdent}[k] });\n}`;
|
|
237
|
+
lines.push(loop);
|
|
238
|
+
exportTransforms.push({
|
|
239
|
+
start: node.start,
|
|
240
|
+
end: node.end,
|
|
241
|
+
code: `${lines.join('\n')}\n`
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
importTransforms,
|
|
248
|
+
exportTransforms,
|
|
249
|
+
needsInterop
|
|
250
|
+
};
|
|
251
|
+
};
|
|
18
252
|
/**
|
|
19
253
|
* Node added support for import.meta.main.
|
|
20
254
|
* Added in: v24.2.0, v22.18.0
|
|
@@ -30,6 +264,13 @@ const format = async (src, ast, opts) => {
|
|
|
30
264
|
};
|
|
31
265
|
const exportTable = opts.target === 'module' ? await (0, _exports.collectCjsExports)(ast.program) : null;
|
|
32
266
|
await (0, _identifiers.collectModuleIdentifiers)(ast.program);
|
|
267
|
+
const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
|
|
268
|
+
const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
|
|
269
|
+
const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
|
|
270
|
+
let pendingCjsTransforms = null;
|
|
271
|
+
if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
|
|
272
|
+
throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
|
|
273
|
+
}
|
|
33
274
|
if (opts.target === 'module' && opts.transformSyntax) {
|
|
34
275
|
/**
|
|
35
276
|
* Prepare ESM output by renaming `exports` to `__exports` and seeding an
|
|
@@ -114,6 +355,25 @@ void import.meta.filename;
|
|
|
114
355
|
}
|
|
115
356
|
}
|
|
116
357
|
});
|
|
358
|
+
if (shouldLowerCjs) {
|
|
359
|
+
const {
|
|
360
|
+
importTransforms,
|
|
361
|
+
exportTransforms,
|
|
362
|
+
needsInterop
|
|
363
|
+
} = lowerEsmToCjs(ast.program, code, opts, containsTopLevelAwait);
|
|
364
|
+
pendingCjsTransforms = {
|
|
365
|
+
transforms: [...importTransforms, ...exportTransforms].sort((a, b) => a.start - b.start),
|
|
366
|
+
needsInterop
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (pendingCjsTransforms) {
|
|
370
|
+
for (const t of pendingCjsTransforms.transforms) {
|
|
371
|
+
code.overwrite(t.start, t.end, t.code);
|
|
372
|
+
}
|
|
373
|
+
if (pendingCjsTransforms.needsInterop) {
|
|
374
|
+
code.prepend(`${interopHelper}exports.__esModule = true;\n`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
117
377
|
if (opts.target === 'module' && opts.transformSyntax && exportTable) {
|
|
118
378
|
const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
119
379
|
const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
|
|
@@ -143,6 +403,16 @@ void import.meta.filename;
|
|
|
143
403
|
code.append(`\n${lines.join('\n')}\n`);
|
|
144
404
|
}
|
|
145
405
|
}
|
|
406
|
+
if (opts.target === 'commonjs' && opts.transformSyntax && containsTopLevelAwait) {
|
|
407
|
+
const body = code.toString();
|
|
408
|
+
if (opts.topLevelAwait === 'wrap') {
|
|
409
|
+
const tlaPromise = `const __tla = (async () => {\n${body}\nreturn module.exports;\n})();\n`;
|
|
410
|
+
const setPromise = `const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== 'object' && type !== 'function') return;\n target.__tla = __tla;\n};\n`;
|
|
411
|
+
const attach = `__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n`;
|
|
412
|
+
return `${tlaPromise}${setPromise}${attach}`;
|
|
413
|
+
}
|
|
414
|
+
return `;(async () => {\n${body}\n})();\n`;
|
|
415
|
+
}
|
|
146
416
|
return code.toString();
|
|
147
417
|
};
|
|
148
418
|
exports.format = format;
|
|
@@ -4,6 +4,19 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.metaProperty = void 0;
|
|
7
|
+
const importMetaMainSupport = '(() => { const [__nmaj, __nmin] = process.versions.node.split(".").map(n => parseInt(n, 10) || 0); return (__nmaj > 24 || (__nmaj === 24 && __nmin >= 2) || (__nmaj === 22 && __nmin >= 18)); })()';
|
|
8
|
+
const importMetaMainShim = 'process.argv[1] === __filename';
|
|
9
|
+
const importMetaMainExpr = mode => {
|
|
10
|
+
switch (mode) {
|
|
11
|
+
case 'warn':
|
|
12
|
+
return `(${importMetaMainSupport} ? ${importMetaMainShim} : (console.warn("import.meta.main is not supported before Node 22.18/24.2; falling back to shim."), ${importMetaMainShim}))`;
|
|
13
|
+
case 'error':
|
|
14
|
+
return `(${importMetaMainSupport} ? ${importMetaMainShim} : (() => { throw new Error("import.meta.main is not supported before Node 22.18/24.2"); })())`;
|
|
15
|
+
case 'shim':
|
|
16
|
+
default:
|
|
17
|
+
return importMetaMainShim;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
7
20
|
const metaProperty = (node, parent, src, options) => {
|
|
8
21
|
if (options.target === 'commonjs') {
|
|
9
22
|
if (parent?.type !== 'MemberExpression') {
|
|
@@ -27,10 +40,15 @@ const metaProperty = (node, parent, src, options) => {
|
|
|
27
40
|
break;
|
|
28
41
|
case 'resolve':
|
|
29
42
|
/**
|
|
30
|
-
*
|
|
43
|
+
* Map to require.resolve intentionally: matches CJS resolution semantics.
|
|
44
|
+
* Wrapping in pathToFileURL(...) would change the return shape (URL string)
|
|
45
|
+
* without truly emulating ESM import.meta.resolve rules.
|
|
31
46
|
*/
|
|
32
47
|
src.update(parent.start, parent.end, 'require.resolve');
|
|
33
48
|
break;
|
|
49
|
+
case 'main':
|
|
50
|
+
src.update(parent.start, parent.end, importMetaMainExpr(options.importMetaMain));
|
|
51
|
+
break;
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
54
|
}
|
package/dist/cjs/module.cjs
CHANGED
package/dist/cjs/types.d.cts
CHANGED
|
@@ -8,6 +8,7 @@ export type ModuleOptions = {
|
|
|
8
8
|
rewriteSpecifier?: RewriteSpecifier;
|
|
9
9
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
10
10
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
11
|
+
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
11
12
|
requireSource?: 'builtin' | 'create-require';
|
|
12
13
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
13
14
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
package/dist/format.js
CHANGED
|
@@ -8,7 +8,240 @@ import { exportsRename, collectCjsExports } from '#utils/exports.js';
|
|
|
8
8
|
import { collectModuleIdentifiers } from '#utils/identifiers.js';
|
|
9
9
|
import { isIdentifierName } from '#helpers/identifier.js';
|
|
10
10
|
import { ancestorWalk } from '#walk';
|
|
11
|
+
const isValidIdent = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
12
|
+
const exportAssignment = (name, expr, live) => {
|
|
13
|
+
const prop = isValidIdent(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
|
|
14
|
+
if (live === 'strict') {
|
|
15
|
+
const key = JSON.stringify(name);
|
|
16
|
+
return `Object.defineProperty(exports, ${key}, { enumerable: true, get: () => ${expr} });`;
|
|
17
|
+
}
|
|
18
|
+
return `exports${prop} = ${expr};`;
|
|
19
|
+
};
|
|
20
|
+
const defaultInteropName = '__interopDefault';
|
|
21
|
+
const interopHelper = `const ${defaultInteropName} = mod => (mod && mod.__esModule ? mod.default : mod);\n`;
|
|
22
|
+
const hasTopLevelAwait = program => {
|
|
23
|
+
let found = false;
|
|
24
|
+
const walkNode = (node, inFunction) => {
|
|
25
|
+
if (found) return;
|
|
26
|
+
switch (node.type) {
|
|
27
|
+
case 'FunctionDeclaration':
|
|
28
|
+
case 'FunctionExpression':
|
|
29
|
+
case 'ArrowFunctionExpression':
|
|
30
|
+
case 'ClassDeclaration':
|
|
31
|
+
case 'ClassExpression':
|
|
32
|
+
inFunction = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (!inFunction && node.type === 'AwaitExpression') {
|
|
36
|
+
found = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const keys = Object.keys(node);
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
const value = node[key];
|
|
42
|
+
if (!value) continue;
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (const item of value) {
|
|
45
|
+
if (item && typeof item === 'object') {
|
|
46
|
+
walkNode(item, inFunction);
|
|
47
|
+
if (found) return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} else if (value && typeof value === 'object') {
|
|
51
|
+
walkNode(value, inFunction);
|
|
52
|
+
if (found) return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
walkNode(program, false);
|
|
57
|
+
return found;
|
|
58
|
+
};
|
|
59
|
+
const lowerEsmToCjs = (program, code, opts, containsTopLevelAwait) => {
|
|
60
|
+
const live = opts.liveBindings ?? 'strict';
|
|
61
|
+
const importTransforms = [];
|
|
62
|
+
const exportTransforms = [];
|
|
63
|
+
let needsInterop = false;
|
|
64
|
+
let importIndex = 0;
|
|
65
|
+
for (const node of program.body) {
|
|
66
|
+
if (node.type === 'ImportDeclaration') {
|
|
67
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
68
|
+
const specifiers = node.specifiers ?? [];
|
|
69
|
+
const defaultSpec = specifiers.find(s => s.type === 'ImportDefaultSpecifier');
|
|
70
|
+
const namespaceSpec = specifiers.find(s => s.type === 'ImportNamespaceSpecifier');
|
|
71
|
+
const namedSpecs = specifiers.filter(s => s.type === 'ImportSpecifier');
|
|
11
72
|
|
|
73
|
+
// Side-effect import
|
|
74
|
+
if (!specifiers.length) {
|
|
75
|
+
importTransforms.push({
|
|
76
|
+
start: node.start,
|
|
77
|
+
end: node.end,
|
|
78
|
+
code: `require(${srcLiteral});\n`,
|
|
79
|
+
needsInterop: false
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const modIdent = `__mod${importIndex++}`;
|
|
84
|
+
const lines = [];
|
|
85
|
+
lines.push(`const ${modIdent} = require(${srcLiteral});`);
|
|
86
|
+
if (namespaceSpec) {
|
|
87
|
+
lines.push(`const ${namespaceSpec.local.name} = ${modIdent};`);
|
|
88
|
+
}
|
|
89
|
+
if (defaultSpec) {
|
|
90
|
+
let init = modIdent;
|
|
91
|
+
switch (opts.cjsDefault) {
|
|
92
|
+
case 'module-exports':
|
|
93
|
+
init = modIdent;
|
|
94
|
+
break;
|
|
95
|
+
case 'none':
|
|
96
|
+
init = `${modIdent}.default`;
|
|
97
|
+
break;
|
|
98
|
+
case 'auto':
|
|
99
|
+
default:
|
|
100
|
+
init = `${defaultInteropName}(${modIdent})`;
|
|
101
|
+
needsInterop = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
lines.push(`const ${defaultSpec.local.name} = ${init};`);
|
|
105
|
+
}
|
|
106
|
+
if (namedSpecs.length) {
|
|
107
|
+
const pairs = namedSpecs.map(s => {
|
|
108
|
+
const imported = s.imported.name;
|
|
109
|
+
const local = s.local.name;
|
|
110
|
+
return imported === local ? imported : `${imported}: ${local}`;
|
|
111
|
+
});
|
|
112
|
+
lines.push(`const { ${pairs.join(', ')} } = ${modIdent};`);
|
|
113
|
+
}
|
|
114
|
+
importTransforms.push({
|
|
115
|
+
start: node.start,
|
|
116
|
+
end: node.end,
|
|
117
|
+
code: `${lines.join('\n')}\n`,
|
|
118
|
+
needsInterop
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
122
|
+
// Handle declaration exports
|
|
123
|
+
if (node.declaration) {
|
|
124
|
+
const decl = node.declaration;
|
|
125
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
126
|
+
const exportedNames = [];
|
|
127
|
+
if (decl.type === 'VariableDeclaration') {
|
|
128
|
+
for (const d of decl.declarations) {
|
|
129
|
+
if (d.id.type === 'Identifier') {
|
|
130
|
+
exportedNames.push(d.id.name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else if (decl.id?.type === 'Identifier') {
|
|
134
|
+
exportedNames.push(decl.id.name);
|
|
135
|
+
}
|
|
136
|
+
const exportLines = exportedNames.map(name => exportAssignment(name, name, live));
|
|
137
|
+
exportTransforms.push({
|
|
138
|
+
start: node.start,
|
|
139
|
+
end: node.end,
|
|
140
|
+
code: `${declSrc}\n${exportLines.join('\n')}\n`
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle re-export or local specifiers
|
|
146
|
+
if (node.specifiers?.length) {
|
|
147
|
+
if (node.source) {
|
|
148
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
149
|
+
const modIdent = `__mod${importIndex++}`;
|
|
150
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`];
|
|
151
|
+
for (const spec of node.specifiers) {
|
|
152
|
+
if (spec.type !== 'ExportSpecifier') continue;
|
|
153
|
+
const exported = spec.exported.name;
|
|
154
|
+
const imported = spec.local.name;
|
|
155
|
+
let rhs = `${modIdent}.${imported}`;
|
|
156
|
+
if (imported === 'default') {
|
|
157
|
+
rhs = `${defaultInteropName}(${modIdent})`;
|
|
158
|
+
needsInterop = true;
|
|
159
|
+
}
|
|
160
|
+
lines.push(exportAssignment(exported, rhs, live));
|
|
161
|
+
}
|
|
162
|
+
exportTransforms.push({
|
|
163
|
+
start: node.start,
|
|
164
|
+
end: node.end,
|
|
165
|
+
code: `${lines.join('\n')}\n`,
|
|
166
|
+
needsInterop
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
const lines = [];
|
|
170
|
+
for (const spec of node.specifiers) {
|
|
171
|
+
if (spec.type !== 'ExportSpecifier') continue;
|
|
172
|
+
const exported = spec.exported.name;
|
|
173
|
+
const local = spec.local.name;
|
|
174
|
+
lines.push(exportAssignment(exported, local, live));
|
|
175
|
+
}
|
|
176
|
+
exportTransforms.push({
|
|
177
|
+
start: node.start,
|
|
178
|
+
end: node.end,
|
|
179
|
+
code: `${lines.join('\n')}\n`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
185
|
+
const decl = node.declaration;
|
|
186
|
+
const useExportsObject = containsTopLevelAwait && opts.topLevelAwait !== 'error';
|
|
187
|
+
if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') {
|
|
188
|
+
if (decl.id?.name) {
|
|
189
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
190
|
+
const assign = useExportsObject ? `exports.default = ${decl.id.name};` : `module.exports = ${decl.id.name};`;
|
|
191
|
+
exportTransforms.push({
|
|
192
|
+
start: node.start,
|
|
193
|
+
end: node.end,
|
|
194
|
+
code: `${declSrc}\n${assign}\n`
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
const declSrc = code.slice(decl.start, decl.end);
|
|
198
|
+
const assign = useExportsObject ? `exports.default = ${declSrc};` : `module.exports = ${declSrc};`;
|
|
199
|
+
exportTransforms.push({
|
|
200
|
+
start: node.start,
|
|
201
|
+
end: node.end,
|
|
202
|
+
code: `${assign}\n`
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const exprSrc = code.slice(decl.start, decl.end);
|
|
207
|
+
const assign = useExportsObject ? `exports.default = ${exprSrc};` : `module.exports = ${exprSrc};`;
|
|
208
|
+
exportTransforms.push({
|
|
209
|
+
start: node.start,
|
|
210
|
+
end: node.end,
|
|
211
|
+
code: `${assign}\n`
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (node.type === 'ExportAllDeclaration') {
|
|
216
|
+
const srcLiteral = code.slice(node.source.start, node.source.end);
|
|
217
|
+
if (node.exported) {
|
|
218
|
+
const exported = node.exported.name;
|
|
219
|
+
const modIdent = `__mod${importIndex++}`;
|
|
220
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`, exportAssignment(exported, modIdent, live)];
|
|
221
|
+
exportTransforms.push({
|
|
222
|
+
start: node.start,
|
|
223
|
+
end: node.end,
|
|
224
|
+
code: `${lines.join('\n')}\n`
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
const modIdent = `__mod${importIndex++}`;
|
|
228
|
+
const lines = [`const ${modIdent} = require(${srcLiteral});`];
|
|
229
|
+
const loop = `for (const k in ${modIdent}) {\n if (k === 'default') continue;\n if (!Object.prototype.hasOwnProperty.call(${modIdent}, k)) continue;\n Object.defineProperty(exports, k, { enumerable: true, get: () => ${modIdent}[k] });\n}`;
|
|
230
|
+
lines.push(loop);
|
|
231
|
+
exportTransforms.push({
|
|
232
|
+
start: node.start,
|
|
233
|
+
end: node.end,
|
|
234
|
+
code: `${lines.join('\n')}\n`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
importTransforms,
|
|
241
|
+
exportTransforms,
|
|
242
|
+
needsInterop
|
|
243
|
+
};
|
|
244
|
+
};
|
|
12
245
|
/**
|
|
13
246
|
* Node added support for import.meta.main.
|
|
14
247
|
* Added in: v24.2.0, v22.18.0
|
|
@@ -24,6 +257,13 @@ const format = async (src, ast, opts) => {
|
|
|
24
257
|
};
|
|
25
258
|
const exportTable = opts.target === 'module' ? await collectCjsExports(ast.program) : null;
|
|
26
259
|
await collectModuleIdentifiers(ast.program);
|
|
260
|
+
const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
|
|
261
|
+
const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
|
|
262
|
+
const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
|
|
263
|
+
let pendingCjsTransforms = null;
|
|
264
|
+
if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
|
|
265
|
+
throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
|
|
266
|
+
}
|
|
27
267
|
if (opts.target === 'module' && opts.transformSyntax) {
|
|
28
268
|
/**
|
|
29
269
|
* Prepare ESM output by renaming `exports` to `__exports` and seeding an
|
|
@@ -108,6 +348,25 @@ void import.meta.filename;
|
|
|
108
348
|
}
|
|
109
349
|
}
|
|
110
350
|
});
|
|
351
|
+
if (shouldLowerCjs) {
|
|
352
|
+
const {
|
|
353
|
+
importTransforms,
|
|
354
|
+
exportTransforms,
|
|
355
|
+
needsInterop
|
|
356
|
+
} = lowerEsmToCjs(ast.program, code, opts, containsTopLevelAwait);
|
|
357
|
+
pendingCjsTransforms = {
|
|
358
|
+
transforms: [...importTransforms, ...exportTransforms].sort((a, b) => a.start - b.start),
|
|
359
|
+
needsInterop
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (pendingCjsTransforms) {
|
|
363
|
+
for (const t of pendingCjsTransforms.transforms) {
|
|
364
|
+
code.overwrite(t.start, t.end, t.code);
|
|
365
|
+
}
|
|
366
|
+
if (pendingCjsTransforms.needsInterop) {
|
|
367
|
+
code.prepend(`${interopHelper}exports.__esModule = true;\n`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
111
370
|
if (opts.target === 'module' && opts.transformSyntax && exportTable) {
|
|
112
371
|
const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
113
372
|
const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
|
|
@@ -137,6 +396,16 @@ void import.meta.filename;
|
|
|
137
396
|
code.append(`\n${lines.join('\n')}\n`);
|
|
138
397
|
}
|
|
139
398
|
}
|
|
399
|
+
if (opts.target === 'commonjs' && opts.transformSyntax && containsTopLevelAwait) {
|
|
400
|
+
const body = code.toString();
|
|
401
|
+
if (opts.topLevelAwait === 'wrap') {
|
|
402
|
+
const tlaPromise = `const __tla = (async () => {\n${body}\nreturn module.exports;\n})();\n`;
|
|
403
|
+
const setPromise = `const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== 'object' && type !== 'function') return;\n target.__tla = __tla;\n};\n`;
|
|
404
|
+
const attach = `__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n`;
|
|
405
|
+
return `${tlaPromise}${setPromise}${attach}`;
|
|
406
|
+
}
|
|
407
|
+
return `;(async () => {\n${body}\n})();\n`;
|
|
408
|
+
}
|
|
140
409
|
return code.toString();
|
|
141
410
|
};
|
|
142
411
|
export { format };
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
const importMetaMainSupport = '(() => { const [__nmaj, __nmin] = process.versions.node.split(".").map(n => parseInt(n, 10) || 0); return (__nmaj > 24 || (__nmaj === 24 && __nmin >= 2) || (__nmaj === 22 && __nmin >= 18)); })()';
|
|
2
|
+
const importMetaMainShim = 'process.argv[1] === __filename';
|
|
3
|
+
const importMetaMainExpr = mode => {
|
|
4
|
+
switch (mode) {
|
|
5
|
+
case 'warn':
|
|
6
|
+
return `(${importMetaMainSupport} ? ${importMetaMainShim} : (console.warn("import.meta.main is not supported before Node 22.18/24.2; falling back to shim."), ${importMetaMainShim}))`;
|
|
7
|
+
case 'error':
|
|
8
|
+
return `(${importMetaMainSupport} ? ${importMetaMainShim} : (() => { throw new Error("import.meta.main is not supported before Node 22.18/24.2"); })())`;
|
|
9
|
+
case 'shim':
|
|
10
|
+
default:
|
|
11
|
+
return importMetaMainShim;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
1
14
|
export const metaProperty = (node, parent, src, options) => {
|
|
2
15
|
if (options.target === 'commonjs') {
|
|
3
16
|
if (parent?.type !== 'MemberExpression') {
|
|
@@ -21,10 +34,15 @@ export const metaProperty = (node, parent, src, options) => {
|
|
|
21
34
|
break;
|
|
22
35
|
case 'resolve':
|
|
23
36
|
/**
|
|
24
|
-
*
|
|
37
|
+
* Map to require.resolve intentionally: matches CJS resolution semantics.
|
|
38
|
+
* Wrapping in pathToFileURL(...) would change the return shape (URL string)
|
|
39
|
+
* without truly emulating ESM import.meta.resolve rules.
|
|
25
40
|
*/
|
|
26
41
|
src.update(parent.start, parent.end, 'require.resolve');
|
|
27
42
|
break;
|
|
43
|
+
case 'main':
|
|
44
|
+
src.update(parent.start, parent.end, importMetaMainExpr(options.importMetaMain));
|
|
45
|
+
break;
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
48
|
}
|
package/dist/module.js
CHANGED
package/dist/src/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type ModuleOptions = {
|
|
|
8
8
|
rewriteSpecifier?: RewriteSpecifier;
|
|
9
9
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
10
10
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
11
|
+
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
11
12
|
requireSource?: 'builtin' | 'create-require';
|
|
12
13
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
13
14
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type ModuleOptions = {
|
|
|
8
8
|
rewriteSpecifier?: RewriteSpecifier;
|
|
9
9
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
10
10
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
11
|
+
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
11
12
|
requireSource?: 'builtin' | 'create-require';
|
|
12
13
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
13
14
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|