From 6e3ed676f2ad9d6435c5cc56da05f8371a7bbe85 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 3 Mar 2026 11:07:59 +0100 Subject: [PATCH 1/2] module: fix extensionless CJS in type:module packages PR #61600 made the CJS loader respect the package.json type field for extensionless files, but also enforced type: "module" which breaks CJS extensionless files inside ESM packages (e.g. yargs v17). Only enforce type: "commonjs" for extensionless files. For type: "module", leave format undefined so V8's syntax detection determines the correct format, allowing CJS extensionless files in ESM packages to continue working. Fixes: https://github.com/nodejs/node/issues/61971 Refs: https://github.com/yargs/yargs/issues/2509 --- lib/internal/modules/cjs/loader.js | 9 +++++---- 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, 19 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 a320736d1b6fd7..37fba407cb20cb 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1932,12 +1932,13 @@ 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 instead of silently + // delegating to ESM. For type: module, leave format undefined so V8's 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" +} From 519235b03487e0c4bb438d84d84909ef6effc6ee Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 3 Mar 2026 04:49:20 -0800 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Joyee Cheung --- lib/internal/modules/cjs/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 37fba407cb20cb..64597b907e2357 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1934,7 +1934,7 @@ Module._extensions['.js'] = function(module, filename) { } else if (path.extname(filename) === '') { // Extensionless files skip the .js suffix check above. When type is commonjs, // follow it so ESM syntax surfaces as SyntaxError instead of silently - // delegating to ESM. For type: module, leave format undefined so V8's syntax + // delegating to ESM. 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;