@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/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
- if (typeof opts.rewriteSpecifier === 'function') {
36
- return opts.rewriteSpecifier(value) ?? undefined;
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
- export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>) => void;
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 {};
@@ -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;
@@ -11,7 +11,9 @@ const literalPropName = (prop, literals) => {
11
11
  }
12
12
  return null;
13
13
  };
14
- const resolveExportTarget = (node, aliases, literals) => {
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
- for (const prop of node.left.properties) {
117
- if (prop.type === 'Property' && prop.value.type === 'MemberExpression') {
118
- const ref = resolveExportTarget(prop.value, aliases, literals);
119
- if (ref) {
120
- addExport(ref, node);
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.map(param => {
30
- if (param.type === 'TSParameterProperty') {
31
- return param.parameter;
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' && decl.id.type === 'Identifier') {
92
- scope.idents.add(decl.id.name);
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.1",
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": "./src/parse.js",
23
- "#format": "./src/format.js",
24
- "#utils/*.js": "./src/utils/*.js",
25
- "#walk": "./src/walk.js",
26
- "#helpers/*.js": "./src/helpers/*.js",
27
- "#formatters/*.js": "./src/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",