@knighted/module 1.0.0-rc.1 → 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 +39 -1
- 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 +10 -9
package/dist/module.js
CHANGED
|
@@ -4,16 +4,158 @@ import { specifier } from './specifier.js';
|
|
|
4
4
|
import { parse } from '#parse';
|
|
5
5
|
import { format } from '#format';
|
|
6
6
|
import { getLangFromExt } from '#utils/lang.js';
|
|
7
|
+
import { builtinModules } from 'node:module';
|
|
8
|
+
import { resolve as pathResolve, dirname as pathDirname, extname, join } from 'node:path';
|
|
9
|
+
import { readFile as fsReadFile, stat } from 'node:fs/promises';
|
|
10
|
+
import { parse as parseModule } from '#parse';
|
|
11
|
+
import { walk } from '#walk';
|
|
12
|
+
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
13
|
+
const builtinSpecifiers = new Set(builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
14
|
+
const parts = mod.split('/');
|
|
15
|
+
const base = parts[0];
|
|
16
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
17
|
+
}));
|
|
18
|
+
const appendExtensionIfNeeded = (spec, mode, dirIndex, value = spec.value) => {
|
|
19
|
+
if (mode === 'off') return;
|
|
20
|
+
if (spec.type === 'TemplateLiteral') {
|
|
21
|
+
const node = spec.node;
|
|
22
|
+
if (node.expressions.length > 0) return;
|
|
23
|
+
} else if (spec.type !== 'StringLiteral') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const collapsed = collapseSpecifier(value);
|
|
27
|
+
const isRelative = /^(?:\.\.?)\//.test(collapsed);
|
|
28
|
+
if (!isRelative) return;
|
|
29
|
+
const base = collapsed.split(/[?#]/)[0];
|
|
30
|
+
if (!base) return;
|
|
31
|
+
if (base.endsWith('/')) {
|
|
32
|
+
if (!dirIndex) return;
|
|
33
|
+
return `${value}${dirIndex}`;
|
|
34
|
+
}
|
|
35
|
+
const lastSegment = base.split('/').pop() ?? '';
|
|
36
|
+
if (lastSegment.includes('.')) return;
|
|
37
|
+
return `${value}.js`;
|
|
38
|
+
};
|
|
39
|
+
const rewriteSpecifierValue = (value, rewriteSpecifier) => {
|
|
40
|
+
if (!rewriteSpecifier) return;
|
|
41
|
+
if (typeof rewriteSpecifier === 'function') {
|
|
42
|
+
return rewriteSpecifier(value) ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
const collapsed = collapseSpecifier(value);
|
|
45
|
+
const relative = /^(?:\.\.?)\//;
|
|
46
|
+
if (relative.test(collapsed)) {
|
|
47
|
+
return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"]*)?$/, `$1${rewriteSpecifier}$2`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const normalizeBuiltinSpecifier = value => {
|
|
51
|
+
const collapsed = collapseSpecifier(value);
|
|
52
|
+
if (!collapsed) return;
|
|
53
|
+
const specPart = collapsed.split(/[?#]/)[0] ?? '';
|
|
54
|
+
|
|
55
|
+
// Ignore relative and absolute paths.
|
|
56
|
+
if (/^(?:\.\.?\/|\/)/.test(specPart)) return;
|
|
57
|
+
|
|
58
|
+
// Skip other protocols (e.g., http:, data:) but allow node:.
|
|
59
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(specPart) && !specPart.startsWith('node:')) return;
|
|
60
|
+
const bare = specPart.startsWith('node:') ? specPart.slice(5) : specPart;
|
|
61
|
+
const base = bare.split('/')[0] ?? '';
|
|
62
|
+
if (!builtinSpecifiers.has(bare) && !builtinSpecifiers.has(base)) return;
|
|
63
|
+
if (specPart.startsWith('node:')) return;
|
|
64
|
+
const quote = /^['"`]/.exec(value)?.[0] ?? '';
|
|
65
|
+
return quote ? `${quote}node:${value.slice(quote.length)}` : `node:${value}`;
|
|
66
|
+
};
|
|
67
|
+
const fileExists = async candidate => {
|
|
68
|
+
try {
|
|
69
|
+
const s = await stat(candidate);
|
|
70
|
+
return s.isFile();
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const resolveRequirePath = async (fromFile, spec, dirIndex) => {
|
|
76
|
+
if (!spec.startsWith('./') && !spec.startsWith('../')) return null;
|
|
77
|
+
const base = pathResolve(pathDirname(fromFile), spec);
|
|
78
|
+
const ext = extname(base);
|
|
79
|
+
const candidates = [];
|
|
80
|
+
if (ext) {
|
|
81
|
+
candidates.push(base);
|
|
82
|
+
} else {
|
|
83
|
+
candidates.push(`${base}.js`, `${base}.cjs`, `${base}.mjs`);
|
|
84
|
+
candidates.push(join(base, dirIndex));
|
|
85
|
+
}
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
if (await fileExists(candidate)) return candidate;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
};
|
|
91
|
+
const collectStaticRequires = async (filePath, dirIndex) => {
|
|
92
|
+
const src = await fsReadFile(filePath, 'utf8');
|
|
93
|
+
const ast = parseModule(filePath, src);
|
|
94
|
+
const specs = [];
|
|
95
|
+
await walk(ast.program, {
|
|
96
|
+
enter(node) {
|
|
97
|
+
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') {
|
|
98
|
+
const spec = node.arguments[0].value;
|
|
99
|
+
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
100
|
+
specs.push(spec);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const resolved = [];
|
|
106
|
+
for (const spec of specs) {
|
|
107
|
+
const target = await resolveRequirePath(filePath, spec, dirIndex);
|
|
108
|
+
if (target) resolved.push(target);
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
111
|
+
};
|
|
112
|
+
const detectCircularRequireGraph = async (entryFile, mode, dirIndex) => {
|
|
113
|
+
const cache = new Map();
|
|
114
|
+
const visiting = new Set();
|
|
115
|
+
const visited = new Set();
|
|
116
|
+
const dfs = async (file, stack) => {
|
|
117
|
+
if (visiting.has(file)) {
|
|
118
|
+
const cycle = [...stack, file];
|
|
119
|
+
const msg = `Circular require detected: ${cycle.join(' -> ')}`;
|
|
120
|
+
if (mode === 'error') {
|
|
121
|
+
throw new Error(msg);
|
|
122
|
+
}
|
|
123
|
+
// eslint-disable-next-line no-console -- surfaced when cycle detection is warn-only
|
|
124
|
+
console.warn(msg);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (visited.has(file)) return;
|
|
128
|
+
visiting.add(file);
|
|
129
|
+
stack.push(file);
|
|
130
|
+
let deps = cache.get(file);
|
|
131
|
+
if (!deps) {
|
|
132
|
+
deps = await collectStaticRequires(file, dirIndex);
|
|
133
|
+
cache.set(file, deps);
|
|
134
|
+
}
|
|
135
|
+
for (const dep of deps) {
|
|
136
|
+
await dfs(dep, stack);
|
|
137
|
+
}
|
|
138
|
+
stack.pop();
|
|
139
|
+
visiting.delete(file);
|
|
140
|
+
visited.add(file);
|
|
141
|
+
};
|
|
142
|
+
await dfs(entryFile, []);
|
|
143
|
+
};
|
|
7
144
|
const defaultOptions = {
|
|
8
145
|
target: 'commonjs',
|
|
9
146
|
sourceType: 'auto',
|
|
10
147
|
transformSyntax: true,
|
|
11
148
|
liveBindings: 'strict',
|
|
12
149
|
rewriteSpecifier: undefined,
|
|
150
|
+
appendJsExtension: undefined,
|
|
151
|
+
appendDirectoryIndex: 'index.js',
|
|
13
152
|
dirFilename: 'inject',
|
|
14
153
|
importMeta: 'shim',
|
|
15
154
|
importMetaMain: 'shim',
|
|
155
|
+
requireMainStrategy: 'import-meta-main',
|
|
156
|
+
detectCircularRequires: 'off',
|
|
16
157
|
requireSource: 'builtin',
|
|
158
|
+
nestedRequireStrategy: 'create-require',
|
|
17
159
|
cjsDefault: 'auto',
|
|
18
160
|
topLevelAwait: 'error',
|
|
19
161
|
out: undefined,
|
|
@@ -24,28 +166,26 @@ const transform = async (filename, options = defaultOptions) => {
|
|
|
24
166
|
...defaultOptions,
|
|
25
167
|
...options
|
|
26
168
|
};
|
|
169
|
+
const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
170
|
+
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
|
171
|
+
const detectCycles = opts.detectCircularRequires ?? 'off';
|
|
27
172
|
const file = resolve(filename);
|
|
28
173
|
const code = (await readFile(file)).toString();
|
|
29
174
|
const ast = parse(filename, code);
|
|
30
175
|
let source = await format(code, ast, opts);
|
|
31
|
-
if (opts.rewriteSpecifier) {
|
|
32
|
-
const code = await specifier.updateSrc(source, getLangFromExt(filename),
|
|
33
|
-
value
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Collapse any BinaryExpression or NewExpression to test for a relative specifier
|
|
40
|
-
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
41
|
-
const relative = /^(?:\.|\.\.)\//;
|
|
42
|
-
if (relative.test(collapsed)) {
|
|
43
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
44
|
-
return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"]*)?$/, `$1${opts.rewriteSpecifier}$2`);
|
|
45
|
-
}
|
|
176
|
+
if (opts.rewriteSpecifier || appendMode !== 'off' || dirIndex) {
|
|
177
|
+
const code = await specifier.updateSrc(source, getLangFromExt(filename), spec => {
|
|
178
|
+
const normalized = normalizeBuiltinSpecifier(spec.value);
|
|
179
|
+
const rewritten = rewriteSpecifierValue(normalized ?? spec.value, opts.rewriteSpecifier);
|
|
180
|
+
const baseValue = rewritten ?? normalized ?? spec.value;
|
|
181
|
+
const appended = appendExtensionIfNeeded(spec, appendMode, dirIndex, baseValue);
|
|
182
|
+
return appended ?? rewritten ?? normalized ?? undefined;
|
|
46
183
|
});
|
|
47
184
|
source = code;
|
|
48
185
|
}
|
|
186
|
+
if (detectCycles !== 'off' && opts.target === 'module' && opts.transformSyntax) {
|
|
187
|
+
await detectCircularRequireGraph(file, detectCycles, dirIndex || 'index.js');
|
|
188
|
+
}
|
|
49
189
|
const outputPath = opts.inPlace ? file : opts.out ? resolve(opts.out) : undefined;
|
|
50
190
|
if (outputPath) {
|
|
51
191
|
await writeFile(outputPath, source);
|
package/dist/specifier.js
CHANGED
|
@@ -121,7 +121,7 @@ const formatSpecifiers = async (src, ast, cb) => {
|
|
|
121
121
|
}
|
|
122
122
|
if (node.type === 'CallExpression') {
|
|
123
123
|
// Handle require(), require.resolve(), import.meta.resolve()
|
|
124
|
-
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') {
|
|
124
|
+
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') {
|
|
125
125
|
formatExpression(node);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -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.js';
|
|
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/src/types.d.ts
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
import type { Node, Span, IdentifierName, IdentifierReference, BindingIdentifier, LabelIdentifier, TSIndexSignatureName } from 'oxc-parser';
|
|
2
2
|
export type RewriteSpecifier = '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts' | ((value: string) => string | null | undefined);
|
|
3
|
+
/** Options that control how modules are parsed, transformed, and emitted. */
|
|
3
4
|
export type ModuleOptions = {
|
|
5
|
+
/** Output format to emit. */
|
|
4
6
|
target: 'module' | 'commonjs';
|
|
7
|
+
/** Explicit source type; auto infers from file extension. */
|
|
5
8
|
sourceType?: 'auto' | 'module' | 'commonjs';
|
|
9
|
+
/** Enable syntax transforms beyond parsing. */
|
|
6
10
|
transformSyntax?: boolean;
|
|
11
|
+
/** How to emit live bindings for ESM exports. */
|
|
7
12
|
liveBindings?: 'strict' | 'loose' | 'off';
|
|
13
|
+
/** Rewrite import specifiers (e.g. add extensions). */
|
|
8
14
|
rewriteSpecifier?: RewriteSpecifier;
|
|
15
|
+
/** Whether to append .js to relative imports. */
|
|
16
|
+
appendJsExtension?: 'off' | 'relative-only' | 'all';
|
|
17
|
+
/** Add directory index (e.g. /index.js) or disable. */
|
|
18
|
+
appendDirectoryIndex?: string | false;
|
|
19
|
+
/** Control __dirname and __filename handling. */
|
|
9
20
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
21
|
+
/** How to treat import.meta. */
|
|
10
22
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
23
|
+
/** Strategy for import.meta.main emulation. */
|
|
11
24
|
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
25
|
+
/** Resolution strategy for detecting the main module. */
|
|
26
|
+
requireMainStrategy?: 'import-meta-main' | 'realpath';
|
|
27
|
+
/** Detect circular require usage level. */
|
|
28
|
+
detectCircularRequires?: 'off' | 'warn' | 'error';
|
|
29
|
+
/** Source used to provide require in ESM output. */
|
|
12
30
|
requireSource?: 'builtin' | 'create-require';
|
|
31
|
+
/** How to rewrite nested or non-hoistable require calls. */
|
|
32
|
+
nestedRequireStrategy?: 'create-require' | 'dynamic-import';
|
|
33
|
+
/** Default interop style for CommonJS default imports. */
|
|
13
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
|
+
/** Handling for top-level await constructs. */
|
|
14
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Output directory or file path when writing. */
|
|
15
38
|
out?: string;
|
|
39
|
+
/** Overwrite input files instead of writing to out. */
|
|
16
40
|
inPlace?: boolean;
|
|
17
41
|
};
|
|
18
42
|
export type SpannedNode = Node & Span;
|
package/dist/types.d.cts
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
import type { Node, Span, IdentifierName, IdentifierReference, BindingIdentifier, LabelIdentifier, TSIndexSignatureName } from 'oxc-parser';
|
|
2
2
|
export type RewriteSpecifier = '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts' | ((value: string) => string | null | undefined);
|
|
3
|
+
/** Options that control how modules are parsed, transformed, and emitted. */
|
|
3
4
|
export type ModuleOptions = {
|
|
5
|
+
/** Output format to emit. */
|
|
4
6
|
target: 'module' | 'commonjs';
|
|
7
|
+
/** Explicit source type; auto infers from file extension. */
|
|
5
8
|
sourceType?: 'auto' | 'module' | 'commonjs';
|
|
9
|
+
/** Enable syntax transforms beyond parsing. */
|
|
6
10
|
transformSyntax?: boolean;
|
|
11
|
+
/** How to emit live bindings for ESM exports. */
|
|
7
12
|
liveBindings?: 'strict' | 'loose' | 'off';
|
|
13
|
+
/** Rewrite import specifiers (e.g. add extensions). */
|
|
8
14
|
rewriteSpecifier?: RewriteSpecifier;
|
|
15
|
+
/** Whether to append .js to relative imports. */
|
|
16
|
+
appendJsExtension?: 'off' | 'relative-only' | 'all';
|
|
17
|
+
/** Add directory index (e.g. /index.js) or disable. */
|
|
18
|
+
appendDirectoryIndex?: string | false;
|
|
19
|
+
/** Control __dirname and __filename handling. */
|
|
9
20
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
21
|
+
/** How to treat import.meta. */
|
|
10
22
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
23
|
+
/** Strategy for import.meta.main emulation. */
|
|
11
24
|
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
25
|
+
/** Resolution strategy for detecting the main module. */
|
|
26
|
+
requireMainStrategy?: 'import-meta-main' | 'realpath';
|
|
27
|
+
/** Detect circular require usage level. */
|
|
28
|
+
detectCircularRequires?: 'off' | 'warn' | 'error';
|
|
29
|
+
/** Source used to provide require in ESM output. */
|
|
12
30
|
requireSource?: 'builtin' | 'create-require';
|
|
31
|
+
/** How to rewrite nested or non-hoistable require calls. */
|
|
32
|
+
nestedRequireStrategy?: 'create-require' | 'dynamic-import';
|
|
33
|
+
/** Default interop style for CommonJS default imports. */
|
|
13
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
|
+
/** Handling for top-level await constructs. */
|
|
14
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Output directory or file path when writing. */
|
|
15
38
|
out?: string;
|
|
39
|
+
/** Overwrite input files instead of writing to out. */
|
|
16
40
|
inPlace?: boolean;
|
|
17
41
|
};
|
|
18
42
|
export type SpannedNode = Node & Span;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
import type { Node, Span, IdentifierName, IdentifierReference, BindingIdentifier, LabelIdentifier, TSIndexSignatureName } from 'oxc-parser';
|
|
2
2
|
export type RewriteSpecifier = '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts' | ((value: string) => string | null | undefined);
|
|
3
|
+
/** Options that control how modules are parsed, transformed, and emitted. */
|
|
3
4
|
export type ModuleOptions = {
|
|
5
|
+
/** Output format to emit. */
|
|
4
6
|
target: 'module' | 'commonjs';
|
|
7
|
+
/** Explicit source type; auto infers from file extension. */
|
|
5
8
|
sourceType?: 'auto' | 'module' | 'commonjs';
|
|
9
|
+
/** Enable syntax transforms beyond parsing. */
|
|
6
10
|
transformSyntax?: boolean;
|
|
11
|
+
/** How to emit live bindings for ESM exports. */
|
|
7
12
|
liveBindings?: 'strict' | 'loose' | 'off';
|
|
13
|
+
/** Rewrite import specifiers (e.g. add extensions). */
|
|
8
14
|
rewriteSpecifier?: RewriteSpecifier;
|
|
15
|
+
/** Whether to append .js to relative imports. */
|
|
16
|
+
appendJsExtension?: 'off' | 'relative-only' | 'all';
|
|
17
|
+
/** Add directory index (e.g. /index.js) or disable. */
|
|
18
|
+
appendDirectoryIndex?: string | false;
|
|
19
|
+
/** Control __dirname and __filename handling. */
|
|
9
20
|
dirFilename?: 'inject' | 'preserve' | 'error';
|
|
21
|
+
/** How to treat import.meta. */
|
|
10
22
|
importMeta?: 'preserve' | 'shim' | 'error';
|
|
23
|
+
/** Strategy for import.meta.main emulation. */
|
|
11
24
|
importMetaMain?: 'shim' | 'warn' | 'error';
|
|
25
|
+
/** Resolution strategy for detecting the main module. */
|
|
26
|
+
requireMainStrategy?: 'import-meta-main' | 'realpath';
|
|
27
|
+
/** Detect circular require usage level. */
|
|
28
|
+
detectCircularRequires?: 'off' | 'warn' | 'error';
|
|
29
|
+
/** Source used to provide require in ESM output. */
|
|
12
30
|
requireSource?: 'builtin' | 'create-require';
|
|
31
|
+
/** How to rewrite nested or non-hoistable require calls. */
|
|
32
|
+
nestedRequireStrategy?: 'create-require' | 'dynamic-import';
|
|
33
|
+
/** Default interop style for CommonJS default imports. */
|
|
13
34
|
cjsDefault?: 'module-exports' | 'auto' | 'none';
|
|
35
|
+
/** Handling for top-level await constructs. */
|
|
14
36
|
topLevelAwait?: 'error' | 'wrap' | 'preserve';
|
|
37
|
+
/** Output directory or file path when writing. */
|
|
15
38
|
out?: string;
|
|
39
|
+
/** Overwrite input files instead of writing to out. */
|
|
16
40
|
inPlace?: boolean;
|
|
17
41
|
};
|
|
18
42
|
export type SpannedNode = Node & Span;
|
package/dist/utils/exports.js
CHANGED
|
@@ -11,7 +11,9 @@ const literalPropName = (prop, literals) => {
|
|
|
11
11
|
}
|
|
12
12
|
return null;
|
|
13
13
|
};
|
|
14
|
-
const
|
|
14
|
+
const bindsThis = ancestor => ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression';
|
|
15
|
+
const isTopLevelThis = ancestors => !ancestors?.some(node => bindsThis(node));
|
|
16
|
+
const resolveExportTarget = (node, aliases, literals, ancestors) => {
|
|
15
17
|
if (node.type === 'Identifier' && node.name === 'exports') {
|
|
16
18
|
return {
|
|
17
19
|
key: 'default',
|
|
@@ -29,7 +31,7 @@ const resolveExportTarget = (node, aliases, literals) => {
|
|
|
29
31
|
const prop = node.property;
|
|
30
32
|
const key = literalPropName(prop, literals);
|
|
31
33
|
if (!key) return null;
|
|
32
|
-
const baseVia = resolveBase(base, aliases);
|
|
34
|
+
const baseVia = resolveBase(base, aliases, ancestors);
|
|
33
35
|
if (!baseVia) return null;
|
|
34
36
|
if (baseVia === 'module.exports' && key === 'exports') {
|
|
35
37
|
return {
|
|
@@ -42,12 +44,15 @@ const resolveExportTarget = (node, aliases, literals) => {
|
|
|
42
44
|
via: baseVia
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
|
-
const resolveBase = (node, aliases) => {
|
|
47
|
+
const resolveBase = (node, aliases, ancestors) => {
|
|
46
48
|
if (node.type === 'Identifier') {
|
|
47
49
|
if (node.name === 'exports') return 'exports';
|
|
48
50
|
const alias = aliases.get(node.name);
|
|
49
51
|
if (alias) return alias;
|
|
50
52
|
}
|
|
53
|
+
if (node.type === 'ThisExpression' && isTopLevelThis(ancestors)) {
|
|
54
|
+
return 'exports';
|
|
55
|
+
}
|
|
51
56
|
if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'module' && node.property.type === 'Identifier' && node.property.name === 'exports') {
|
|
52
57
|
return 'module.exports';
|
|
53
58
|
}
|
|
@@ -79,22 +84,24 @@ const collectCjsExports = async ast => {
|
|
|
79
84
|
exportsMap.set(ref.key, entry);
|
|
80
85
|
};
|
|
81
86
|
await ancestorWalk(ast, {
|
|
82
|
-
enter(node) {
|
|
87
|
+
enter(node, ancestors) {
|
|
83
88
|
if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier' && node.init) {
|
|
84
|
-
const via = resolveBase(node.init, aliases);
|
|
89
|
+
const via = resolveBase(node.init, aliases, ancestors);
|
|
85
90
|
if (via) {
|
|
86
91
|
aliases.set(node.id.name, via);
|
|
87
92
|
}
|
|
93
|
+
const parentDecl = ancestors?.[ancestors.length - 2];
|
|
94
|
+
const isConstDecl = parentDecl?.type === 'VariableDeclaration' && parentDecl.kind === 'const';
|
|
88
95
|
if (node.init.type === 'Literal' && (typeof node.init.value === 'string' || typeof node.init.value === 'number')) {
|
|
89
|
-
literals.set(node.id.name, node.init.value);
|
|
96
|
+
if (isConstDecl) literals.set(node.id.name, node.init.value);
|
|
90
97
|
}
|
|
91
98
|
if (node.init.type === 'TemplateLiteral' && node.init.expressions.length === 0 && node.init.quasis.length === 1) {
|
|
92
99
|
const cooked = node.init.quasis[0].value.cooked ?? node.init.quasis[0].value.raw;
|
|
93
|
-
literals.set(node.id.name, cooked);
|
|
100
|
+
if (isConstDecl) literals.set(node.id.name, cooked);
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
if (node.type === 'AssignmentExpression') {
|
|
97
|
-
const target = resolveExportTarget(node.left, aliases, literals);
|
|
104
|
+
const target = resolveExportTarget(node.left, aliases, literals, ancestors);
|
|
98
105
|
if (target) {
|
|
99
106
|
const rhsIdent = node.right.type === 'Identifier' ? node.right : undefined;
|
|
100
107
|
addExport(target, node, rhsIdent);
|
|
@@ -112,15 +119,31 @@ const collectCjsExports = async ast => {
|
|
|
112
119
|
});
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
|
-
if (node.left.type === 'ObjectPattern') {
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
const ref = resolveExportTarget(
|
|
119
|
-
if (ref)
|
|
120
|
-
|
|
122
|
+
if (node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern') {
|
|
123
|
+
const findExportRefs = pattern => {
|
|
124
|
+
if (pattern.type === 'MemberExpression') {
|
|
125
|
+
const ref = resolveExportTarget(pattern, aliases, literals, ancestors);
|
|
126
|
+
if (ref) addExport(ref, node);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (pattern.type === 'ObjectPattern') {
|
|
130
|
+
for (const prop of pattern.properties) {
|
|
131
|
+
const target = prop.type === 'Property' ? prop.value : prop.argument;
|
|
132
|
+
if (target) findExportRefs(target);
|
|
121
133
|
}
|
|
134
|
+
return;
|
|
122
135
|
}
|
|
123
|
-
|
|
136
|
+
if (pattern.type === 'ArrayPattern') {
|
|
137
|
+
for (const el of pattern.elements) {
|
|
138
|
+
if (el) findExportRefs(el);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (pattern.type === 'RestElement') {
|
|
143
|
+
findExportRefs(pattern.argument);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
findExportRefs(node.left);
|
|
124
147
|
}
|
|
125
148
|
}
|
|
126
149
|
if (node.type === 'CallExpression') {
|
|
@@ -129,7 +152,7 @@ const collectCjsExports = async ast => {
|
|
|
129
152
|
// Object.assign(exports, { foo: bar })
|
|
130
153
|
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'assign' && node.arguments.length >= 2) {
|
|
131
154
|
const targetArg = node.arguments[0];
|
|
132
|
-
const ref = resolveBase(targetArg, aliases);
|
|
155
|
+
const ref = resolveBase(targetArg, aliases, ancestors);
|
|
133
156
|
if (!ref) return;
|
|
134
157
|
for (let i = 1; i < node.arguments.length; i++) {
|
|
135
158
|
const arg = node.arguments[i];
|
|
@@ -153,7 +176,7 @@ const collectCjsExports = async ast => {
|
|
|
153
176
|
|
|
154
177
|
// Object.defineProperty(exports, 'foo', { value, get, set })
|
|
155
178
|
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperty' && node.arguments.length >= 3) {
|
|
156
|
-
const target = resolveBase(node.arguments[0], aliases);
|
|
179
|
+
const target = resolveBase(node.arguments[0], aliases, ancestors);
|
|
157
180
|
if (!target) return;
|
|
158
181
|
const keyName = literalPropName(node.arguments[1], literals);
|
|
159
182
|
if (!keyName) return;
|
|
@@ -184,7 +207,7 @@ const collectCjsExports = async ast => {
|
|
|
184
207
|
|
|
185
208
|
// Object.defineProperties(exports, { foo: { value: ... }, bar: { get: ... } })
|
|
186
209
|
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperties' && node.arguments.length >= 2) {
|
|
187
|
-
const target = resolveBase(node.arguments[0], aliases);
|
|
210
|
+
const target = resolveBase(node.arguments[0], aliases, ancestors);
|
|
188
211
|
if (!target) return;
|
|
189
212
|
const descMap = node.arguments[1];
|
|
190
213
|
if (descMap.type !== 'ObjectExpression') return;
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { ancestorWalk } from '#walk';
|
|
2
2
|
import { identifier } from '#helpers/identifier.js';
|
|
3
3
|
import { scopeNodes } from './scopeNodes.js';
|
|
4
|
+
const addBindingNames = (pattern, into) => {
|
|
5
|
+
if (!pattern) return;
|
|
6
|
+
switch (pattern.type) {
|
|
7
|
+
case 'Identifier':
|
|
8
|
+
into.add(pattern.name);
|
|
9
|
+
return;
|
|
10
|
+
case 'AssignmentPattern':
|
|
11
|
+
addBindingNames(pattern.left, into);
|
|
12
|
+
return;
|
|
13
|
+
case 'RestElement':
|
|
14
|
+
addBindingNames(pattern.argument, into);
|
|
15
|
+
return;
|
|
16
|
+
case 'ObjectPattern':
|
|
17
|
+
for (const prop of pattern.properties ?? []) {
|
|
18
|
+
if (prop.type === 'Property') {
|
|
19
|
+
addBindingNames(prop.value, into);
|
|
20
|
+
} else if (prop.type === 'RestElement') {
|
|
21
|
+
addBindingNames(prop.argument, into);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
case 'ArrayPattern':
|
|
26
|
+
for (const elem of pattern.elements ?? []) {
|
|
27
|
+
if (elem) addBindingNames(elem, into);
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
4
32
|
const collectScopeIdentifiers = (node, scopes) => {
|
|
5
33
|
const {
|
|
6
34
|
type
|
|
@@ -26,20 +54,10 @@ const collectScopeIdentifiers = (node, scopes) => {
|
|
|
26
54
|
type: 'Function',
|
|
27
55
|
idents: new Set()
|
|
28
56
|
};
|
|
29
|
-
node.params
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (param.type === 'RestElement') {
|
|
34
|
-
return param.argument;
|
|
35
|
-
}
|
|
36
|
-
if (param.type === 'AssignmentPattern') {
|
|
37
|
-
return param.left;
|
|
38
|
-
}
|
|
39
|
-
return param;
|
|
40
|
-
}).filter(identifier.isNamed).forEach(param => {
|
|
41
|
-
scope.idents.add(param.name);
|
|
42
|
-
});
|
|
57
|
+
for (const param of node.params) {
|
|
58
|
+
const normalized = param.type === 'TSParameterProperty' ? param.parameter : param;
|
|
59
|
+
addBindingNames(normalized, scope.idents);
|
|
60
|
+
}
|
|
43
61
|
|
|
44
62
|
/**
|
|
45
63
|
* If a FunctionExpression has an id, it is a named function expression.
|
|
@@ -88,8 +106,8 @@ const collectScopeIdentifiers = (node, scopes) => {
|
|
|
88
106
|
if (scopes.length > 0) {
|
|
89
107
|
const scope = scopes[scopes.length - 1];
|
|
90
108
|
node.declarations.forEach(decl => {
|
|
91
|
-
if (decl.type === 'VariableDeclarator'
|
|
92
|
-
|
|
109
|
+
if (decl.type === 'VariableDeclarator') {
|
|
110
|
+
addBindingNames(decl.id, scope.idents);
|
|
93
111
|
}
|
|
94
112
|
});
|
|
95
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/module",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.3",
|
|
4
4
|
"description": "Transforms differences between ES modules and CommonJS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/module.js",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
21
|
"imports": {
|
|
22
|
-
"#parse": "./
|
|
23
|
-
"#format": "./
|
|
24
|
-
"#utils/*.js": "./
|
|
25
|
-
"#walk": "./
|
|
26
|
-
"#helpers/*.js": "./
|
|
27
|
-
"#formatters/*.js": "./
|
|
22
|
+
"#parse": "./dist/parse.js",
|
|
23
|
+
"#format": "./dist/format.js",
|
|
24
|
+
"#utils/*.js": "./dist/utils/*.js",
|
|
25
|
+
"#walk": "./dist/walk.js",
|
|
26
|
+
"#helpers/*.js": "./dist/helpers/*.js",
|
|
27
|
+
"#formatters/*.js": "./dist/formatters/*.js"
|
|
28
28
|
},
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=22.21.1"
|
|
30
|
+
"node": ">=22.21.1 <23 || >=24 <25"
|
|
31
31
|
},
|
|
32
32
|
"engineStrict": true,
|
|
33
33
|
"scripts": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"build:types": "tsc --emitDeclarationOnly",
|
|
41
41
|
"build:dual": "babel-dual-package src --extensions .ts",
|
|
42
42
|
"build": "npm run build:types && npm run build:dual",
|
|
43
|
-
"prepack": "npm run build"
|
|
43
|
+
"prepack": "npm run build && node scripts/setImportsToDist.js",
|
|
44
|
+
"postpack": "node scripts/restoreImportsToSrc.js"
|
|
44
45
|
},
|
|
45
46
|
"keywords": [
|
|
46
47
|
"transform",
|