@knighted/module 1.0.0-beta.2 → 1.0.0-beta.4

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 CHANGED
@@ -22,7 +22,7 @@ By default `@knighted/module` transforms the one-to-one [differences between ES
22
22
 
23
23
  ## Requirements
24
24
 
25
- - Node >= 20.11.0
25
+ - Node >= 22.21.1
26
26
 
27
27
  ## Install
28
28
 
@@ -134,7 +134,7 @@ Behavior notes (defaults in parentheses)
134
134
  - `cjsDefault` (`auto`): bundler-style default interop vs direct `module.exports`.
135
135
  - `out`/`inPlace`: write the transformed code to a file; otherwise the function returns the transformed string only.
136
136
 
137
- 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.
137
+ 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).
138
138
 
139
139
  ## Roadmap
140
140
 
@@ -26,6 +26,79 @@ 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 isRequireCallee = (callee, shadowed) => {
30
+ if (callee.type === 'Identifier' && callee.name === 'require' && !shadowed.has('require')) {
31
+ return true;
32
+ }
33
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'module' && !shadowed.has('module') && callee.property.type === 'Identifier' && callee.property.name === 'require') {
34
+ return true;
35
+ }
36
+ return false;
37
+ };
38
+ const isStaticRequire = (node, shadowed) => node.type === 'CallExpression' && isRequireCallee(node.callee, shadowed) && node.arguments.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string';
39
+ const isRequireCall = (node, shadowed) => node.type === 'CallExpression' && isRequireCallee(node.callee, shadowed);
40
+ const lowerCjsRequireToImports = (program, code, shadowed) => {
41
+ const transforms = [];
42
+ const imports = [];
43
+ let nsIndex = 0;
44
+ let needsCreateRequire = false;
45
+ for (const stmt of program.body) {
46
+ if (stmt.type === 'VariableDeclaration') {
47
+ const decls = stmt.declarations;
48
+ const allStatic = decls.length > 0 && decls.every(decl => decl.init && isStaticRequire(decl.init, shadowed));
49
+ if (allStatic) {
50
+ for (const decl of decls) {
51
+ const init = decl.init;
52
+ const source = code.slice(init.arguments[0].start, init.arguments[0].end);
53
+ if (decl.id.type === 'Identifier') {
54
+ imports.push(`import * as ${decl.id.name} from ${source};\n`);
55
+ } else if (decl.id.type === 'ObjectPattern') {
56
+ const ns = `__cjsImport${nsIndex++}`;
57
+ const pattern = code.slice(decl.id.start, decl.id.end);
58
+ imports.push(`import * as ${ns} from ${source};\n`);
59
+ imports.push(`const ${pattern} = ${ns};\n`);
60
+ } else {
61
+ needsCreateRequire = true;
62
+ }
63
+ }
64
+ transforms.push({
65
+ start: stmt.start,
66
+ end: stmt.end,
67
+ code: ';\n'
68
+ });
69
+ continue;
70
+ }
71
+ for (const decl of decls) {
72
+ const init = decl.init;
73
+ if (init && isRequireCall(init, shadowed)) {
74
+ needsCreateRequire = true;
75
+ }
76
+ }
77
+ }
78
+ if (stmt.type === 'ExpressionStatement') {
79
+ const expr = stmt.expression;
80
+ if (expr && isStaticRequire(expr, shadowed)) {
81
+ const source = code.slice(expr.arguments[0].start, expr.arguments[0].end);
82
+ imports.push(`import ${source};\n`);
83
+ transforms.push({
84
+ start: stmt.start,
85
+ end: stmt.end,
86
+ code: ';\n'
87
+ });
88
+ continue;
89
+ }
90
+ if (expr && isRequireCall(expr, shadowed)) {
91
+ needsCreateRequire = true;
92
+ }
93
+ }
94
+ }
95
+ return {
96
+ transforms,
97
+ imports,
98
+ needsCreateRequire
99
+ };
100
+ };
101
+ 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';
29
102
  const hasTopLevelAwait = program => {
30
103
  let found = false;
31
104
  const walkNode = (node, inFunction) => {
@@ -262,28 +335,67 @@ const format = async (src, ast, opts) => {
262
335
  hasDefaultExportBeenReassigned: false,
263
336
  hasDefaultExportBeenAssigned: false
264
337
  };
338
+ const moduleIdentifiers = await (0, _identifiers.collectModuleIdentifiers)(ast.program);
339
+ const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
340
+ if (opts.target === 'module' && opts.transformSyntax) {
341
+ if (shadowedBindings.has('module') || shadowedBindings.has('exports')) {
342
+ throw new Error('Cannot transform to ESM: module or exports is shadowed in module scope.');
343
+ }
344
+ }
265
345
  const exportTable = opts.target === 'module' ? await (0, _exports.collectCjsExports)(ast.program) : null;
266
- await (0, _identifiers.collectModuleIdentifiers)(ast.program);
267
346
  const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
268
347
  const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
269
348
  const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
349
+ const shouldRaiseEsm = opts.target === 'module' && opts.transformSyntax;
350
+ let hoistedImports = [];
351
+ let pendingRequireTransforms = [];
352
+ let needsCreateRequire = false;
270
353
  let pendingCjsTransforms = null;
271
354
  if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
272
355
  throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
273
356
  }
274
- if (opts.target === 'module' && opts.transformSyntax) {
275
- /**
276
- * Prepare ESM output by renaming `exports` to `__exports` and seeding an
277
- * `import.meta.filename` touch so import.meta is present even when the
278
- * original source never referenced it.
279
- */
280
- code.prepend(`let ${_exports.exportsRename} = {};
281
- void import.meta.filename;
282
- `);
357
+ if (shouldRaiseEsm) {
358
+ const {
359
+ transforms,
360
+ imports,
361
+ needsCreateRequire: reqCreate
362
+ } = lowerCjsRequireToImports(ast.program, code, shadowedBindings);
363
+ pendingRequireTransforms = transforms;
364
+ hoistedImports = imports;
365
+ needsCreateRequire = reqCreate;
283
366
  }
284
367
  await (0, _walk.ancestorWalk)(ast.program, {
285
368
  async enter(node, ancestors) {
286
369
  const parent = ancestors[ancestors.length - 2] ?? null;
370
+ if (shouldRaiseEsm && node.type === 'BinaryExpression') {
371
+ const op = node.operator;
372
+ const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
373
+ if (isEquality) {
374
+ const leftMain = isRequireMainMember(node.left, shadowedBindings);
375
+ const rightMain = isRequireMainMember(node.right, shadowedBindings);
376
+ const leftModule = node.left.type === 'Identifier' && node.left.name === 'module' && !shadowedBindings.has('module');
377
+ const rightModule = node.right.type === 'Identifier' && node.right.name === 'module' && !shadowedBindings.has('module');
378
+ if (leftMain && rightModule || rightMain && leftModule) {
379
+ const negate = op === '!==' || op === '!=';
380
+ code.update(node.start, node.end, negate ? '!import.meta.main' : 'import.meta.main');
381
+ return;
382
+ }
383
+ }
384
+ }
385
+ if (shouldRaiseEsm && node.type === 'CallExpression' && isRequireCall(node, shadowedBindings)) {
386
+ const isStatic = isStaticRequire(node, shadowedBindings);
387
+ const parent = ancestors[ancestors.length - 2] ?? null;
388
+ const grandparent = ancestors[ancestors.length - 3] ?? null;
389
+ const greatGrandparent = ancestors[ancestors.length - 4] ?? null;
390
+
391
+ // Hoistable cases are handled separately and don't need createRequire.
392
+ const topLevelExprStmt = parent?.type === 'ExpressionStatement' && grandparent?.type === 'Program';
393
+ const topLevelVarDecl = parent?.type === 'VariableDeclarator' && grandparent?.type === 'VariableDeclaration' && greatGrandparent?.type === 'Program';
394
+ const hoistableTopLevel = isStatic && (topLevelExprStmt || topLevelVarDecl);
395
+ if (!isStatic || !hoistableTopLevel) {
396
+ needsCreateRequire = true;
397
+ }
398
+ }
287
399
  if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
288
400
  const skipped = ['__filename', '__dirname'];
289
401
  const skippedParams = node.params.filter(param => param.type === 'Identifier' && skipped.includes(param.name));
@@ -342,7 +454,7 @@ void import.meta.filename;
342
454
  (0, _metaProperty.metaProperty)(node, parent, code, opts);
343
455
  }
344
456
  if (node.type === 'MemberExpression') {
345
- (0, _memberExpression.memberExpression)(node, parent, code, opts);
457
+ (0, _memberExpression.memberExpression)(node, parent, code, opts, shadowedBindings);
346
458
  }
347
459
  if ((0, _identifier2.isIdentifierName)(node)) {
348
460
  (0, _identifier.identifier)({
@@ -350,11 +462,17 @@ void import.meta.filename;
350
462
  ancestors,
351
463
  code,
352
464
  opts,
353
- meta: exportsMeta
465
+ meta: exportsMeta,
466
+ shadowed: shadowedBindings
354
467
  });
355
468
  }
356
469
  }
357
470
  });
471
+ if (pendingRequireTransforms.length) {
472
+ for (const t of pendingRequireTransforms) {
473
+ code.overwrite(t.start, t.end, t.code);
474
+ }
475
+ }
358
476
  if (shouldLowerCjs) {
359
477
  const {
360
478
  importTransforms,
@@ -403,6 +521,20 @@ void import.meta.filename;
403
521
  code.append(`\n${lines.join('\n')}\n`);
404
522
  }
405
523
  }
524
+ if (shouldRaiseEsm && opts.transformSyntax) {
525
+ const importPrelude = [];
526
+ if (needsCreateRequire) {
527
+ importPrelude.push('import { createRequire } from "node:module";\n');
528
+ }
529
+ if (hoistedImports.length) {
530
+ importPrelude.push(...hoistedImports);
531
+ }
532
+ const requireInit = needsCreateRequire ? 'const require = createRequire(import.meta.url);\n' : '';
533
+ const prelude = `${importPrelude.join('')}${importPrelude.length ? '\n' : ''}${requireInit}let ${_exports.exportsRename} = {};
534
+ void import.meta.filename;
535
+ `;
536
+ code.prepend(prelude);
537
+ }
406
538
  if (opts.target === 'commonjs' && opts.transformSyntax && containsTopLevelAwait) {
407
539
  const body = code.toString();
408
540
  if (opts.topLevelAwait === 'wrap') {
@@ -11,7 +11,8 @@ const identifier = ({
11
11
  ancestors,
12
12
  code,
13
13
  opts,
14
- meta
14
+ meta,
15
+ shadowed
15
16
  }) => {
16
17
  if (opts.target === 'module') {
17
18
  const {
@@ -19,6 +20,9 @@ const identifier = ({
19
20
  end,
20
21
  name
21
22
  } = node;
23
+ if (shadowed?.has(name)) {
24
+ return;
25
+ }
22
26
  switch (name) {
23
27
  case '__filename':
24
28
  code.update(start, end, 'import.meta.url');
@@ -5,8 +5,11 @@ 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) => {
8
+ const memberExpression = (node, parent, src, options, shadowed) => {
9
9
  if (options.target === 'module') {
10
+ if (node.object.type === 'Identifier' && shadowed?.has(node.object.name) || node.property.type === 'Identifier' && shadowed?.has(node.property.name)) {
11
+ return;
12
+ }
10
13
  if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'exports') {
11
14
  src.update(node.start, node.end, _exports.exportsRename);
12
15
  return;
@@ -23,18 +26,10 @@ const memberExpression = (node, parent, src, options) => {
23
26
  // CommonJS properties of `require`
24
27
  switch (name) {
25
28
  case 'main':
26
- /**
27
- * Node.js team still quibbling over import.meta.main ¯\_(ツ)_/¯
28
- * @see https://github.com/nodejs/node/pull/32223
29
- */
30
- if (parent?.type === 'ExpressionStatement') {
31
- // This is a standalone expression so remove it to not cause run-time errors.
32
- src.remove(start, end);
29
+ if (parent?.type === 'BinaryExpression') {
30
+ return;
33
31
  }
34
- /**
35
- * Transform require.main === module.
36
- */
37
- if (parent?.type === 'BinaryExpression') {}
32
+ src.update(start, end, 'import.meta.main');
38
33
  break;
39
34
  case 'resolve':
40
35
  src.update(start, end, 'import.meta.resolve');
@@ -48,6 +43,11 @@ const memberExpression = (node, parent, src, options) => {
48
43
  break;
49
44
  }
50
45
  }
46
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
47
+ if (!shadowed?.has('module')) {
48
+ src.update(node.start, node.end, 'require');
49
+ }
50
+ }
51
51
  }
52
52
  };
53
53
  exports.memberExpression = memberExpression;
@@ -1,4 +1,4 @@
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) => void;
4
+ export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>) => void;
@@ -0,0 +1,2 @@
1
+ declare const scopeNodes: string[];
2
+ export { scopeNodes };
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.collectScopeIdentifiers = exports.collectModuleIdentifiers = void 0;
7
7
  var _walk = require("#walk");
8
- var _scope = require("#helpers/scope.js");
9
8
  var _identifier = require("#helpers/identifier.js");
9
+ var _scopeNodes = require("./scopeNodes.cjs");
10
10
  const collectScopeIdentifiers = (node, scopes) => {
11
11
  const {
12
12
  type
@@ -180,7 +180,7 @@ const collectModuleIdentifiers = async (ast, hoisting = true) => {
180
180
  const {
181
181
  type
182
182
  } = node;
183
- if (_scope.scopes.includes(type)) {
183
+ if (_scopeNodes.scopeNodes.includes(type)) {
184
184
  scopes.pop();
185
185
  }
186
186
  }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.scopeNodes = void 0;
7
+ const scopeNodes = exports.scopeNodes = ['BlockStatement', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression', 'ClassDeclaration', 'ClassExpression', 'ClassBody', 'StaticBlock'];
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.requireMainRgx = exports.isValidUrl = exports.getLangFromExt = exports.exportsRename = exports.collectScopeIdentifiers = exports.collectModuleIdentifiers = exports.collectCjsExports = void 0;
7
7
  var _nodePath = require("node:path");
8
8
  var _walk = require("./walk.cjs");
9
- var _scope = require("./helpers/scope.cjs");
10
9
  var _identifier = require("./helpers/identifier.cjs");
10
+ var _scopeNodes = require("./utils/scopeNodes.cjs");
11
11
  const getLangFromExt = filename => {
12
12
  const ext = (0, _nodePath.extname)(filename);
13
13
  if (ext.endsWith('.js')) {
@@ -278,7 +278,7 @@ const collectModuleIdentifiers = async (ast, hoisting = true) => {
278
278
  const {
279
279
  type
280
280
  } = node;
281
- if (_scope.scopes.includes(type)) {
281
+ if (_scopeNodes.scopeNodes.includes(type)) {
282
282
  scopes.pop();
283
283
  }
284
284
  }
package/dist/format.js CHANGED
@@ -19,6 +19,79 @@ 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 isRequireCallee = (callee, shadowed) => {
23
+ if (callee.type === 'Identifier' && callee.name === 'require' && !shadowed.has('require')) {
24
+ return true;
25
+ }
26
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'module' && !shadowed.has('module') && callee.property.type === 'Identifier' && callee.property.name === 'require') {
27
+ return true;
28
+ }
29
+ return false;
30
+ };
31
+ const isStaticRequire = (node, shadowed) => node.type === 'CallExpression' && isRequireCallee(node.callee, shadowed) && node.arguments.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string';
32
+ const isRequireCall = (node, shadowed) => node.type === 'CallExpression' && isRequireCallee(node.callee, shadowed);
33
+ const lowerCjsRequireToImports = (program, code, shadowed) => {
34
+ const transforms = [];
35
+ const imports = [];
36
+ let nsIndex = 0;
37
+ let needsCreateRequire = false;
38
+ for (const stmt of program.body) {
39
+ if (stmt.type === 'VariableDeclaration') {
40
+ const decls = stmt.declarations;
41
+ const allStatic = decls.length > 0 && decls.every(decl => decl.init && isStaticRequire(decl.init, shadowed));
42
+ if (allStatic) {
43
+ for (const decl of decls) {
44
+ const init = decl.init;
45
+ const source = code.slice(init.arguments[0].start, init.arguments[0].end);
46
+ 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++}`;
50
+ 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`);
53
+ } else {
54
+ needsCreateRequire = true;
55
+ }
56
+ }
57
+ transforms.push({
58
+ start: stmt.start,
59
+ end: stmt.end,
60
+ code: ';\n'
61
+ });
62
+ continue;
63
+ }
64
+ for (const decl of decls) {
65
+ const init = decl.init;
66
+ if (init && isRequireCall(init, shadowed)) {
67
+ needsCreateRequire = true;
68
+ }
69
+ }
70
+ }
71
+ if (stmt.type === 'ExpressionStatement') {
72
+ const expr = stmt.expression;
73
+ if (expr && isStaticRequire(expr, shadowed)) {
74
+ const source = code.slice(expr.arguments[0].start, expr.arguments[0].end);
75
+ imports.push(`import ${source};\n`);
76
+ transforms.push({
77
+ start: stmt.start,
78
+ end: stmt.end,
79
+ code: ';\n'
80
+ });
81
+ continue;
82
+ }
83
+ if (expr && isRequireCall(expr, shadowed)) {
84
+ needsCreateRequire = true;
85
+ }
86
+ }
87
+ }
88
+ return {
89
+ transforms,
90
+ imports,
91
+ needsCreateRequire
92
+ };
93
+ };
94
+ 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';
22
95
  const hasTopLevelAwait = program => {
23
96
  let found = false;
24
97
  const walkNode = (node, inFunction) => {
@@ -255,28 +328,67 @@ const format = async (src, ast, opts) => {
255
328
  hasDefaultExportBeenReassigned: false,
256
329
  hasDefaultExportBeenAssigned: false
257
330
  };
331
+ const moduleIdentifiers = await collectModuleIdentifiers(ast.program);
332
+ const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
333
+ if (opts.target === 'module' && opts.transformSyntax) {
334
+ if (shadowedBindings.has('module') || shadowedBindings.has('exports')) {
335
+ throw new Error('Cannot transform to ESM: module or exports is shadowed in module scope.');
336
+ }
337
+ }
258
338
  const exportTable = opts.target === 'module' ? await collectCjsExports(ast.program) : null;
259
- await collectModuleIdentifiers(ast.program);
260
339
  const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
261
340
  const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
262
341
  const shouldLowerCjs = opts.target === 'commonjs' && opts.transformSyntax;
342
+ const shouldRaiseEsm = opts.target === 'module' && opts.transformSyntax;
343
+ let hoistedImports = [];
344
+ let pendingRequireTransforms = [];
345
+ let needsCreateRequire = false;
263
346
  let pendingCjsTransforms = null;
264
347
  if (shouldLowerCjs && opts.topLevelAwait === 'error' && containsTopLevelAwait) {
265
348
  throw new Error('Top-level await is not supported when targeting CommonJS (set topLevelAwait to "wrap" or "preserve" to override).');
266
349
  }
267
- if (opts.target === 'module' && opts.transformSyntax) {
268
- /**
269
- * Prepare ESM output by renaming `exports` to `__exports` and seeding an
270
- * `import.meta.filename` touch so import.meta is present even when the
271
- * original source never referenced it.
272
- */
273
- code.prepend(`let ${exportsRename} = {};
274
- void import.meta.filename;
275
- `);
350
+ if (shouldRaiseEsm) {
351
+ const {
352
+ transforms,
353
+ imports,
354
+ needsCreateRequire: reqCreate
355
+ } = lowerCjsRequireToImports(ast.program, code, shadowedBindings);
356
+ pendingRequireTransforms = transforms;
357
+ hoistedImports = imports;
358
+ needsCreateRequire = reqCreate;
276
359
  }
277
360
  await ancestorWalk(ast.program, {
278
361
  async enter(node, ancestors) {
279
362
  const parent = ancestors[ancestors.length - 2] ?? null;
363
+ if (shouldRaiseEsm && node.type === 'BinaryExpression') {
364
+ const op = node.operator;
365
+ const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
366
+ if (isEquality) {
367
+ const leftMain = isRequireMainMember(node.left, shadowedBindings);
368
+ const rightMain = isRequireMainMember(node.right, shadowedBindings);
369
+ const leftModule = node.left.type === 'Identifier' && node.left.name === 'module' && !shadowedBindings.has('module');
370
+ const rightModule = node.right.type === 'Identifier' && node.right.name === 'module' && !shadowedBindings.has('module');
371
+ if (leftMain && rightModule || rightMain && leftModule) {
372
+ const negate = op === '!==' || op === '!=';
373
+ code.update(node.start, node.end, negate ? '!import.meta.main' : 'import.meta.main');
374
+ return;
375
+ }
376
+ }
377
+ }
378
+ if (shouldRaiseEsm && node.type === 'CallExpression' && isRequireCall(node, shadowedBindings)) {
379
+ const isStatic = isStaticRequire(node, shadowedBindings);
380
+ const parent = ancestors[ancestors.length - 2] ?? null;
381
+ const grandparent = ancestors[ancestors.length - 3] ?? null;
382
+ const greatGrandparent = ancestors[ancestors.length - 4] ?? null;
383
+
384
+ // Hoistable cases are handled separately and don't need createRequire.
385
+ const topLevelExprStmt = parent?.type === 'ExpressionStatement' && grandparent?.type === 'Program';
386
+ const topLevelVarDecl = parent?.type === 'VariableDeclarator' && grandparent?.type === 'VariableDeclaration' && greatGrandparent?.type === 'Program';
387
+ const hoistableTopLevel = isStatic && (topLevelExprStmt || topLevelVarDecl);
388
+ if (!isStatic || !hoistableTopLevel) {
389
+ needsCreateRequire = true;
390
+ }
391
+ }
280
392
  if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
281
393
  const skipped = ['__filename', '__dirname'];
282
394
  const skippedParams = node.params.filter(param => param.type === 'Identifier' && skipped.includes(param.name));
@@ -335,7 +447,7 @@ void import.meta.filename;
335
447
  metaProperty(node, parent, code, opts);
336
448
  }
337
449
  if (node.type === 'MemberExpression') {
338
- memberExpression(node, parent, code, opts);
450
+ memberExpression(node, parent, code, opts, shadowedBindings);
339
451
  }
340
452
  if (isIdentifierName(node)) {
341
453
  identifier({
@@ -343,11 +455,17 @@ void import.meta.filename;
343
455
  ancestors,
344
456
  code,
345
457
  opts,
346
- meta: exportsMeta
458
+ meta: exportsMeta,
459
+ shadowed: shadowedBindings
347
460
  });
348
461
  }
349
462
  }
350
463
  });
464
+ if (pendingRequireTransforms.length) {
465
+ for (const t of pendingRequireTransforms) {
466
+ code.overwrite(t.start, t.end, t.code);
467
+ }
468
+ }
351
469
  if (shouldLowerCjs) {
352
470
  const {
353
471
  importTransforms,
@@ -396,6 +514,20 @@ void import.meta.filename;
396
514
  code.append(`\n${lines.join('\n')}\n`);
397
515
  }
398
516
  }
517
+ if (shouldRaiseEsm && opts.transformSyntax) {
518
+ const importPrelude = [];
519
+ if (needsCreateRequire) {
520
+ importPrelude.push('import { createRequire } from "node:module";\n');
521
+ }
522
+ if (hoistedImports.length) {
523
+ importPrelude.push(...hoistedImports);
524
+ }
525
+ const requireInit = needsCreateRequire ? 'const require = createRequire(import.meta.url);\n' : '';
526
+ const prelude = `${importPrelude.join('')}${importPrelude.length ? '\n' : ''}${requireInit}let ${exportsRename} = {};
527
+ void import.meta.filename;
528
+ `;
529
+ code.prepend(prelude);
530
+ }
399
531
  if (opts.target === 'commonjs' && opts.transformSyntax && containsTopLevelAwait) {
400
532
  const body = code.toString();
401
533
  if (opts.topLevelAwait === 'wrap') {
@@ -5,7 +5,8 @@ export const identifier = ({
5
5
  ancestors,
6
6
  code,
7
7
  opts,
8
- meta
8
+ meta,
9
+ shadowed
9
10
  }) => {
10
11
  if (opts.target === 'module') {
11
12
  const {
@@ -13,6 +14,9 @@ export const identifier = ({
13
14
  end,
14
15
  name
15
16
  } = node;
17
+ if (shadowed?.has(name)) {
18
+ return;
19
+ }
16
20
  switch (name) {
17
21
  case '__filename':
18
22
  code.update(start, end, 'import.meta.url');
@@ -1,6 +1,9 @@
1
1
  import { exportsRename } from '#utils/exports.js';
2
- export const memberExpression = (node, parent, src, options) => {
2
+ export const memberExpression = (node, parent, src, options, shadowed) => {
3
3
  if (options.target === 'module') {
4
+ if (node.object.type === 'Identifier' && shadowed?.has(node.object.name) || node.property.type === 'Identifier' && shadowed?.has(node.property.name)) {
5
+ return;
6
+ }
4
7
  if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'exports') {
5
8
  src.update(node.start, node.end, exportsRename);
6
9
  return;
@@ -17,18 +20,10 @@ export const memberExpression = (node, parent, src, options) => {
17
20
  // CommonJS properties of `require`
18
21
  switch (name) {
19
22
  case 'main':
20
- /**
21
- * Node.js team still quibbling over import.meta.main ¯\_(ツ)_/¯
22
- * @see https://github.com/nodejs/node/pull/32223
23
- */
24
- if (parent?.type === 'ExpressionStatement') {
25
- // This is a standalone expression so remove it to not cause run-time errors.
26
- src.remove(start, end);
23
+ if (parent?.type === 'BinaryExpression') {
24
+ return;
27
25
  }
28
- /**
29
- * Transform require.main === module.
30
- */
31
- if (parent?.type === 'BinaryExpression') {}
26
+ src.update(start, end, 'import.meta.main');
32
27
  break;
33
28
  case 'resolve':
34
29
  src.update(start, end, 'import.meta.resolve');
@@ -42,5 +37,10 @@ export const memberExpression = (node, parent, src, options) => {
42
37
  break;
43
38
  }
44
39
  }
40
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
41
+ if (!shadowed?.has('module')) {
42
+ src.update(node.start, node.end, 'require');
43
+ }
44
+ }
45
45
  }
46
46
  };
@@ -1,4 +1,4 @@
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) => void;
4
+ export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>) => void;
@@ -0,0 +1,2 @@
1
+ declare const scopeNodes: string[];
2
+ export { scopeNodes };
@@ -7,6 +7,7 @@ type IdentifierArg = {
7
7
  code: MagicString;
8
8
  opts: FormatterOptions;
9
9
  meta: ExportsMeta;
10
+ shadowed?: Set<string>;
10
11
  };
11
- export declare const identifier: ({ node, ancestors, code, opts, meta }: IdentifierArg) => void;
12
+ export declare const identifier: ({ node, ancestors, code, opts, meta, shadowed, }: IdentifierArg) => void;
12
13
  export {};
@@ -1,4 +1,4 @@
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) => void;
4
+ export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>) => void;
@@ -0,0 +1,2 @@
1
+ declare const scopeNodes: string[];
2
+ export { scopeNodes };
@@ -1,6 +1,6 @@
1
1
  import { ancestorWalk } from '#walk';
2
- import { scopes as scopeNodes } from '#helpers/scope.js';
3
2
  import { identifier } from '#helpers/identifier.js';
3
+ import { scopeNodes } from './scopeNodes.js';
4
4
  const collectScopeIdentifiers = (node, scopes) => {
5
5
  const {
6
6
  type
@@ -0,0 +1,2 @@
1
+ const scopeNodes = ['BlockStatement', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression', 'ClassDeclaration', 'ClassExpression', 'ClassBody', 'StaticBlock'];
2
+ export { scopeNodes };
package/dist/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { extname } from 'node:path';
2
2
  import { ancestorWalk } from './walk.js';
3
- import { scopes as scopeNodes } from './helpers/scope.js';
4
3
  import { identifier } from './helpers/identifier.js';
4
+ import { scopeNodes } from './utils/scopeNodes.js';
5
5
  const getLangFromExt = filename => {
6
6
  const ext = extname(filename);
7
7
  if (ext.endsWith('.js')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/module",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Transforms differences between ES modules and CommonJS.",
5
5
  "type": "module",
6
6
  "main": "dist/module.js",
@@ -27,7 +27,7 @@
27
27
  "#formatters/*.js": "./src/formatters/*.js"
28
28
  },
29
29
  "engines": {
30
- "node": ">=20.11.0"
30
+ "node": ">=22.21.1"
31
31
  },
32
32
  "engineStrict": true,
33
33
  "scripts": {
@@ -1,12 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.scopes = exports.scope = void 0;
7
- const scopes = exports.scopes = ['BlockStatement', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression', 'ClassDeclaration', 'ClassExpression', 'ClassBody', 'StaticBlock'];
8
- const scope = exports.scope = {
9
- isScope(node) {
10
- return scopes.includes(node.type);
11
- }
12
- };
@@ -1,6 +0,0 @@
1
- import type { Node } from 'oxc-parser';
2
- declare const scopes: string[];
3
- declare const scope: {
4
- isScope(node: Node): boolean;
5
- };
6
- export { scopes, scope };
@@ -1,7 +0,0 @@
1
- const scopes = ['BlockStatement', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression', 'ClassDeclaration', 'ClassExpression', 'ClassBody', 'StaticBlock'];
2
- const scope = {
3
- isScope(node) {
4
- return scopes.includes(node.type);
5
- }
6
- };
7
- export { scopes, scope };
package/dist/scope.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import type { Node } from 'oxc-parser';
2
- declare const scopes: string[];
3
- declare const scope: {
4
- isScope(node: Node): boolean;
5
- };
6
- export { scopes, scope };
@@ -1,6 +0,0 @@
1
- import type { Node } from 'oxc-parser';
2
- declare const scopes: string[];
3
- declare const scope: {
4
- isScope(node: Node): boolean;
5
- };
6
- export { scopes, scope };