@knighted/module 1.0.0-rc.3 → 1.0.0-rc.5
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 +30 -0
- package/dist/cjs/format.cjs +82 -3
- package/dist/cjs/formatters/memberExpression.cjs +17 -0
- package/dist/cjs/memberExpression.d.cts +4 -0
- package/dist/cjs/module.cjs +2 -1
- package/dist/cjs/types.d.cts +14 -0
- package/dist/format.js +82 -3
- package/dist/formatters/memberExpression.js +17 -0
- package/dist/memberExpression.d.cts +4 -0
- package/dist/memberExpression.d.ts +4 -0
- package/dist/module.js +2 -1
- package/dist/src/formatters/memberExpression.d.ts +4 -0
- package/dist/src/types.d.ts +14 -0
- package/dist/types.d.cts +14 -0
- package/dist/types.d.ts +14 -0
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -151,6 +151,36 @@ See [docs/esm-to-cjs.md](docs/esm-to-cjs.md) for deeper notes on live bindings,
|
|
|
151
151
|
> [!NOTE]
|
|
152
152
|
> Known limitations: `with` and unshadowed `eval` are rejected when raising CJS to ESM because the rewrite would be unsound; bare specifiers are not rewritten—only relative specifiers participate in `rewriteSpecifier`.
|
|
153
153
|
|
|
154
|
+
### Diagnostics callback example
|
|
155
|
+
|
|
156
|
+
Pass a `diagnostics` callback to surface CJS→ESM edge cases (mixed `module.exports`/`exports`, top-level `return`, legacy `require.cache`/`require.extensions`, live-binding reassignments, string-literal export names):
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { transform } from '@knighted/module'
|
|
160
|
+
|
|
161
|
+
const diagnostics: any[] = []
|
|
162
|
+
|
|
163
|
+
await transform('./file.cjs', {
|
|
164
|
+
target: 'module',
|
|
165
|
+
diagnostics: diag => diagnostics.push(diag),
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
console.log(diagnostics)
|
|
169
|
+
// [
|
|
170
|
+
// {
|
|
171
|
+
// level: 'warning',
|
|
172
|
+
// code: 'cjs-mixed-exports',
|
|
173
|
+
// message: 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.',
|
|
174
|
+
// filePath: './file.cjs',
|
|
175
|
+
// loc: { start: 12, end: 48 }
|
|
176
|
+
// },
|
|
177
|
+
// ...
|
|
178
|
+
// ]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
> [!WARNING]
|
|
182
|
+
> When raising CommonJS to ESM, synthesized named exports rely on literal keys and `const` literal aliases (e.g., `const key = 'foo'; exports[key] = value`). `var`/`let` bindings used as export keys are not tracked, so prefer direct property names or `const` literals when exporting.
|
|
183
|
+
|
|
154
184
|
## Pre-`tsc` transforms for TypeScript diagnostics
|
|
155
185
|
|
|
156
186
|
TypeScript reports asymmetric module-global errors (e.g., `import.meta` in CJS, `__dirname` in ESM) as tracked in [microsoft/TypeScript#58658](https://github.com/microsoft/TypeScript/issues/58658). You can mitigate this by running `@knighted/module` **before** `tsc` so the checker sees already-rewritten sources.
|
package/dist/cjs/format.cjs
CHANGED
|
@@ -370,6 +370,33 @@ const format = async (src, ast, opts) => {
|
|
|
370
370
|
hasDefaultExportBeenReassigned: false,
|
|
371
371
|
hasDefaultExportBeenAssigned: false
|
|
372
372
|
};
|
|
373
|
+
const warned = new Set();
|
|
374
|
+
const emitDiagnostic = diag => {
|
|
375
|
+
if (opts.diagnostics) {
|
|
376
|
+
opts.diagnostics(diag);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (diag.level === 'warning') {
|
|
380
|
+
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
381
|
+
console.warn(diag.message);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
386
|
+
console.error(diag.message);
|
|
387
|
+
};
|
|
388
|
+
const warnOnce = (codeId, message, loc) => {
|
|
389
|
+
const key = `${codeId}:${loc?.start ?? ''}`;
|
|
390
|
+
if (warned.has(key)) return;
|
|
391
|
+
warned.add(key);
|
|
392
|
+
emitDiagnostic({
|
|
393
|
+
level: 'warning',
|
|
394
|
+
code: codeId,
|
|
395
|
+
message,
|
|
396
|
+
filePath: opts.filePath,
|
|
397
|
+
loc
|
|
398
|
+
});
|
|
399
|
+
};
|
|
373
400
|
const moduleIdentifiers = await (0, _identifiers.collectModuleIdentifiers)(ast.program);
|
|
374
401
|
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
375
402
|
if (opts.target === 'module' && opts.transformSyntax) {
|
|
@@ -378,6 +405,18 @@ const format = async (src, ast, opts) => {
|
|
|
378
405
|
}
|
|
379
406
|
}
|
|
380
407
|
const exportTable = opts.target === 'module' ? await (0, _exports.collectCjsExports)(ast.program) : null;
|
|
408
|
+
if (opts.target === 'module' && exportTable) {
|
|
409
|
+
const hasExportsVia = [...exportTable.values()].some(entry => entry.via.has('exports'));
|
|
410
|
+
const hasModuleExportsVia = [...exportTable.values()].some(entry => entry.via.has('module.exports'));
|
|
411
|
+
if (hasExportsVia && hasModuleExportsVia) {
|
|
412
|
+
const firstExports = [...exportTable.values()].find(entry => entry.via.has('exports'))?.writes[0];
|
|
413
|
+
const firstModule = [...exportTable.values()].find(entry => entry.via.has('module.exports'))?.writes[0];
|
|
414
|
+
warnOnce('cjs-mixed-exports', 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.', {
|
|
415
|
+
start: firstModule?.start ?? 0,
|
|
416
|
+
end: firstExports?.end ?? 0
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
381
420
|
const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
|
|
382
421
|
const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
|
|
383
422
|
const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
|
|
@@ -412,6 +451,12 @@ const format = async (src, ast, opts) => {
|
|
|
412
451
|
await (0, _walk.ancestorWalk)(ast.program, {
|
|
413
452
|
async enter(node, ancestors) {
|
|
414
453
|
const parent = ancestors[ancestors.length - 2] ?? null;
|
|
454
|
+
if (shouldRaiseEsm && node.type === 'ReturnStatement' && parent?.type === 'Program') {
|
|
455
|
+
warnOnce('top-level-return', 'Top-level return is not allowed in ESM; the transformed module will fail to parse.', {
|
|
456
|
+
start: node.start,
|
|
457
|
+
end: node.end
|
|
458
|
+
});
|
|
459
|
+
}
|
|
415
460
|
if (shouldRaiseEsm && node.type === 'BinaryExpression') {
|
|
416
461
|
const op = node.operator;
|
|
417
462
|
const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
|
|
@@ -525,7 +570,10 @@ const format = async (src, ast, opts) => {
|
|
|
525
570
|
onRequireResolve: () => {
|
|
526
571
|
if (shouldRaiseEsm) needsRequireResolveHelper = true;
|
|
527
572
|
},
|
|
528
|
-
requireResolveName: '__requireResolve'
|
|
573
|
+
requireResolveName: '__requireResolve',
|
|
574
|
+
onDiagnostic: (codeId, message, loc) => {
|
|
575
|
+
if (shouldRaiseEsm) warnOnce(codeId, message, loc);
|
|
576
|
+
}
|
|
529
577
|
});
|
|
530
578
|
}
|
|
531
579
|
if (shouldRaiseEsm && node.type === 'ThisExpression') {
|
|
@@ -579,21 +627,52 @@ const format = async (src, ast, opts) => {
|
|
|
579
627
|
const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
580
628
|
const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
|
|
581
629
|
const accessProp = name => isValidExportName(name) ? `${_exports.exportsRename}.${name}` : `${_exports.exportsRename}[${JSON.stringify(name)}]`;
|
|
630
|
+
const exportValueFor = name => {
|
|
631
|
+
if (name === '__dirname') return 'import.meta.dirname';
|
|
632
|
+
if (name === '__filename') return 'import.meta.filename';
|
|
633
|
+
return name;
|
|
634
|
+
};
|
|
582
635
|
const tempNameFor = name => {
|
|
583
636
|
const sanitized = name.replace(/[^$\w]/g, '_') || 'value';
|
|
584
637
|
const safe = /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;
|
|
585
638
|
return `__export_${safe}`;
|
|
586
639
|
};
|
|
640
|
+
for (const [key, entry] of exportTable) {
|
|
641
|
+
if (entry.reassignments.length) {
|
|
642
|
+
const loc = entry.reassignments[0];
|
|
643
|
+
warnOnce(`cjs-export-reassignment:${key}`, `Export '${key}' is reassigned after export; ESM live bindings may change consumer behavior.`, {
|
|
644
|
+
start: loc.start,
|
|
645
|
+
end: loc.end
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
587
649
|
const lines = [];
|
|
588
650
|
const defaultEntry = exportTable.get('default');
|
|
589
651
|
if (defaultEntry) {
|
|
590
652
|
const def = defaultEntry.fromIdentifier ?? _exports.exportsRename;
|
|
591
|
-
|
|
653
|
+
const defExpr = exportValueFor(def);
|
|
654
|
+
if (defExpr !== def) {
|
|
655
|
+
const temp = tempNameFor(def);
|
|
656
|
+
lines.push(`const ${temp} = ${defExpr};`);
|
|
657
|
+
lines.push(`export default ${temp};`);
|
|
658
|
+
} else {
|
|
659
|
+
lines.push(`export default ${defExpr};`);
|
|
660
|
+
}
|
|
592
661
|
}
|
|
593
662
|
for (const [key, entry] of exportTable) {
|
|
594
663
|
if (key === 'default') continue;
|
|
664
|
+
if (!isValidExportName(key)) {
|
|
665
|
+
warnOnce(`cjs-string-export:${key}`, `Synthesized string-literal export '${key}'. Some tooling may require bracket access to use it.`);
|
|
666
|
+
}
|
|
595
667
|
if (entry.fromIdentifier) {
|
|
596
|
-
|
|
668
|
+
const resolved = exportValueFor(entry.fromIdentifier);
|
|
669
|
+
if (resolved !== entry.fromIdentifier) {
|
|
670
|
+
const temp = tempNameFor(entry.fromIdentifier);
|
|
671
|
+
lines.push(`const ${temp} = ${resolved};`);
|
|
672
|
+
lines.push(`export { ${temp} as ${asExportName(key)} };`);
|
|
673
|
+
} else {
|
|
674
|
+
lines.push(`export { ${resolved} as ${asExportName(key)} };`);
|
|
675
|
+
}
|
|
597
676
|
} else {
|
|
598
677
|
const temp = tempNameFor(key);
|
|
599
678
|
lines.push(`const ${temp} = ${accessProp(key)};`);
|
|
@@ -40,14 +40,31 @@ const memberExpression = (node, parent, src, options, shadowed, extras) => {
|
|
|
40
40
|
* Can of worms here. ¯\_(ツ)_/¯
|
|
41
41
|
* @see https://github.com/nodejs/help/issues/2806
|
|
42
42
|
*/
|
|
43
|
+
extras?.onDiagnostic?.('legacy-require-cache', 'Access to require.cache is not supported when raising to ESM; behavior may differ.', {
|
|
44
|
+
start,
|
|
45
|
+
end
|
|
46
|
+
});
|
|
43
47
|
src.update(start, end, '{}');
|
|
44
48
|
break;
|
|
49
|
+
case 'extensions':
|
|
50
|
+
extras?.onDiagnostic?.('legacy-require-extensions', 'Access to require.extensions is not supported when raising to ESM; use loaders instead.', {
|
|
51
|
+
start,
|
|
52
|
+
end
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
|
|
48
58
|
if (!shadowed?.has('module')) {
|
|
49
59
|
src.update(node.start, node.end, 'require');
|
|
50
60
|
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && (node.property.name === 'parent' || node.property.name === 'children')) {
|
|
64
|
+
extras?.onDiagnostic?.(`legacy-module-${node.property.name}`, `Access to module.${node.property.name} may not behave the same in ESM; consider loaders or explicit wiring instead.`, {
|
|
65
|
+
start: node.start,
|
|
66
|
+
end: node.end
|
|
67
|
+
});
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
70
|
};
|
|
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.cjs';
|
|
|
4
4
|
type MemberExpressionExtras = {
|
|
5
5
|
onRequireResolve?: () => void;
|
|
6
6
|
requireResolveName?: string;
|
|
7
|
+
onDiagnostic?: (code: string, message: string, loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}) => void;
|
|
7
11
|
};
|
|
8
12
|
export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
|
|
9
13
|
export {};
|
package/dist/cjs/module.cjs
CHANGED
|
@@ -167,7 +167,8 @@ const defaultOptions = {
|
|
|
167
167
|
const transform = async (filename, options = defaultOptions) => {
|
|
168
168
|
const opts = {
|
|
169
169
|
...defaultOptions,
|
|
170
|
-
...options
|
|
170
|
+
...options,
|
|
171
|
+
filePath: filename
|
|
171
172
|
};
|
|
172
173
|
const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
173
174
|
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -34,11 +34,25 @@ export type ModuleOptions = {
|
|
|
34
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
35
|
/** Handling for top-level await constructs. */
|
|
36
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Optional diagnostics sink for warnings/errors emitted during transform. */
|
|
38
|
+
diagnostics?: (diag: Diagnostic) => void;
|
|
39
|
+
/** Optional source file path used for diagnostics context. */
|
|
40
|
+
filePath?: string;
|
|
37
41
|
/** Output directory or file path when writing. */
|
|
38
42
|
out?: string;
|
|
39
43
|
/** Overwrite input files instead of writing to out. */
|
|
40
44
|
inPlace?: boolean;
|
|
41
45
|
};
|
|
46
|
+
export type Diagnostic = {
|
|
47
|
+
level: 'warning' | 'error';
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
filePath?: string;
|
|
51
|
+
loc?: {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
42
56
|
export type SpannedNode = Node & Span;
|
|
43
57
|
export type ExportsMeta = {
|
|
44
58
|
hasExportsBeenReassigned: boolean;
|
package/dist/format.js
CHANGED
|
@@ -363,6 +363,33 @@ const format = async (src, ast, opts) => {
|
|
|
363
363
|
hasDefaultExportBeenReassigned: false,
|
|
364
364
|
hasDefaultExportBeenAssigned: false
|
|
365
365
|
};
|
|
366
|
+
const warned = new Set();
|
|
367
|
+
const emitDiagnostic = diag => {
|
|
368
|
+
if (opts.diagnostics) {
|
|
369
|
+
opts.diagnostics(diag);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (diag.level === 'warning') {
|
|
373
|
+
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
374
|
+
console.warn(diag.message);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
379
|
+
console.error(diag.message);
|
|
380
|
+
};
|
|
381
|
+
const warnOnce = (codeId, message, loc) => {
|
|
382
|
+
const key = `${codeId}:${loc?.start ?? ''}`;
|
|
383
|
+
if (warned.has(key)) return;
|
|
384
|
+
warned.add(key);
|
|
385
|
+
emitDiagnostic({
|
|
386
|
+
level: 'warning',
|
|
387
|
+
code: codeId,
|
|
388
|
+
message,
|
|
389
|
+
filePath: opts.filePath,
|
|
390
|
+
loc
|
|
391
|
+
});
|
|
392
|
+
};
|
|
366
393
|
const moduleIdentifiers = await collectModuleIdentifiers(ast.program);
|
|
367
394
|
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
368
395
|
if (opts.target === 'module' && opts.transformSyntax) {
|
|
@@ -371,6 +398,18 @@ const format = async (src, ast, opts) => {
|
|
|
371
398
|
}
|
|
372
399
|
}
|
|
373
400
|
const exportTable = opts.target === 'module' ? await collectCjsExports(ast.program) : null;
|
|
401
|
+
if (opts.target === 'module' && exportTable) {
|
|
402
|
+
const hasExportsVia = [...exportTable.values()].some(entry => entry.via.has('exports'));
|
|
403
|
+
const hasModuleExportsVia = [...exportTable.values()].some(entry => entry.via.has('module.exports'));
|
|
404
|
+
if (hasExportsVia && hasModuleExportsVia) {
|
|
405
|
+
const firstExports = [...exportTable.values()].find(entry => entry.via.has('exports'))?.writes[0];
|
|
406
|
+
const firstModule = [...exportTable.values()].find(entry => entry.via.has('module.exports'))?.writes[0];
|
|
407
|
+
warnOnce('cjs-mixed-exports', 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.', {
|
|
408
|
+
start: firstModule?.start ?? 0,
|
|
409
|
+
end: firstExports?.end ?? 0
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
374
413
|
const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
|
|
375
414
|
const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
|
|
376
415
|
const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
|
|
@@ -405,6 +444,12 @@ const format = async (src, ast, opts) => {
|
|
|
405
444
|
await ancestorWalk(ast.program, {
|
|
406
445
|
async enter(node, ancestors) {
|
|
407
446
|
const parent = ancestors[ancestors.length - 2] ?? null;
|
|
447
|
+
if (shouldRaiseEsm && node.type === 'ReturnStatement' && parent?.type === 'Program') {
|
|
448
|
+
warnOnce('top-level-return', 'Top-level return is not allowed in ESM; the transformed module will fail to parse.', {
|
|
449
|
+
start: node.start,
|
|
450
|
+
end: node.end
|
|
451
|
+
});
|
|
452
|
+
}
|
|
408
453
|
if (shouldRaiseEsm && node.type === 'BinaryExpression') {
|
|
409
454
|
const op = node.operator;
|
|
410
455
|
const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
|
|
@@ -518,7 +563,10 @@ const format = async (src, ast, opts) => {
|
|
|
518
563
|
onRequireResolve: () => {
|
|
519
564
|
if (shouldRaiseEsm) needsRequireResolveHelper = true;
|
|
520
565
|
},
|
|
521
|
-
requireResolveName: '__requireResolve'
|
|
566
|
+
requireResolveName: '__requireResolve',
|
|
567
|
+
onDiagnostic: (codeId, message, loc) => {
|
|
568
|
+
if (shouldRaiseEsm) warnOnce(codeId, message, loc);
|
|
569
|
+
}
|
|
522
570
|
});
|
|
523
571
|
}
|
|
524
572
|
if (shouldRaiseEsm && node.type === 'ThisExpression') {
|
|
@@ -572,21 +620,52 @@ const format = async (src, ast, opts) => {
|
|
|
572
620
|
const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
|
|
573
621
|
const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
|
|
574
622
|
const accessProp = name => isValidExportName(name) ? `${exportsRename}.${name}` : `${exportsRename}[${JSON.stringify(name)}]`;
|
|
623
|
+
const exportValueFor = name => {
|
|
624
|
+
if (name === '__dirname') return 'import.meta.dirname';
|
|
625
|
+
if (name === '__filename') return 'import.meta.filename';
|
|
626
|
+
return name;
|
|
627
|
+
};
|
|
575
628
|
const tempNameFor = name => {
|
|
576
629
|
const sanitized = name.replace(/[^$\w]/g, '_') || 'value';
|
|
577
630
|
const safe = /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;
|
|
578
631
|
return `__export_${safe}`;
|
|
579
632
|
};
|
|
633
|
+
for (const [key, entry] of exportTable) {
|
|
634
|
+
if (entry.reassignments.length) {
|
|
635
|
+
const loc = entry.reassignments[0];
|
|
636
|
+
warnOnce(`cjs-export-reassignment:${key}`, `Export '${key}' is reassigned after export; ESM live bindings may change consumer behavior.`, {
|
|
637
|
+
start: loc.start,
|
|
638
|
+
end: loc.end
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
580
642
|
const lines = [];
|
|
581
643
|
const defaultEntry = exportTable.get('default');
|
|
582
644
|
if (defaultEntry) {
|
|
583
645
|
const def = defaultEntry.fromIdentifier ?? exportsRename;
|
|
584
|
-
|
|
646
|
+
const defExpr = exportValueFor(def);
|
|
647
|
+
if (defExpr !== def) {
|
|
648
|
+
const temp = tempNameFor(def);
|
|
649
|
+
lines.push(`const ${temp} = ${defExpr};`);
|
|
650
|
+
lines.push(`export default ${temp};`);
|
|
651
|
+
} else {
|
|
652
|
+
lines.push(`export default ${defExpr};`);
|
|
653
|
+
}
|
|
585
654
|
}
|
|
586
655
|
for (const [key, entry] of exportTable) {
|
|
587
656
|
if (key === 'default') continue;
|
|
657
|
+
if (!isValidExportName(key)) {
|
|
658
|
+
warnOnce(`cjs-string-export:${key}`, `Synthesized string-literal export '${key}'. Some tooling may require bracket access to use it.`);
|
|
659
|
+
}
|
|
588
660
|
if (entry.fromIdentifier) {
|
|
589
|
-
|
|
661
|
+
const resolved = exportValueFor(entry.fromIdentifier);
|
|
662
|
+
if (resolved !== entry.fromIdentifier) {
|
|
663
|
+
const temp = tempNameFor(entry.fromIdentifier);
|
|
664
|
+
lines.push(`const ${temp} = ${resolved};`);
|
|
665
|
+
lines.push(`export { ${temp} as ${asExportName(key)} };`);
|
|
666
|
+
} else {
|
|
667
|
+
lines.push(`export { ${resolved} as ${asExportName(key)} };`);
|
|
668
|
+
}
|
|
590
669
|
} else {
|
|
591
670
|
const temp = tempNameFor(key);
|
|
592
671
|
lines.push(`const ${temp} = ${accessProp(key)};`);
|
|
@@ -34,14 +34,31 @@ export const memberExpression = (node, parent, src, options, shadowed, extras) =
|
|
|
34
34
|
* Can of worms here. ¯\_(ツ)_/¯
|
|
35
35
|
* @see https://github.com/nodejs/help/issues/2806
|
|
36
36
|
*/
|
|
37
|
+
extras?.onDiagnostic?.('legacy-require-cache', 'Access to require.cache is not supported when raising to ESM; behavior may differ.', {
|
|
38
|
+
start,
|
|
39
|
+
end
|
|
40
|
+
});
|
|
37
41
|
src.update(start, end, '{}');
|
|
38
42
|
break;
|
|
43
|
+
case 'extensions':
|
|
44
|
+
extras?.onDiagnostic?.('legacy-require-extensions', 'Access to require.extensions is not supported when raising to ESM; use loaders instead.', {
|
|
45
|
+
start,
|
|
46
|
+
end
|
|
47
|
+
});
|
|
48
|
+
break;
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
|
|
42
52
|
if (!shadowed?.has('module')) {
|
|
43
53
|
src.update(node.start, node.end, 'require');
|
|
44
54
|
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && (node.property.name === 'parent' || node.property.name === 'children')) {
|
|
58
|
+
extras?.onDiagnostic?.(`legacy-module-${node.property.name}`, `Access to module.${node.property.name} may not behave the same in ESM; consider loaders or explicit wiring instead.`, {
|
|
59
|
+
start: node.start,
|
|
60
|
+
end: node.end
|
|
61
|
+
});
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
};
|
|
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.cjs';
|
|
|
4
4
|
type MemberExpressionExtras = {
|
|
5
5
|
onRequireResolve?: () => void;
|
|
6
6
|
requireResolveName?: string;
|
|
7
|
+
onDiagnostic?: (code: string, message: string, loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}) => void;
|
|
7
11
|
};
|
|
8
12
|
export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
|
|
9
13
|
export {};
|
|
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.js';
|
|
|
4
4
|
type MemberExpressionExtras = {
|
|
5
5
|
onRequireResolve?: () => void;
|
|
6
6
|
requireResolveName?: string;
|
|
7
|
+
onDiagnostic?: (code: string, message: string, loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}) => void;
|
|
7
11
|
};
|
|
8
12
|
export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
|
|
9
13
|
export {};
|
package/dist/module.js
CHANGED
|
@@ -164,7 +164,8 @@ const defaultOptions = {
|
|
|
164
164
|
const transform = async (filename, options = defaultOptions) => {
|
|
165
165
|
const opts = {
|
|
166
166
|
...defaultOptions,
|
|
167
|
-
...options
|
|
167
|
+
...options,
|
|
168
|
+
filePath: filename
|
|
168
169
|
};
|
|
169
170
|
const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
170
171
|
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
|
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.js';
|
|
|
4
4
|
type MemberExpressionExtras = {
|
|
5
5
|
onRequireResolve?: () => void;
|
|
6
6
|
requireResolveName?: string;
|
|
7
|
+
onDiagnostic?: (code: string, message: string, loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}) => void;
|
|
7
11
|
};
|
|
8
12
|
export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
|
|
9
13
|
export {};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -34,11 +34,25 @@ export type ModuleOptions = {
|
|
|
34
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
35
|
/** Handling for top-level await constructs. */
|
|
36
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Optional diagnostics sink for warnings/errors emitted during transform. */
|
|
38
|
+
diagnostics?: (diag: Diagnostic) => void;
|
|
39
|
+
/** Optional source file path used for diagnostics context. */
|
|
40
|
+
filePath?: string;
|
|
37
41
|
/** Output directory or file path when writing. */
|
|
38
42
|
out?: string;
|
|
39
43
|
/** Overwrite input files instead of writing to out. */
|
|
40
44
|
inPlace?: boolean;
|
|
41
45
|
};
|
|
46
|
+
export type Diagnostic = {
|
|
47
|
+
level: 'warning' | 'error';
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
filePath?: string;
|
|
51
|
+
loc?: {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
42
56
|
export type SpannedNode = Node & Span;
|
|
43
57
|
export type ExportsMeta = {
|
|
44
58
|
hasExportsBeenReassigned: boolean;
|
package/dist/types.d.cts
CHANGED
|
@@ -34,11 +34,25 @@ export type ModuleOptions = {
|
|
|
34
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
35
|
/** Handling for top-level await constructs. */
|
|
36
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Optional diagnostics sink for warnings/errors emitted during transform. */
|
|
38
|
+
diagnostics?: (diag: Diagnostic) => void;
|
|
39
|
+
/** Optional source file path used for diagnostics context. */
|
|
40
|
+
filePath?: string;
|
|
37
41
|
/** Output directory or file path when writing. */
|
|
38
42
|
out?: string;
|
|
39
43
|
/** Overwrite input files instead of writing to out. */
|
|
40
44
|
inPlace?: boolean;
|
|
41
45
|
};
|
|
46
|
+
export type Diagnostic = {
|
|
47
|
+
level: 'warning' | 'error';
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
filePath?: string;
|
|
51
|
+
loc?: {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
42
56
|
export type SpannedNode = Node & Span;
|
|
43
57
|
export type ExportsMeta = {
|
|
44
58
|
hasExportsBeenReassigned: boolean;
|
package/dist/types.d.ts
CHANGED
|
@@ -34,11 +34,25 @@ export type ModuleOptions = {
|
|
|
34
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
35
|
/** Handling for top-level await constructs. */
|
|
36
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Optional diagnostics sink for warnings/errors emitted during transform. */
|
|
38
|
+
diagnostics?: (diag: Diagnostic) => void;
|
|
39
|
+
/** Optional source file path used for diagnostics context. */
|
|
40
|
+
filePath?: string;
|
|
37
41
|
/** Output directory or file path when writing. */
|
|
38
42
|
out?: string;
|
|
39
43
|
/** Overwrite input files instead of writing to out. */
|
|
40
44
|
inPlace?: boolean;
|
|
41
45
|
};
|
|
46
|
+
export type Diagnostic = {
|
|
47
|
+
level: 'warning' | 'error';
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
filePath?: string;
|
|
51
|
+
loc?: {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
42
56
|
export type SpannedNode = Node & Span;
|
|
43
57
|
export type ExportsMeta = {
|
|
44
58
|
hasExportsBeenReassigned: boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/module",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
4
|
+
"description": "Bidirectional transform for ES modules and CommonJS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/module.js",
|
|
7
7
|
"exports": {
|
|
@@ -44,13 +44,12 @@
|
|
|
44
44
|
"postpack": "node scripts/restoreImportsToSrc.js"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
|
-
"
|
|
48
|
-
"es module",
|
|
47
|
+
"esm",
|
|
49
48
|
"commonjs",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"import.meta
|
|
49
|
+
"transform",
|
|
50
|
+
"cjs-to-esm",
|
|
51
|
+
"esm-to-cjs",
|
|
52
|
+
"import.meta",
|
|
54
53
|
"__dirname",
|
|
55
54
|
"__filename"
|
|
56
55
|
],
|