@knighted/module 1.0.0-rc.2 → 1.0.0-rc.3
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 +35 -0
- package/dist/cjs/format.cjs +113 -14
- package/dist/cjs/formatters/identifier.cjs +1 -1
- package/dist/cjs/formatters/memberExpression.cjs +3 -2
- package/dist/cjs/helpers/identifier.cjs +48 -7
- package/dist/cjs/memberExpression.d.cts +6 -1
- package/dist/cjs/module.cjs +152 -15
- package/dist/cjs/specifier.cjs +1 -1
- package/dist/cjs/types.d.cts +24 -0
- package/dist/cjs/utils/exports.cjs +41 -18
- package/dist/cjs/utils/identifiers.cjs +34 -16
- package/dist/format.js +113 -14
- package/dist/formatters/identifier.js +1 -1
- package/dist/formatters/memberExpression.js +3 -2
- package/dist/helpers/identifier.js +48 -7
- package/dist/memberExpression.d.cts +6 -1
- package/dist/memberExpression.d.ts +6 -1
- package/dist/module.js +155 -15
- package/dist/specifier.js +1 -1
- package/dist/src/formatters/memberExpression.d.ts +6 -1
- package/dist/src/types.d.ts +24 -0
- package/dist/types.d.cts +24 -0
- package/dist/types.d.ts +24 -0
- package/dist/utils/exports.js +41 -18
- package/dist/utils/identifiers.js +34 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ type ModuleOptions = {
|
|
|
101
101
|
sourceType?: 'auto' | 'module' | 'commonjs'
|
|
102
102
|
transformSyntax?: boolean
|
|
103
103
|
liveBindings?: 'strict' | 'loose' | 'off'
|
|
104
|
+
appendJsExtension?: 'off' | 'relative-only' | 'all'
|
|
105
|
+
appendDirectoryIndex?: string | false
|
|
104
106
|
rewriteSpecifier?:
|
|
105
107
|
| '.js'
|
|
106
108
|
| '.mjs'
|
|
@@ -112,6 +114,8 @@ type ModuleOptions = {
|
|
|
112
114
|
dirFilename?: 'inject' | 'preserve' | 'error'
|
|
113
115
|
importMeta?: 'preserve' | 'shim' | 'error'
|
|
114
116
|
importMetaMain?: 'shim' | 'warn' | 'error'
|
|
117
|
+
requireMainStrategy?: 'import-meta-main' | 'realpath'
|
|
118
|
+
detectCircularRequires?: 'off' | 'warn' | 'error'
|
|
115
119
|
requireSource?: 'builtin' | 'create-require'
|
|
116
120
|
cjsDefault?: 'module-exports' | 'auto' | 'none'
|
|
117
121
|
topLevelAwait?: 'error' | 'wrap' | 'preserve'
|
|
@@ -125,9 +129,13 @@ Behavior notes (defaults in parentheses)
|
|
|
125
129
|
- `target` (`commonjs`): output module system.
|
|
126
130
|
- `transformSyntax` (true): enable/disable the ESM↔CJS lowering pass.
|
|
127
131
|
- `liveBindings` (`strict`): getter-based live bindings, or snapshot (`loose`/`off`).
|
|
132
|
+
- `appendJsExtension` (`relative-only` when targeting ESM): append `.js` to relative specifiers; never touches bare specifiers.
|
|
133
|
+
- `appendDirectoryIndex` (`index.js`): when a relative specifier ends with a slash, append this index filename (set `false` to disable).
|
|
128
134
|
- `dirFilename` (`inject`): inject `__dirname`/`__filename`, preserve existing, or throw.
|
|
129
135
|
- `importMeta` (`shim`): rewrite `import.meta.*` to CommonJS equivalents.
|
|
130
136
|
- `importMetaMain` (`shim`): gate `import.meta.main` with shimming/warning/error when Node support is too old.
|
|
137
|
+
- `requireMainStrategy` (`import-meta-main`): use `import.meta.main` or the realpath-based `pathToFileURL(realpathSync(process.argv[1])).href` check.
|
|
138
|
+
- `detectCircularRequires` (`off`): optionally detect relative static require cycles and warn/throw.
|
|
131
139
|
- `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output.
|
|
132
140
|
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback.
|
|
133
141
|
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
|
|
@@ -135,11 +143,38 @@ Behavior notes (defaults in parentheses)
|
|
|
135
143
|
- `out`/`inPlace`: write the transformed code to a file; otherwise the function returns the transformed string only.
|
|
136
144
|
- CommonJS → ESM lowering will throw on `with` statements and unshadowed `eval` calls to avoid unsound rewrites.
|
|
137
145
|
|
|
146
|
+
> [!NOTE]
|
|
147
|
+
> Package-level metadata (`package.json` updates such as setting `"type": "module"` or authoring `exports`) is not edited by this tool today; plan that change outside the per-file transform.
|
|
148
|
+
|
|
138
149
|
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. For CommonJS to ESM lowering details, read [docs/cjs-to-esm.md](docs/cjs-to-esm.md).
|
|
139
150
|
|
|
140
151
|
> [!NOTE]
|
|
141
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`.
|
|
142
153
|
|
|
154
|
+
## Pre-`tsc` transforms for TypeScript diagnostics
|
|
155
|
+
|
|
156
|
+
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.
|
|
157
|
+
|
|
158
|
+
Minimal flow:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
import { glob } from 'glob'
|
|
162
|
+
import { transform } from '@knighted/module'
|
|
163
|
+
|
|
164
|
+
const files = await glob('src/**/*.{ts,js,mts,cts}', { ignore: 'node_modules/**' })
|
|
165
|
+
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
await transform(file, {
|
|
168
|
+
target: 'commonjs', // or 'module' when raising CJS → ESM
|
|
169
|
+
inPlace: true,
|
|
170
|
+
transformSyntax: true,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
// then run `tsc`
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This pre-`tsc` step removes the flagged globals in the compiled orientation; runtime semantics still match the target build.
|
|
177
|
+
|
|
143
178
|
## Roadmap
|
|
144
179
|
|
|
145
180
|
- Emit source maps and clearer diagnostics for transform choices.
|
package/dist/cjs/format.cjs
CHANGED
|
@@ -26,6 +26,8 @@ const exportAssignment = (name, expr, live) => {
|
|
|
26
26
|
};
|
|
27
27
|
const defaultInteropName = '__interopDefault';
|
|
28
28
|
const interopHelper = `const ${defaultInteropName} = mod => (mod && mod.__esModule ? mod.default : mod);\n`;
|
|
29
|
+
const requireInteropName = '__requireDefault';
|
|
30
|
+
const requireInteropHelper = `const ${requireInteropName} = mod => (mod && typeof mod === 'object' && 'default' in mod ? mod.default : mod);\n`;
|
|
29
31
|
const isRequireCallee = (callee, shadowed) => {
|
|
30
32
|
if (callee.type === 'Identifier' && callee.name === 'require' && !shadowed.has('require')) {
|
|
31
33
|
return true;
|
|
@@ -40,8 +42,14 @@ const isRequireCall = (node, shadowed) => node.type === 'CallExpression' && isRe
|
|
|
40
42
|
const lowerCjsRequireToImports = (program, code, shadowed) => {
|
|
41
43
|
const transforms = [];
|
|
42
44
|
const imports = [];
|
|
45
|
+
const hoisted = [];
|
|
43
46
|
let nsIndex = 0;
|
|
44
47
|
let needsCreateRequire = false;
|
|
48
|
+
let needsInteropHelper = false;
|
|
49
|
+
const isJsonSpecifier = value => {
|
|
50
|
+
const base = value.split(/[?#]/)[0] ?? value;
|
|
51
|
+
return base.endsWith('.json');
|
|
52
|
+
};
|
|
45
53
|
for (const stmt of program.body) {
|
|
46
54
|
if (stmt.type === 'VariableDeclaration') {
|
|
47
55
|
const decls = stmt.declarations;
|
|
@@ -49,14 +57,21 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
|
|
|
49
57
|
if (allStatic) {
|
|
50
58
|
for (const decl of decls) {
|
|
51
59
|
const init = decl.init;
|
|
52
|
-
const
|
|
60
|
+
const arg = init.arguments[0];
|
|
61
|
+
const source = code.slice(arg.start, arg.end);
|
|
62
|
+
const value = arg.value;
|
|
63
|
+
const isJson = typeof value === 'string' && isJsonSpecifier(value);
|
|
64
|
+
const ns = `__cjsImport${nsIndex++}`;
|
|
65
|
+
const jsonImport = isJson ? `${source} with { type: "json" }` : source;
|
|
53
66
|
if (decl.id.type === 'Identifier') {
|
|
54
|
-
imports.push(`import * as ${
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
imports.push(isJson ? `import ${ns} from ${jsonImport};\n` : `import * as ${ns} from ${jsonImport};\n`);
|
|
68
|
+
hoisted.push(isJson ? `const ${decl.id.name} = ${ns};\n` : `const ${decl.id.name} = ${requireInteropName}(${ns});\n`);
|
|
69
|
+
needsInteropHelper ||= !isJson;
|
|
70
|
+
} else if (decl.id.type === 'ObjectPattern' || decl.id.type === 'ArrayPattern') {
|
|
57
71
|
const pattern = code.slice(decl.id.start, decl.id.end);
|
|
58
|
-
imports.push(`import * as ${ns} from ${
|
|
59
|
-
|
|
72
|
+
imports.push(isJson ? `import ${ns} from ${jsonImport};\n` : `import * as ${ns} from ${jsonImport};\n`);
|
|
73
|
+
hoisted.push(isJson ? `const ${pattern} = ${ns};\n` : `const ${pattern} = ${requireInteropName}(${ns});\n`);
|
|
74
|
+
needsInteropHelper ||= !isJson;
|
|
60
75
|
} else {
|
|
61
76
|
needsCreateRequire = true;
|
|
62
77
|
}
|
|
@@ -78,8 +93,12 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
|
|
|
78
93
|
if (stmt.type === 'ExpressionStatement') {
|
|
79
94
|
const expr = stmt.expression;
|
|
80
95
|
if (expr && isStaticRequire(expr, shadowed)) {
|
|
81
|
-
const
|
|
82
|
-
|
|
96
|
+
const arg = expr.arguments[0];
|
|
97
|
+
const source = code.slice(arg.start, arg.end);
|
|
98
|
+
const value = arg.value;
|
|
99
|
+
const isJson = typeof value === 'string' && isJsonSpecifier(value);
|
|
100
|
+
const jsonImport = isJson ? `${source} with { type: "json" }` : source;
|
|
101
|
+
imports.push(`import ${jsonImport};\n`);
|
|
83
102
|
transforms.push({
|
|
84
103
|
start: stmt.start,
|
|
85
104
|
end: stmt.end,
|
|
@@ -95,7 +114,9 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
|
|
|
95
114
|
return {
|
|
96
115
|
transforms,
|
|
97
116
|
imports,
|
|
98
|
-
|
|
117
|
+
hoisted,
|
|
118
|
+
needsCreateRequire,
|
|
119
|
+
needsInteropHelper
|
|
99
120
|
};
|
|
100
121
|
};
|
|
101
122
|
const isRequireMainMember = (node, shadowed) => node && node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'require' && !shadowed.has('require') && node.property.type === 'Identifier' && node.property.name === 'main';
|
|
@@ -136,6 +157,20 @@ const hasTopLevelAwait = program => {
|
|
|
136
157
|
walkNode(program, false);
|
|
137
158
|
return found;
|
|
138
159
|
};
|
|
160
|
+
const isAsyncContext = ancestors => {
|
|
161
|
+
for (let i = ancestors.length - 1; i >= 0; i -= 1) {
|
|
162
|
+
const node = ancestors[i];
|
|
163
|
+
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
|
164
|
+
return !!node.async;
|
|
165
|
+
}
|
|
166
|
+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Program scope (top-level) supports await in ESM.
|
|
172
|
+
return true;
|
|
173
|
+
};
|
|
139
174
|
const lowerEsmToCjs = (program, code, opts, containsTopLevelAwait) => {
|
|
140
175
|
const live = opts.liveBindings ?? 'strict';
|
|
141
176
|
const importTransforms = [];
|
|
@@ -345,11 +380,17 @@ const format = async (src, ast, opts) => {
|
|
|
345
380
|
const exportTable = opts.target === 'module' ? await (0, _exports.collectCjsExports)(ast.program) : null;
|
|
346
381
|
const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
|
|
347
382
|
const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
|
|
383
|
+
const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
|
|
384
|
+
let requireMainNeedsRealpath = false;
|
|
385
|
+
let needsRequireResolveHelper = false;
|
|
386
|
+
const nestedRequireStrategy = opts.nestedRequireStrategy ?? 'create-require';
|
|
348
387
|
const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
|
|
349
388
|
const shouldRaiseEsm = opts.target === 'module' && opts.transformSyntax;
|
|
350
389
|
let hoistedImports = [];
|
|
390
|
+
let hoistedStatements = [];
|
|
351
391
|
let pendingRequireTransforms = [];
|
|
352
392
|
let needsCreateRequire = false;
|
|
393
|
+
let needsImportInterop = false;
|
|
353
394
|
let pendingCjsTransforms = null;
|
|
354
395
|
if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
|
|
355
396
|
throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
|
|
@@ -358,11 +399,15 @@ const format = async (src, ast, opts) => {
|
|
|
358
399
|
const {
|
|
359
400
|
transforms,
|
|
360
401
|
imports,
|
|
361
|
-
|
|
402
|
+
hoisted,
|
|
403
|
+
needsCreateRequire: reqCreate,
|
|
404
|
+
needsInteropHelper: reqInteropHelper
|
|
362
405
|
} = lowerCjsRequireToImports(ast.program, code, shadowedBindings);
|
|
363
406
|
pendingRequireTransforms = transforms;
|
|
364
407
|
hoistedImports = imports;
|
|
408
|
+
hoistedStatements = hoisted;
|
|
365
409
|
needsCreateRequire = reqCreate;
|
|
410
|
+
needsImportInterop = reqInteropHelper;
|
|
366
411
|
}
|
|
367
412
|
await (0, _walk.ancestorWalk)(ast.program, {
|
|
368
413
|
async enter(node, ancestors) {
|
|
@@ -377,7 +422,11 @@ const format = async (src, ast, opts) => {
|
|
|
377
422
|
const rightModule = node.right.type === 'Identifier' && node.right.name === 'module' && !shadowedBindings.has('module');
|
|
378
423
|
if (leftMain && rightModule || rightMain && leftModule) {
|
|
379
424
|
const negate = op === '!==' || op === '!=';
|
|
380
|
-
|
|
425
|
+
const mainExpr = requireMainStrategy === 'import-meta-main' ? 'import.meta.main' : 'import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href';
|
|
426
|
+
if (requireMainStrategy === 'realpath') {
|
|
427
|
+
requireMainNeedsRealpath = true;
|
|
428
|
+
}
|
|
429
|
+
code.update(node.start, node.end, negate ? `!(${mainExpr})` : mainExpr);
|
|
381
430
|
return;
|
|
382
431
|
}
|
|
383
432
|
}
|
|
@@ -399,6 +448,18 @@ const format = async (src, ast, opts) => {
|
|
|
399
448
|
const topLevelVarDecl = parent?.type === 'VariableDeclarator' && grandparent?.type === 'VariableDeclaration' && greatGrandparent?.type === 'Program';
|
|
400
449
|
const hoistableTopLevel = isStatic && (topLevelExprStmt || topLevelVarDecl);
|
|
401
450
|
if (!isStatic || !hoistableTopLevel) {
|
|
451
|
+
if (nestedRequireStrategy === 'dynamic-import') {
|
|
452
|
+
const asyncCapable = isAsyncContext(ancestors);
|
|
453
|
+
if (asyncCapable) {
|
|
454
|
+
const arg = node.arguments[0];
|
|
455
|
+
const argSrc = arg ? code.slice(arg.start, arg.end) : 'undefined';
|
|
456
|
+
const literalVal = arg?.value;
|
|
457
|
+
const isJson = arg?.type === 'Literal' && typeof literalVal === 'string' && (literalVal.split(/[?#]/)[0] ?? literalVal).endsWith('.json');
|
|
458
|
+
const importTarget = isJson ? `${argSrc} with { type: "json" }` : argSrc;
|
|
459
|
+
code.update(node.start, node.end, `(await import(${importTarget}))`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
402
463
|
needsCreateRequire = true;
|
|
403
464
|
}
|
|
404
465
|
}
|
|
@@ -460,7 +521,23 @@ const format = async (src, ast, opts) => {
|
|
|
460
521
|
(0, _metaProperty.metaProperty)(node, parent, code, opts);
|
|
461
522
|
}
|
|
462
523
|
if (node.type === 'MemberExpression') {
|
|
463
|
-
(0, _memberExpression.memberExpression)(node, parent, code, opts, shadowedBindings
|
|
524
|
+
(0, _memberExpression.memberExpression)(node, parent, code, opts, shadowedBindings, {
|
|
525
|
+
onRequireResolve: () => {
|
|
526
|
+
if (shouldRaiseEsm) needsRequireResolveHelper = true;
|
|
527
|
+
},
|
|
528
|
+
requireResolveName: '__requireResolve'
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (shouldRaiseEsm && node.type === 'ThisExpression') {
|
|
532
|
+
const bindsThis = ancestor => {
|
|
533
|
+
return ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression';
|
|
534
|
+
};
|
|
535
|
+
const bindingAncestor = ancestors.find(ancestor => bindsThis(ancestor));
|
|
536
|
+
const isTopLevel = !bindingAncestor;
|
|
537
|
+
if (isTopLevel) {
|
|
538
|
+
code.update(node.start, node.end, _exports.exportsRename);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
464
541
|
}
|
|
465
542
|
if ((0, _identifier2.isIdentifierName)(node)) {
|
|
466
543
|
(0, _identifier.identifier)({
|
|
@@ -529,14 +606,36 @@ const format = async (src, ast, opts) => {
|
|
|
529
606
|
}
|
|
530
607
|
if (shouldRaiseEsm && opts.transformSyntax) {
|
|
531
608
|
const importPrelude = [];
|
|
532
|
-
if (needsCreateRequire) {
|
|
609
|
+
if (needsCreateRequire || needsRequireResolveHelper) {
|
|
533
610
|
importPrelude.push('import { createRequire } from "node:module";\n');
|
|
534
611
|
}
|
|
612
|
+
if (needsRequireResolveHelper) {
|
|
613
|
+
importPrelude.push('import { fileURLToPath } from "node:url";\n');
|
|
614
|
+
}
|
|
615
|
+
if (requireMainNeedsRealpath) {
|
|
616
|
+
importPrelude.push('import { realpathSync } from "node:fs";\n');
|
|
617
|
+
importPrelude.push('import { pathToFileURL } from "node:url";\n');
|
|
618
|
+
}
|
|
535
619
|
if (hoistedImports.length) {
|
|
536
620
|
importPrelude.push(...hoistedImports);
|
|
537
621
|
}
|
|
622
|
+
const setupPrelude = [];
|
|
623
|
+
if (needsImportInterop) {
|
|
624
|
+
setupPrelude.push(requireInteropHelper);
|
|
625
|
+
}
|
|
626
|
+
if (hoistedStatements.length) {
|
|
627
|
+
setupPrelude.push(...hoistedStatements);
|
|
628
|
+
}
|
|
538
629
|
const requireInit = needsCreateRequire ? 'const require = createRequire(import.meta.url);\n' : '';
|
|
539
|
-
const
|
|
630
|
+
const requireResolveInit = needsRequireResolveHelper ? needsCreateRequire ? `const __requireResolve = (id, parent) => {
|
|
631
|
+
const resolved = require.resolve(id, parent);
|
|
632
|
+
return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
|
|
633
|
+
};\n` : `const __requireResolve = (id, parent) => {
|
|
634
|
+
const req = createRequire(parent ?? import.meta.url);
|
|
635
|
+
const resolved = req.resolve(id, parent);
|
|
636
|
+
return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
|
|
637
|
+
};\n` : '';
|
|
638
|
+
const prelude = `${importPrelude.join('')}${importPrelude.length ? '\n' : ''}${setupPrelude.join('')}${setupPrelude.length ? '\n' : ''}${requireInit}${requireResolveInit}let ${_exports.exportsRename} = {};
|
|
540
639
|
void import.meta.filename;
|
|
541
640
|
`;
|
|
542
641
|
code.prepend(prelude);
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.memberExpression = void 0;
|
|
7
7
|
var _exports = require("#utils/exports.js");
|
|
8
|
-
const memberExpression = (node, parent, src, options, shadowed) => {
|
|
8
|
+
const memberExpression = (node, parent, src, options, shadowed, extras) => {
|
|
9
9
|
if (options.target === 'module') {
|
|
10
10
|
if (node.object.type === 'Identifier' && shadowed?.has(node.object.name) || node.property.type === 'Identifier' && shadowed?.has(node.property.name)) {
|
|
11
11
|
return;
|
|
@@ -32,7 +32,8 @@ const memberExpression = (node, parent, src, options, shadowed) => {
|
|
|
32
32
|
src.update(start, end, 'import.meta.main');
|
|
33
33
|
break;
|
|
34
34
|
case 'resolve':
|
|
35
|
-
|
|
35
|
+
extras?.onRequireResolve?.();
|
|
36
|
+
src.update(start, end, extras?.requireResolveName ?? 'import.meta.resolve');
|
|
36
37
|
break;
|
|
37
38
|
case 'cache':
|
|
38
39
|
/**
|
|
@@ -30,6 +30,31 @@ const getScopeContext = program => {
|
|
|
30
30
|
scopeCache.set(program, context);
|
|
31
31
|
return context;
|
|
32
32
|
};
|
|
33
|
+
const isInBindingPattern = (pattern, target) => {
|
|
34
|
+
if (pattern === target) return true;
|
|
35
|
+
switch (pattern.type) {
|
|
36
|
+
case 'Identifier':
|
|
37
|
+
return pattern === target;
|
|
38
|
+
case 'AssignmentPattern':
|
|
39
|
+
return isInBindingPattern(pattern.left, target);
|
|
40
|
+
case 'RestElement':
|
|
41
|
+
return isInBindingPattern(pattern.argument, target);
|
|
42
|
+
case 'ObjectPattern':
|
|
43
|
+
return (pattern.properties ?? []).some(prop => {
|
|
44
|
+
if (prop.type === 'Property') {
|
|
45
|
+
return isInBindingPattern(prop.value, target);
|
|
46
|
+
}
|
|
47
|
+
if (prop.type === 'RestElement') {
|
|
48
|
+
return isInBindingPattern(prop.argument, target);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
case 'ArrayPattern':
|
|
53
|
+
return (pattern.elements ?? []).some(elem => elem && isInBindingPattern(elem, target));
|
|
54
|
+
default:
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
33
58
|
|
|
34
59
|
/**
|
|
35
60
|
* All methods receive the full set of ancestors, which
|
|
@@ -48,6 +73,7 @@ const identifier = exports.identifier = {
|
|
|
48
73
|
isModuleScope(ancestors, includeImports = false) {
|
|
49
74
|
const node = ancestors[ancestors.length - 1];
|
|
50
75
|
const parent = ancestors[ancestors.length - 2];
|
|
76
|
+
const grandParent = ancestors[ancestors.length - 3];
|
|
51
77
|
const program = ancestors[0];
|
|
52
78
|
if (!identifier.isNamed(node) || identifier.isMetaProperty(ancestors) || parent.type === 'LabeledStatement' || parent.type === 'BreakStatement' || parent.type === 'ContinueStatement') {
|
|
53
79
|
return false;
|
|
@@ -56,7 +82,9 @@ const identifier = exports.identifier = {
|
|
|
56
82
|
return includeImports && parent.local.name === node.name;
|
|
57
83
|
}
|
|
58
84
|
if (parent.type === 'Property' && parent.key === node && !parent.computed) {
|
|
59
|
-
|
|
85
|
+
if (grandParent?.type !== 'ObjectPattern') {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
60
88
|
}
|
|
61
89
|
if (parent.type === 'MemberExpression' && parent.property === node && !parent.computed) {
|
|
62
90
|
return false;
|
|
@@ -78,8 +106,17 @@ const identifier = exports.identifier = {
|
|
|
78
106
|
},
|
|
79
107
|
isDeclaration(ancestors) {
|
|
80
108
|
const node = ancestors[ancestors.length - 1];
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
// Walk outwards to find a declarator that binds the node
|
|
110
|
+
for (let i = ancestors.length - 2; i >= 0; i--) {
|
|
111
|
+
const parent = ancestors[i];
|
|
112
|
+
if (parent.type === 'VariableDeclarator') {
|
|
113
|
+
return parent.id === node || isInBindingPattern(parent.id, node);
|
|
114
|
+
}
|
|
115
|
+
if (parent.type === 'FunctionDeclaration' || parent.type === 'ClassDeclaration') {
|
|
116
|
+
return parent.id === node;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
83
120
|
},
|
|
84
121
|
isClassOrFuncDeclarationId(ancestors) {
|
|
85
122
|
const node = ancestors[ancestors.length - 1];
|
|
@@ -88,12 +125,16 @@ const identifier = exports.identifier = {
|
|
|
88
125
|
},
|
|
89
126
|
isVarDeclarationInGlobalScope(ancestors) {
|
|
90
127
|
const node = ancestors[ancestors.length - 1];
|
|
91
|
-
const parent = ancestors[ancestors.length - 2];
|
|
92
|
-
const grandParent = ancestors[ancestors.length - 3];
|
|
93
128
|
const varBoundScopes = ['ClassDeclaration', 'ClassExpression', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'];
|
|
94
|
-
|
|
95
|
-
return
|
|
129
|
+
const declaratorIndex = ancestors.findIndex(ancestor => {
|
|
130
|
+
return ancestor.type === 'VariableDeclarator' && (ancestor === node || isInBindingPattern(ancestor.id, node));
|
|
96
131
|
});
|
|
132
|
+
if (declaratorIndex === -1) return false;
|
|
133
|
+
const declarator = ancestors[declaratorIndex];
|
|
134
|
+
const declaration = ancestors[declaratorIndex - 1];
|
|
135
|
+
return declaration?.type === 'VariableDeclaration' && declaration.kind === 'var' && ancestors.every(ancestor => {
|
|
136
|
+
return !varBoundScopes.includes(ancestor.type);
|
|
137
|
+
}) && (declarator.id === node || isInBindingPattern(declarator.id, node));
|
|
97
138
|
},
|
|
98
139
|
isIife(ancestors) {
|
|
99
140
|
const parent = ancestors[ancestors.length - 2];
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import MagicString from 'magic-string';
|
|
2
2
|
import type { MemberExpression, Node } from 'oxc-parser';
|
|
3
3
|
import type { FormatterOptions } from '../types.cjs';
|
|
4
|
-
|
|
4
|
+
type MemberExpressionExtras = {
|
|
5
|
+
onRequireResolve?: () => void;
|
|
6
|
+
requireResolveName?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
|
|
9
|
+
export {};
|
package/dist/cjs/module.cjs
CHANGED
|
@@ -10,16 +10,155 @@ var _specifier = require("./specifier.cjs");
|
|
|
10
10
|
var _parse = require("#parse");
|
|
11
11
|
var _format = require("#format");
|
|
12
12
|
var _lang = require("#utils/lang.js");
|
|
13
|
+
var _nodeModule = require("node:module");
|
|
14
|
+
var _walk = require("#walk");
|
|
15
|
+
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
16
|
+
const builtinSpecifiers = new Set(_nodeModule.builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
17
|
+
const parts = mod.split('/');
|
|
18
|
+
const base = parts[0];
|
|
19
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
20
|
+
}));
|
|
21
|
+
const appendExtensionIfNeeded = (spec, mode, dirIndex, value = spec.value) => {
|
|
22
|
+
if (mode === 'off') return;
|
|
23
|
+
if (spec.type === 'TemplateLiteral') {
|
|
24
|
+
const node = spec.node;
|
|
25
|
+
if (node.expressions.length > 0) return;
|
|
26
|
+
} else if (spec.type !== 'StringLiteral') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const collapsed = collapseSpecifier(value);
|
|
30
|
+
const isRelative = /^(?:\.\.?)\//.test(collapsed);
|
|
31
|
+
if (!isRelative) return;
|
|
32
|
+
const base = collapsed.split(/[?#]/)[0];
|
|
33
|
+
if (!base) return;
|
|
34
|
+
if (base.endsWith('/')) {
|
|
35
|
+
if (!dirIndex) return;
|
|
36
|
+
return `${value}${dirIndex}`;
|
|
37
|
+
}
|
|
38
|
+
const lastSegment = base.split('/').pop() ?? '';
|
|
39
|
+
if (lastSegment.includes('.')) return;
|
|
40
|
+
return `${value}.js`;
|
|
41
|
+
};
|
|
42
|
+
const rewriteSpecifierValue = (value, rewriteSpecifier) => {
|
|
43
|
+
if (!rewriteSpecifier) return;
|
|
44
|
+
if (typeof rewriteSpecifier === 'function') {
|
|
45
|
+
return rewriteSpecifier(value) ?? undefined;
|
|
46
|
+
}
|
|
47
|
+
const collapsed = collapseSpecifier(value);
|
|
48
|
+
const relative = /^(?:\.\.?)\//;
|
|
49
|
+
if (relative.test(collapsed)) {
|
|
50
|
+
return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"]*)?$/, `$1${rewriteSpecifier}$2`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const normalizeBuiltinSpecifier = value => {
|
|
54
|
+
const collapsed = collapseSpecifier(value);
|
|
55
|
+
if (!collapsed) return;
|
|
56
|
+
const specPart = collapsed.split(/[?#]/)[0] ?? '';
|
|
57
|
+
|
|
58
|
+
// Ignore relative and absolute paths.
|
|
59
|
+
if (/^(?:\.\.?\/|\/)/.test(specPart)) return;
|
|
60
|
+
|
|
61
|
+
// Skip other protocols (e.g., http:, data:) but allow node:.
|
|
62
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(specPart) && !specPart.startsWith('node:')) return;
|
|
63
|
+
const bare = specPart.startsWith('node:') ? specPart.slice(5) : specPart;
|
|
64
|
+
const base = bare.split('/')[0] ?? '';
|
|
65
|
+
if (!builtinSpecifiers.has(bare) && !builtinSpecifiers.has(base)) return;
|
|
66
|
+
if (specPart.startsWith('node:')) return;
|
|
67
|
+
const quote = /^['"`]/.exec(value)?.[0] ?? '';
|
|
68
|
+
return quote ? `${quote}node:${value.slice(quote.length)}` : `node:${value}`;
|
|
69
|
+
};
|
|
70
|
+
const fileExists = async candidate => {
|
|
71
|
+
try {
|
|
72
|
+
const s = await (0, _promises.stat)(candidate);
|
|
73
|
+
return s.isFile();
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const resolveRequirePath = async (fromFile, spec, dirIndex) => {
|
|
79
|
+
if (!spec.startsWith('./') && !spec.startsWith('../')) return null;
|
|
80
|
+
const base = (0, _nodePath.resolve)((0, _nodePath.dirname)(fromFile), spec);
|
|
81
|
+
const ext = (0, _nodePath.extname)(base);
|
|
82
|
+
const candidates = [];
|
|
83
|
+
if (ext) {
|
|
84
|
+
candidates.push(base);
|
|
85
|
+
} else {
|
|
86
|
+
candidates.push(`${base}.js`, `${base}.cjs`, `${base}.mjs`);
|
|
87
|
+
candidates.push((0, _nodePath.join)(base, dirIndex));
|
|
88
|
+
}
|
|
89
|
+
for (const candidate of candidates) {
|
|
90
|
+
if (await fileExists(candidate)) return candidate;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
const collectStaticRequires = async (filePath, dirIndex) => {
|
|
95
|
+
const src = await (0, _promises.readFile)(filePath, 'utf8');
|
|
96
|
+
const ast = (0, _parse.parse)(filePath, src);
|
|
97
|
+
const specs = [];
|
|
98
|
+
await (0, _walk.walk)(ast.program, {
|
|
99
|
+
enter(node) {
|
|
100
|
+
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
|
|
101
|
+
const spec = node.arguments[0].value;
|
|
102
|
+
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
103
|
+
specs.push(spec);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const resolved = [];
|
|
109
|
+
for (const spec of specs) {
|
|
110
|
+
const target = await resolveRequirePath(filePath, spec, dirIndex);
|
|
111
|
+
if (target) resolved.push(target);
|
|
112
|
+
}
|
|
113
|
+
return resolved;
|
|
114
|
+
};
|
|
115
|
+
const detectCircularRequireGraph = async (entryFile, mode, dirIndex) => {
|
|
116
|
+
const cache = new Map();
|
|
117
|
+
const visiting = new Set();
|
|
118
|
+
const visited = new Set();
|
|
119
|
+
const dfs = async (file, stack) => {
|
|
120
|
+
if (visiting.has(file)) {
|
|
121
|
+
const cycle = [...stack, file];
|
|
122
|
+
const msg = `Circular require detected: ${cycle.join(' -> ')}`;
|
|
123
|
+
if (mode === 'error') {
|
|
124
|
+
throw new Error(msg);
|
|
125
|
+
}
|
|
126
|
+
// eslint-disable-next-line no-console -- surfaced when cycle detection is warn-only
|
|
127
|
+
console.warn(msg);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (visited.has(file)) return;
|
|
131
|
+
visiting.add(file);
|
|
132
|
+
stack.push(file);
|
|
133
|
+
let deps = cache.get(file);
|
|
134
|
+
if (!deps) {
|
|
135
|
+
deps = await collectStaticRequires(file, dirIndex);
|
|
136
|
+
cache.set(file, deps);
|
|
137
|
+
}
|
|
138
|
+
for (const dep of deps) {
|
|
139
|
+
await dfs(dep, stack);
|
|
140
|
+
}
|
|
141
|
+
stack.pop();
|
|
142
|
+
visiting.delete(file);
|
|
143
|
+
visited.add(file);
|
|
144
|
+
};
|
|
145
|
+
await dfs(entryFile, []);
|
|
146
|
+
};
|
|
13
147
|
const defaultOptions = {
|
|
14
148
|
target: 'commonjs',
|
|
15
149
|
sourceType: 'auto',
|
|
16
150
|
transformSyntax: true,
|
|
17
151
|
liveBindings: 'strict',
|
|
18
152
|
rewriteSpecifier: undefined,
|
|
153
|
+
appendJsExtension: undefined,
|
|
154
|
+
appendDirectoryIndex: 'index.js',
|
|
19
155
|
dirFilename: 'inject',
|
|
20
156
|
importMeta: 'shim',
|
|
21
157
|
importMetaMain: 'shim',
|
|
158
|
+
requireMainStrategy: 'import-meta-main',
|
|
159
|
+
detectCircularRequires: 'off',
|
|
22
160
|
requireSource: 'builtin',
|
|
161
|
+
nestedRequireStrategy: 'create-require',
|
|
23
162
|
cjsDefault: 'auto',
|
|
24
163
|
topLevelAwait: 'error',
|
|
25
164
|
out: undefined,
|
|
@@ -30,28 +169,26 @@ const transform = async (filename, options = defaultOptions) => {
|
|
|
30
169
|
...defaultOptions,
|
|
31
170
|
...options
|
|
32
171
|
};
|
|
172
|
+
const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
173
|
+
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
|
174
|
+
const detectCycles = opts.detectCircularRequires ?? 'off';
|
|
33
175
|
const file = (0, _nodePath.resolve)(filename);
|
|
34
176
|
const code = (await (0, _promises.readFile)(file)).toString();
|
|
35
177
|
const ast = (0, _parse.parse)(filename, code);
|
|
36
178
|
let source = await (0, _format.format)(code, ast, opts);
|
|
37
|
-
if (opts.rewriteSpecifier) {
|
|
38
|
-
const code = await _specifier.specifier.updateSrc(source, (0, _lang.getLangFromExt)(filename),
|
|
39
|
-
value
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Collapse any BinaryExpression or NewExpression to test for a relative specifier
|
|
46
|
-
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
47
|
-
const relative = /^(?:\.|\.\.)\//;
|
|
48
|
-
if (relative.test(collapsed)) {
|
|
49
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
50
|
-
return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"]*)?$/, `$1${opts.rewriteSpecifier}$2`);
|
|
51
|
-
}
|
|
179
|
+
if (opts.rewriteSpecifier || appendMode !== 'off' || dirIndex) {
|
|
180
|
+
const code = await _specifier.specifier.updateSrc(source, (0, _lang.getLangFromExt)(filename), spec => {
|
|
181
|
+
const normalized = normalizeBuiltinSpecifier(spec.value);
|
|
182
|
+
const rewritten = rewriteSpecifierValue(normalized ?? spec.value, opts.rewriteSpecifier);
|
|
183
|
+
const baseValue = rewritten ?? normalized ?? spec.value;
|
|
184
|
+
const appended = appendExtensionIfNeeded(spec, appendMode, dirIndex, baseValue);
|
|
185
|
+
return appended ?? rewritten ?? normalized ?? undefined;
|
|
52
186
|
});
|
|
53
187
|
source = code;
|
|
54
188
|
}
|
|
189
|
+
if (detectCycles !== 'off' && opts.target === 'module' && opts.transformSyntax) {
|
|
190
|
+
await detectCircularRequireGraph(file, detectCycles, dirIndex || 'index.js');
|
|
191
|
+
}
|
|
55
192
|
const outputPath = opts.inPlace ? file : opts.out ? (0, _nodePath.resolve)(opts.out) : undefined;
|
|
56
193
|
if (outputPath) {
|
|
57
194
|
await (0, _promises.writeFile)(outputPath, source);
|
package/dist/cjs/specifier.cjs
CHANGED
|
@@ -128,7 +128,7 @@ const formatSpecifiers = async (src, ast, cb) => {
|
|
|
128
128
|
}
|
|
129
129
|
if (node.type === 'CallExpression') {
|
|
130
130
|
// Handle require(), require.resolve(), import.meta.resolve()
|
|
131
|
-
if (node.callee.type === 'Identifier' && node.callee.name === 'require' || node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'require' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve' || node.callee.type === 'MemberExpression' && node.callee.object.type === 'MetaProperty' && node.callee.object.meta.name === 'import' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve') {
|
|
131
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'require' || node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'require' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve' || node.callee.type === 'Identifier' && node.callee.name === '__requireResolve' || node.callee.type === 'MemberExpression' && node.callee.object.type === 'MetaProperty' && node.callee.object.meta.name === 'import' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve') {
|
|
132
132
|
formatExpression(node);
|
|
133
133
|
}
|
|
134
134
|
}
|