From 9842d3e1a74f8d8ef1a6fec1190fae27d076a2be Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 9 Mar 2026 13:23:37 +0100 Subject: [PATCH] module: fix extensionless CJS files in type:module packages PR #61600 made the CJS loader respect the package.json type field for extensionless files, which fixed #61104 but also enforced type: "module" for extensionless files. This broke CJS extensionless files inside ESM packages (e.g. yargs v17). Only enforce type: "commonjs" for extensionless files. For type: "module", leave format undefined so syntax detection handles it. This restores the documented exception in the CJS documentation that has existed since v16: extensionless files in type: "module" packages are recognized as CJS when included via require(). Fixes: https://github.com/nodejs/node/issues/61971 Refs: https://github.com/yargs/yargs/issues/2509 Refs: https://github.com/nodejs/node/pull/61600 PR-URL: https://github.com/nodejs/node/pull/62083 --- lib/internal/modules/cjs/loader.js | 8 ++++---- test/es-module/test-extensionless-esm-type-commonjs.js | 10 ++++++++++ .../fixtures/es-modules/extensionless-cjs-module/index | 1 + .../es-modules/extensionless-cjs-module/package.json | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/es-modules/extensionless-cjs-module/index create mode 100644 test/fixtures/es-modules/extensionless-cjs-module/package.json diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index f5a47448ba3eb9..6e82be7cf3e7ee 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1952,12 +1952,12 @@ Module._extensions['.js'] = function(module, filename) { format = 'typescript'; } } else if (path.extname(filename) === '') { - // Extensionless files skip the .js suffix check above. When type is explicit, - // follow it so ESM syntax surfaces as SyntaxError for commonjs instead of - // silently delegating to ESM. + // Extensionless files skip the .js suffix check above. When type is commonjs, follow it so ESM + // syntax surfaces as SyntaxError. For type: module, leave format undefined so our syntax + // detection handles it (allowing CJS extensionless files in ESM packages). pkg = packageJsonReader.getNearestParentPackageJSON(filename); const typeFromPjson = pkg?.data?.type; - if (typeFromPjson === 'commonjs' || typeFromPjson === 'module') { + if (typeFromPjson === 'commonjs') { format = typeFromPjson; } } diff --git a/test/es-module/test-extensionless-esm-type-commonjs.js b/test/es-module/test-extensionless-esm-type-commonjs.js index cdfdf9361393e9..13349ed1c156af 100644 --- a/test/es-module/test-extensionless-esm-type-commonjs.js +++ b/test/es-module/test-extensionless-esm-type-commonjs.js @@ -17,3 +17,13 @@ spawnSyncAndAssert(process.execPath, [ stdout: /script STARTED[\s\S]*v\d+\./, trim: true, }); + +// CJS extensionless file inside a type: "module" package should work +// when require()'d. Regression test for https://github.com/nodejs/node/issues/61971 +spawnSyncAndAssert(process.execPath, [ + '-e', `const m = require(${JSON.stringify( + fixtures.path('es-modules', 'extensionless-cjs-module', 'index') + )}); if (m.hello !== 'world') throw new Error('expected CJS exports, got: ' + JSON.stringify(m))`, +], { + status: 0, +}); diff --git a/test/fixtures/es-modules/extensionless-cjs-module/index b/test/fixtures/es-modules/extensionless-cjs-module/index new file mode 100644 index 00000000000000..6c45e12abc39f5 --- /dev/null +++ b/test/fixtures/es-modules/extensionless-cjs-module/index @@ -0,0 +1 @@ +module.exports = { hello: 'world' }; diff --git a/test/fixtures/es-modules/extensionless-cjs-module/package.json b/test/fixtures/es-modules/extensionless-cjs-module/package.json new file mode 100644 index 00000000000000..3dbc1ca591c055 --- /dev/null +++ b/test/fixtures/es-modules/extensionless-cjs-module/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}