@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.
@@ -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;
@@ -17,7 +17,9 @@ const literalPropName = (prop, literals) => {
17
17
  }
18
18
  return null;
19
19
  };
20
- const resolveExportTarget = (node, aliases, literals) => {
20
+ const bindsThis = ancestor => ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression';
21
+ const isTopLevelThis = ancestors => !ancestors?.some(node => bindsThis(node));
22
+ const resolveExportTarget = (node, aliases, literals, ancestors) => {
21
23
  if (node.type === 'Identifier' && node.name === 'exports') {
22
24
  return {
23
25
  key: 'default',
@@ -35,7 +37,7 @@ const resolveExportTarget = (node, aliases, literals) => {
35
37
  const prop = node.property;
36
38
  const key = literalPropName(prop, literals);
37
39
  if (!key) return null;
38
- const baseVia = resolveBase(base, aliases);
40
+ const baseVia = resolveBase(base, aliases, ancestors);
39
41
  if (!baseVia) return null;
40
42
  if (baseVia === 'module.exports' && key === 'exports') {
41
43
  return {
@@ -48,12 +50,15 @@ const resolveExportTarget = (node, aliases, literals) => {
48
50
  via: baseVia
49
51
  };
50
52
  };
51
- const resolveBase = (node, aliases) => {
53
+ const resolveBase = (node, aliases, ancestors) => {
52
54
  if (node.type === 'Identifier') {
53
55
  if (node.name === 'exports') return 'exports';
54
56
  const alias = aliases.get(node.name);
55
57
  if (alias) return alias;
56
58
  }
59
+ if (node.type === 'ThisExpression' && isTopLevelThis(ancestors)) {
60
+ return 'exports';
61
+ }
57
62
  if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'module' && node.property.type === 'Identifier' && node.property.name === 'exports') {
58
63
  return 'module.exports';
59
64
  }
@@ -85,22 +90,24 @@ const collectCjsExports = async ast => {
85
90
  exportsMap.set(ref.key, entry);
86
91
  };
87
92
  await (0, _walk.ancestorWalk)(ast, {
88
- enter(node) {
93
+ enter(node, ancestors) {
89
94
  if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier' && node.init) {
90
- const via = resolveBase(node.init, aliases);
95
+ const via = resolveBase(node.init, aliases, ancestors);
91
96
  if (via) {
92
97
  aliases.set(node.id.name, via);
93
98
  }
99
+ const parentDecl = ancestors?.[ancestors.length - 2];
100
+ const isConstDecl = parentDecl?.type === 'VariableDeclaration' && parentDecl.kind === 'const';
94
101
  if (node.init.type === 'Literal' && (typeof node.init.value === 'string' || typeof node.init.value === 'number')) {
95
- literals.set(node.id.name, node.init.value);
102
+ if (isConstDecl) literals.set(node.id.name, node.init.value);
96
103
  }
97
104
  if (node.init.type === 'TemplateLiteral' && node.init.expressions.length === 0 && node.init.quasis.length === 1) {
98
105
  const cooked = node.init.quasis[0].value.cooked ?? node.init.quasis[0].value.raw;
99
- literals.set(node.id.name, cooked);
106
+ if (isConstDecl) literals.set(node.id.name, cooked);
100
107
  }
101
108
  }
102
109
  if (node.type === 'AssignmentExpression') {
103
- const target = resolveExportTarget(node.left, aliases, literals);
110
+ const target = resolveExportTarget(node.left, aliases, literals, ancestors);
104
111
  if (target) {
105
112
  const rhsIdent = node.right.type === 'Identifier' ? node.right : undefined;
106
113
  addExport(target, node, rhsIdent);
@@ -118,15 +125,31 @@ const collectCjsExports = async ast => {
118
125
  });
119
126
  }
120
127
  }
121
- if (node.left.type === 'ObjectPattern') {
122
- for (const prop of node.left.properties) {
123
- if (prop.type === 'Property' && prop.value.type === 'MemberExpression') {
124
- const ref = resolveExportTarget(prop.value, aliases, literals);
125
- if (ref) {
126
- addExport(ref, node);
128
+ if (node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern') {
129
+ const findExportRefs = pattern => {
130
+ if (pattern.type === 'MemberExpression') {
131
+ const ref = resolveExportTarget(pattern, aliases, literals, ancestors);
132
+ if (ref) addExport(ref, node);
133
+ return;
134
+ }
135
+ if (pattern.type === 'ObjectPattern') {
136
+ for (const prop of pattern.properties) {
137
+ const target = prop.type === 'Property' ? prop.value : prop.argument;
138
+ if (target) findExportRefs(target);
127
139
  }
140
+ return;
128
141
  }
129
- }
142
+ if (pattern.type === 'ArrayPattern') {
143
+ for (const el of pattern.elements) {
144
+ if (el) findExportRefs(el);
145
+ }
146
+ return;
147
+ }
148
+ if (pattern.type === 'RestElement') {
149
+ findExportRefs(pattern.argument);
150
+ }
151
+ };
152
+ findExportRefs(node.left);
130
153
  }
131
154
  }
132
155
  if (node.type === 'CallExpression') {
@@ -135,7 +158,7 @@ const collectCjsExports = async ast => {
135
158
  // Object.assign(exports, { foo: bar })
136
159
  if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'assign' && node.arguments.length >= 2) {
137
160
  const targetArg = node.arguments[0];
138
- const ref = resolveBase(targetArg, aliases);
161
+ const ref = resolveBase(targetArg, aliases, ancestors);
139
162
  if (!ref) return;
140
163
  for (let i = 1; i < node.arguments.length; i++) {
141
164
  const arg = node.arguments[i];
@@ -159,7 +182,7 @@ const collectCjsExports = async ast => {
159
182
 
160
183
  // Object.defineProperty(exports, 'foo', { value, get, set })
161
184
  if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperty' && node.arguments.length >= 3) {
162
- const target = resolveBase(node.arguments[0], aliases);
185
+ const target = resolveBase(node.arguments[0], aliases, ancestors);
163
186
  if (!target) return;
164
187
  const keyName = literalPropName(node.arguments[1], literals);
165
188
  if (!keyName) return;
@@ -190,7 +213,7 @@ const collectCjsExports = async ast => {
190
213
 
191
214
  // Object.defineProperties(exports, { foo: { value: ... }, bar: { get: ... } })
192
215
  if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperties' && node.arguments.length >= 2) {
193
- const target = resolveBase(node.arguments[0], aliases);
216
+ const target = resolveBase(node.arguments[0], aliases, ancestors);
194
217
  if (!target) return;
195
218
  const descMap = node.arguments[1];
196
219
  if (descMap.type !== 'ObjectExpression') return;
@@ -7,6 +7,34 @@ exports.collectScopeIdentifiers = exports.collectModuleIdentifiers = void 0;
7
7
  var _walk = require("#walk");
8
8
  var _identifier = require("#helpers/identifier.js");
9
9
  var _scopeNodes = require("./scopeNodes.cjs");
10
+ const addBindingNames = (pattern, into) => {
11
+ if (!pattern) return;
12
+ switch (pattern.type) {
13
+ case 'Identifier':
14
+ into.add(pattern.name);
15
+ return;
16
+ case 'AssignmentPattern':
17
+ addBindingNames(pattern.left, into);
18
+ return;
19
+ case 'RestElement':
20
+ addBindingNames(pattern.argument, into);
21
+ return;
22
+ case 'ObjectPattern':
23
+ for (const prop of pattern.properties ?? []) {
24
+ if (prop.type === 'Property') {
25
+ addBindingNames(prop.value, into);
26
+ } else if (prop.type === 'RestElement') {
27
+ addBindingNames(prop.argument, into);
28
+ }
29
+ }
30
+ return;
31
+ case 'ArrayPattern':
32
+ for (const elem of pattern.elements ?? []) {
33
+ if (elem) addBindingNames(elem, into);
34
+ }
35
+ return;
36
+ }
37
+ };
10
38
  const collectScopeIdentifiers = (node, scopes) => {
11
39
  const {
12
40
  type
@@ -32,20 +60,10 @@ const collectScopeIdentifiers = (node, scopes) => {
32
60
  type: 'Function',
33
61
  idents: new Set()
34
62
  };
35
- node.params.map(param => {
36
- if (param.type === 'TSParameterProperty') {
37
- return param.parameter;
38
- }
39
- if (param.type === 'RestElement') {
40
- return param.argument;
41
- }
42
- if (param.type === 'AssignmentPattern') {
43
- return param.left;
44
- }
45
- return param;
46
- }).filter(_identifier.identifier.isNamed).forEach(param => {
47
- scope.idents.add(param.name);
48
- });
63
+ for (const param of node.params) {
64
+ const normalized = param.type === 'TSParameterProperty' ? param.parameter : param;
65
+ addBindingNames(normalized, scope.idents);
66
+ }
49
67
 
50
68
  /**
51
69
  * If a FunctionExpression has an id, it is a named function expression.
@@ -94,8 +112,8 @@ const collectScopeIdentifiers = (node, scopes) => {
94
112
  if (scopes.length > 0) {
95
113
  const scope = scopes[scopes.length - 1];
96
114
  node.declarations.forEach(decl => {
97
- if (decl.type === 'VariableDeclarator' && decl.id.type === 'Identifier') {
98
- scope.idents.add(decl.id.name);
115
+ if (decl.type === 'VariableDeclarator') {
116
+ addBindingNames(decl.id, scope.idents);
99
117
  }
100
118
  });
101
119
  }
package/dist/format.js CHANGED
@@ -19,6 +19,8 @@ const exportAssignment = (name, expr, live) => {
19
19
  };
20
20
  const defaultInteropName = '__interopDefault';
21
21
  const interopHelper = `const ${defaultInteropName} = mod => (mod && mod.__esModule ? mod.default : mod);\n`;
22
+ const requireInteropName = '__requireDefault';
23
+ const requireInteropHelper = `const ${requireInteropName} = mod => (mod && typeof mod === 'object' && 'default' in mod ? mod.default : mod);\n`;
22
24
  const isRequireCallee = (callee, shadowed) => {
23
25
  if (callee.type === 'Identifier' && callee.name === 'require' && !shadowed.has('require')) {
24
26
  return true;
@@ -33,8 +35,14 @@ const isRequireCall = (node, shadowed) => node.type === 'CallExpression' && isRe
33
35
  const lowerCjsRequireToImports = (program, code, shadowed) => {
34
36
  const transforms = [];
35
37
  const imports = [];
38
+ const hoisted = [];
36
39
  let nsIndex = 0;
37
40
  let needsCreateRequire = false;
41
+ let needsInteropHelper = false;
42
+ const isJsonSpecifier = value => {
43
+ const base = value.split(/[?#]/)[0] ?? value;
44
+ return base.endsWith('.json');
45
+ };
38
46
  for (const stmt of program.body) {
39
47
  if (stmt.type === 'VariableDeclaration') {
40
48
  const decls = stmt.declarations;
@@ -42,14 +50,21 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
42
50
  if (allStatic) {
43
51
  for (const decl of decls) {
44
52
  const init = decl.init;
45
- const source = code.slice(init.arguments[0].start, init.arguments[0].end);
53
+ const arg = init.arguments[0];
54
+ const source = code.slice(arg.start, arg.end);
55
+ const value = arg.value;
56
+ const isJson = typeof value === 'string' && isJsonSpecifier(value);
57
+ const ns = `__cjsImport${nsIndex++}`;
58
+ const jsonImport = isJson ? `${source} with { type: "json" }` : source;
46
59
  if (decl.id.type === 'Identifier') {
47
- imports.push(`import * as ${decl.id.name} from ${source};\n`);
48
- } else if (decl.id.type === 'ObjectPattern') {
49
- const ns = `__cjsImport${nsIndex++}`;
60
+ imports.push(isJson ? `import ${ns} from ${jsonImport};\n` : `import * as ${ns} from ${jsonImport};\n`);
61
+ hoisted.push(isJson ? `const ${decl.id.name} = ${ns};\n` : `const ${decl.id.name} = ${requireInteropName}(${ns});\n`);
62
+ needsInteropHelper ||= !isJson;
63
+ } else if (decl.id.type === 'ObjectPattern' || decl.id.type === 'ArrayPattern') {
50
64
  const pattern = code.slice(decl.id.start, decl.id.end);
51
- imports.push(`import * as ${ns} from ${source};\n`);
52
- imports.push(`const ${pattern} = ${ns};\n`);
65
+ imports.push(isJson ? `import ${ns} from ${jsonImport};\n` : `import * as ${ns} from ${jsonImport};\n`);
66
+ hoisted.push(isJson ? `const ${pattern} = ${ns};\n` : `const ${pattern} = ${requireInteropName}(${ns});\n`);
67
+ needsInteropHelper ||= !isJson;
53
68
  } else {
54
69
  needsCreateRequire = true;
55
70
  }
@@ -71,8 +86,12 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
71
86
  if (stmt.type === 'ExpressionStatement') {
72
87
  const expr = stmt.expression;
73
88
  if (expr && isStaticRequire(expr, shadowed)) {
74
- const source = code.slice(expr.arguments[0].start, expr.arguments[0].end);
75
- imports.push(`import ${source};\n`);
89
+ const arg = expr.arguments[0];
90
+ const source = code.slice(arg.start, arg.end);
91
+ const value = arg.value;
92
+ const isJson = typeof value === 'string' && isJsonSpecifier(value);
93
+ const jsonImport = isJson ? `${source} with { type: "json" }` : source;
94
+ imports.push(`import ${jsonImport};\n`);
76
95
  transforms.push({
77
96
  start: stmt.start,
78
97
  end: stmt.end,
@@ -88,7 +107,9 @@ const lowerCjsRequireToImports = (program, code, shadowed) => {
88
107
  return {
89
108
  transforms,
90
109
  imports,
91
- needsCreateRequire
110
+ hoisted,
111
+ needsCreateRequire,
112
+ needsInteropHelper
92
113
  };
93
114
  };
94
115
  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';
@@ -129,6 +150,20 @@ const hasTopLevelAwait = program => {
129
150
  walkNode(program, false);
130
151
  return found;
131
152
  };
153
+ const isAsyncContext = ancestors => {
154
+ for (let i = ancestors.length - 1; i >= 0; i -= 1) {
155
+ const node = ancestors[i];
156
+ if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
157
+ return !!node.async;
158
+ }
159
+ if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ // Program scope (top-level) supports await in ESM.
165
+ return true;
166
+ };
132
167
  const lowerEsmToCjs = (program, code, opts, containsTopLevelAwait) => {
133
168
  const live = opts.liveBindings ?? 'strict';
134
169
  const importTransforms = [];
@@ -338,11 +373,17 @@ const format = async (src, ast, opts) => {
338
373
  const exportTable = opts.target === 'module' ? await collectCjsExports(ast.program) : null;
339
374
  const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
340
375
  const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
376
+ const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
377
+ let requireMainNeedsRealpath = false;
378
+ let needsRequireResolveHelper = false;
379
+ const nestedRequireStrategy = opts.nestedRequireStrategy ?? 'create-require';
341
380
  const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
342
381
  const shouldRaiseEsm = opts.target === 'module' && opts.transformSyntax;
343
382
  let hoistedImports = [];
383
+ let hoistedStatements = [];
344
384
  let pendingRequireTransforms = [];
345
385
  let needsCreateRequire = false;
386
+ let needsImportInterop = false;
346
387
  let pendingCjsTransforms = null;
347
388
  if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
348
389
  throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
@@ -351,11 +392,15 @@ const format = async (src, ast, opts) => {
351
392
  const {
352
393
  transforms,
353
394
  imports,
354
- needsCreateRequire: reqCreate
395
+ hoisted,
396
+ needsCreateRequire: reqCreate,
397
+ needsInteropHelper: reqInteropHelper
355
398
  } = lowerCjsRequireToImports(ast.program, code, shadowedBindings);
356
399
  pendingRequireTransforms = transforms;
357
400
  hoistedImports = imports;
401
+ hoistedStatements = hoisted;
358
402
  needsCreateRequire = reqCreate;
403
+ needsImportInterop = reqInteropHelper;
359
404
  }
360
405
  await ancestorWalk(ast.program, {
361
406
  async enter(node, ancestors) {
@@ -370,7 +415,11 @@ const format = async (src, ast, opts) => {
370
415
  const rightModule = node.right.type === 'Identifier' && node.right.name === 'module' && !shadowedBindings.has('module');
371
416
  if (leftMain && rightModule || rightMain && leftModule) {
372
417
  const negate = op === '!==' || op === '!=';
373
- code.update(node.start, node.end, negate ? '!import.meta.main' : 'import.meta.main');
418
+ const mainExpr = requireMainStrategy === 'import-meta-main' ? 'import.meta.main' : 'import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href';
419
+ if (requireMainStrategy === 'realpath') {
420
+ requireMainNeedsRealpath = true;
421
+ }
422
+ code.update(node.start, node.end, negate ? `!(${mainExpr})` : mainExpr);
374
423
  return;
375
424
  }
376
425
  }
@@ -392,6 +441,18 @@ const format = async (src, ast, opts) => {
392
441
  const topLevelVarDecl = parent?.type === 'VariableDeclarator' && grandparent?.type === 'VariableDeclaration' && greatGrandparent?.type === 'Program';
393
442
  const hoistableTopLevel = isStatic && (topLevelExprStmt || topLevelVarDecl);
394
443
  if (!isStatic || !hoistableTopLevel) {
444
+ if (nestedRequireStrategy === 'dynamic-import') {
445
+ const asyncCapable = isAsyncContext(ancestors);
446
+ if (asyncCapable) {
447
+ const arg = node.arguments[0];
448
+ const argSrc = arg ? code.slice(arg.start, arg.end) : 'undefined';
449
+ const literalVal = arg?.value;
450
+ const isJson = arg?.type === 'Literal' && typeof literalVal === 'string' && (literalVal.split(/[?#]/)[0] ?? literalVal).endsWith('.json');
451
+ const importTarget = isJson ? `${argSrc} with { type: "json" }` : argSrc;
452
+ code.update(node.start, node.end, `(await import(${importTarget}))`);
453
+ return;
454
+ }
455
+ }
395
456
  needsCreateRequire = true;
396
457
  }
397
458
  }
@@ -453,7 +514,23 @@ const format = async (src, ast, opts) => {
453
514
  metaProperty(node, parent, code, opts);
454
515
  }
455
516
  if (node.type === 'MemberExpression') {
456
- memberExpression(node, parent, code, opts, shadowedBindings);
517
+ memberExpression(node, parent, code, opts, shadowedBindings, {
518
+ onRequireResolve: () => {
519
+ if (shouldRaiseEsm) needsRequireResolveHelper = true;
520
+ },
521
+ requireResolveName: '__requireResolve'
522
+ });
523
+ }
524
+ if (shouldRaiseEsm && node.type === 'ThisExpression') {
525
+ const bindsThis = ancestor => {
526
+ return ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression';
527
+ };
528
+ const bindingAncestor = ancestors.find(ancestor => bindsThis(ancestor));
529
+ const isTopLevel = !bindingAncestor;
530
+ if (isTopLevel) {
531
+ code.update(node.start, node.end, exportsRename);
532
+ return;
533
+ }
457
534
  }
458
535
  if (isIdentifierName(node)) {
459
536
  identifier({
@@ -522,14 +599,36 @@ const format = async (src, ast, opts) => {
522
599
  }
523
600
  if (shouldRaiseEsm && opts.transformSyntax) {
524
601
  const importPrelude = [];
525
- if (needsCreateRequire) {
602
+ if (needsCreateRequire || needsRequireResolveHelper) {
526
603
  importPrelude.push('import { createRequire } from "node:module";\n');
527
604
  }
605
+ if (needsRequireResolveHelper) {
606
+ importPrelude.push('import { fileURLToPath } from "node:url";\n');
607
+ }
608
+ if (requireMainNeedsRealpath) {
609
+ importPrelude.push('import { realpathSync } from "node:fs";\n');
610
+ importPrelude.push('import { pathToFileURL } from "node:url";\n');
611
+ }
528
612
  if (hoistedImports.length) {
529
613
  importPrelude.push(...hoistedImports);
530
614
  }
615
+ const setupPrelude = [];
616
+ if (needsImportInterop) {
617
+ setupPrelude.push(requireInteropHelper);
618
+ }
619
+ if (hoistedStatements.length) {
620
+ setupPrelude.push(...hoistedStatements);
621
+ }
531
622
  const requireInit = needsCreateRequire ? 'const require = createRequire(import.meta.url);\n' : '';
532
- const prelude = `${importPrelude.join('')}${importPrelude.length ? '\n' : ''}${requireInit}let ${exportsRename} = {};
623
+ const requireResolveInit = needsRequireResolveHelper ? needsCreateRequire ? `const __requireResolve = (id, parent) => {
624
+ const resolved = require.resolve(id, parent);
625
+ return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
626
+ };\n` : `const __requireResolve = (id, parent) => {
627
+ const req = createRequire(parent ?? import.meta.url);
628
+ const resolved = req.resolve(id, parent);
629
+ return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
630
+ };\n` : '';
631
+ const prelude = `${importPrelude.join('')}${importPrelude.length ? '\n' : ''}${setupPrelude.join('')}${setupPrelude.length ? '\n' : ''}${requireInit}${requireResolveInit}let ${exportsRename} = {};
533
632
  void import.meta.filename;
534
633
  `;
535
634
  code.prepend(prelude);
@@ -19,7 +19,7 @@ export const identifier = ({
19
19
  }
20
20
  switch (name) {
21
21
  case '__filename':
22
- code.update(start, end, 'import.meta.url');
22
+ code.update(start, end, 'import.meta.filename');
23
23
  break;
24
24
  case '__dirname':
25
25
  code.update(start, end, 'import.meta.dirname');
@@ -1,5 +1,5 @@
1
1
  import { exportsRename } from '#utils/exports.js';
2
- export const memberExpression = (node, parent, src, options, shadowed) => {
2
+ export const memberExpression = (node, parent, src, options, shadowed, extras) => {
3
3
  if (options.target === 'module') {
4
4
  if (node.object.type === 'Identifier' && shadowed?.has(node.object.name) || node.property.type === 'Identifier' && shadowed?.has(node.property.name)) {
5
5
  return;
@@ -26,7 +26,8 @@ export const memberExpression = (node, parent, src, options, shadowed) => {
26
26
  src.update(start, end, 'import.meta.main');
27
27
  break;
28
28
  case 'resolve':
29
- src.update(start, end, 'import.meta.resolve');
29
+ extras?.onRequireResolve?.();
30
+ src.update(start, end, extras?.requireResolveName ?? 'import.meta.resolve');
30
31
  break;
31
32
  case 'cache':
32
33
  /**
@@ -24,6 +24,31 @@ const getScopeContext = program => {
24
24
  scopeCache.set(program, context);
25
25
  return context;
26
26
  };
27
+ const isInBindingPattern = (pattern, target) => {
28
+ if (pattern === target) return true;
29
+ switch (pattern.type) {
30
+ case 'Identifier':
31
+ return pattern === target;
32
+ case 'AssignmentPattern':
33
+ return isInBindingPattern(pattern.left, target);
34
+ case 'RestElement':
35
+ return isInBindingPattern(pattern.argument, target);
36
+ case 'ObjectPattern':
37
+ return (pattern.properties ?? []).some(prop => {
38
+ if (prop.type === 'Property') {
39
+ return isInBindingPattern(prop.value, target);
40
+ }
41
+ if (prop.type === 'RestElement') {
42
+ return isInBindingPattern(prop.argument, target);
43
+ }
44
+ return false;
45
+ });
46
+ case 'ArrayPattern':
47
+ return (pattern.elements ?? []).some(elem => elem && isInBindingPattern(elem, target));
48
+ default:
49
+ return false;
50
+ }
51
+ };
27
52
 
28
53
  /**
29
54
  * All methods receive the full set of ancestors, which
@@ -42,6 +67,7 @@ const identifier = {
42
67
  isModuleScope(ancestors, includeImports = false) {
43
68
  const node = ancestors[ancestors.length - 1];
44
69
  const parent = ancestors[ancestors.length - 2];
70
+ const grandParent = ancestors[ancestors.length - 3];
45
71
  const program = ancestors[0];
46
72
  if (!identifier.isNamed(node) || identifier.isMetaProperty(ancestors) || parent.type === 'LabeledStatement' || parent.type === 'BreakStatement' || parent.type === 'ContinueStatement') {
47
73
  return false;
@@ -50,7 +76,9 @@ const identifier = {
50
76
  return includeImports && parent.local.name === node.name;
51
77
  }
52
78
  if (parent.type === 'Property' && parent.key === node && !parent.computed) {
53
- return false;
79
+ if (grandParent?.type !== 'ObjectPattern') {
80
+ return false;
81
+ }
54
82
  }
55
83
  if (parent.type === 'MemberExpression' && parent.property === node && !parent.computed) {
56
84
  return false;
@@ -72,8 +100,17 @@ const identifier = {
72
100
  },
73
101
  isDeclaration(ancestors) {
74
102
  const node = ancestors[ancestors.length - 1];
75
- const parent = ancestors[ancestors.length - 2];
76
- return (parent.type === 'VariableDeclarator' || parent.type === 'FunctionDeclaration' || parent.type === 'ClassDeclaration') && parent.id === node;
103
+ // Walk outwards to find a declarator that binds the node
104
+ for (let i = ancestors.length - 2; i >= 0; i--) {
105
+ const parent = ancestors[i];
106
+ if (parent.type === 'VariableDeclarator') {
107
+ return parent.id === node || isInBindingPattern(parent.id, node);
108
+ }
109
+ if (parent.type === 'FunctionDeclaration' || parent.type === 'ClassDeclaration') {
110
+ return parent.id === node;
111
+ }
112
+ }
113
+ return false;
77
114
  },
78
115
  isClassOrFuncDeclarationId(ancestors) {
79
116
  const node = ancestors[ancestors.length - 1];
@@ -82,12 +119,16 @@ const identifier = {
82
119
  },
83
120
  isVarDeclarationInGlobalScope(ancestors) {
84
121
  const node = ancestors[ancestors.length - 1];
85
- const parent = ancestors[ancestors.length - 2];
86
- const grandParent = ancestors[ancestors.length - 3];
87
122
  const varBoundScopes = ['ClassDeclaration', 'ClassExpression', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'];
88
- return parent.type === 'VariableDeclarator' && parent.id === node && grandParent.type === 'VariableDeclaration' && grandParent.kind === 'var' && ancestors.every(ancestor => {
89
- return !varBoundScopes.includes(ancestor.type);
123
+ const declaratorIndex = ancestors.findIndex(ancestor => {
124
+ return ancestor.type === 'VariableDeclarator' && (ancestor === node || isInBindingPattern(ancestor.id, node));
90
125
  });
126
+ if (declaratorIndex === -1) return false;
127
+ const declarator = ancestors[declaratorIndex];
128
+ const declaration = ancestors[declaratorIndex - 1];
129
+ return declaration?.type === 'VariableDeclaration' && declaration.kind === 'var' && ancestors.every(ancestor => {
130
+ return !varBoundScopes.includes(ancestor.type);
131
+ }) && (declarator.id === node || isInBindingPattern(declarator.id, node));
91
132
  },
92
133
  isIife(ancestors) {
93
134
  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
- 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,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 {};