From 674c1509dda77fb38d08e1aa703a83a881c4254c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heath=20Dutton=F0=9F=95=B4=EF=B8=8F?= Date: Wed, 24 Dec 2025 23:09:20 -0500 Subject: [PATCH] module: throw error for ESM syntax in explicit commonjs entry When a main entry point contains ESM syntax but is in a package with "type": "commonjs" in package.json, the module would silently exit with code 0 without executing or showing any error. This happened because the CJS loader detected ESM syntax, attempted to defer to ESM loading, but the async execution never completed before the process exited. This change throws ERR_REQUIRE_ESM for main modules with ESM syntax in explicitly CommonJS-typed packages, providing a clear error message instead of silent failure. Fixes: https://github.com/nodejs/node/issues/61104 --- lib/internal/modules/cjs/loader.js | 6 +++ test/es-module/test-esm-syntax-in-cjs-main.js | 38 +++++++++++++++++++ .../esm-script.js | 4 ++ .../package.json | 3 ++ 4 files changed, 51 insertions(+) create mode 100644 test/es-module/test-esm-syntax-in-cjs-main.js create mode 100644 test/fixtures/es-modules/package-type-commonjs-esm-syntax/esm-script.js create mode 100644 test/fixtures/es-modules/package-type-commonjs-esm-syntax/package.json diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index d9fc92bbc813e8..b761a451b70aa5 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1751,6 +1751,7 @@ function wrapSafe(filename, content, cjsModuleInstance, format) { * @returns {any} */ Module.prototype._compile = function(content, filename, format) { + const explicitCommonJS = format === 'commonjs' || format === 'commonjs-typescript'; if (format === 'commonjs-typescript' || format === 'module-typescript' || format === 'typescript') { content = stripTypeScriptModuleTypes(content, filename); switch (format) { @@ -1777,6 +1778,11 @@ Module.prototype._compile = function(content, filename, format) { const result = wrapSafe(filename, content, this, format); compiledWrapper = result.function; if (result.canParseAsESM) { + // Throw for ESM syntax in explicitly CommonJS main entry to avoid silent failure. + if (explicitCommonJS && this[kIsMainSymbol]) { + const pkg = packageJsonReader.getPackageScopeConfig(filename); + throw new ERR_REQUIRE_ESM(filename, true, null, pkg?.pjsonPath); + } format = 'module'; } } diff --git a/test/es-module/test-esm-syntax-in-cjs-main.js b/test/es-module/test-esm-syntax-in-cjs-main.js new file mode 100644 index 00000000000000..5cc636e4567dc9 --- /dev/null +++ b/test/es-module/test-esm-syntax-in-cjs-main.js @@ -0,0 +1,38 @@ +// Flags: --no-warnings +'use strict'; + +// Test that running a main entry point with ESM syntax in a "type": "commonjs" +// package throws an error instead of silently failing with exit code 0. +// Regression test for https://github.com/nodejs/node/issues/61104 + +const { spawnPromisified } = require('../common'); +const fixtures = require('../common/fixtures.js'); +const assert = require('node:assert'); +const { execPath } = require('node:process'); +const { describe, it } = require('node:test'); + +describe('ESM syntax in explicit CommonJS main entry point', { concurrency: !process.env.TEST_PARALLEL }, () => { + it('should throw ERR_REQUIRE_ESM when main module has ESM syntax in type:commonjs package', async () => { + const mainScript = fixtures.path('es-modules/package-type-commonjs-esm-syntax/esm-script.js'); + const { code, signal, stderr } = await spawnPromisified(execPath, [mainScript]); + + // Should exit with non-zero exit code + assert.strictEqual(code, 1, `Expected exit code 1, got ${code}`); + assert.strictEqual(signal, null); + + // Should contain error about ESM in CommonJS context + assert.match(stderr, /ERR_REQUIRE_ESM/, + 'Expected error message to contain ERR_REQUIRE_ESM'); + assert.match(stderr, /esm-script\.js/, + 'Expected error message to mention the script filename'); + }); + + it('should include helpful message about package.json type field', async () => { + const mainScript = fixtures.path('es-modules/package-type-commonjs-esm-syntax/esm-script.js'); + const { stderr } = await spawnPromisified(execPath, [mainScript]); + + // Error message should mention the package.json path for helpful debugging + assert.match(stderr, /package\.json/, + 'Expected error message to mention package.json'); + }); +}); diff --git a/test/fixtures/es-modules/package-type-commonjs-esm-syntax/esm-script.js b/test/fixtures/es-modules/package-type-commonjs-esm-syntax/esm-script.js new file mode 100644 index 00000000000000..4cabb2a94dbeae --- /dev/null +++ b/test/fixtures/es-modules/package-type-commonjs-esm-syntax/esm-script.js @@ -0,0 +1,4 @@ +// This file has ESM syntax but is in a "type": "commonjs" package +console.log('script STARTED'); +import { version } from 'node:process'; +console.log(version); diff --git a/test/fixtures/es-modules/package-type-commonjs-esm-syntax/package.json b/test/fixtures/es-modules/package-type-commonjs-esm-syntax/package.json new file mode 100644 index 00000000000000..5bbefffbabee39 --- /dev/null +++ b/test/fixtures/es-modules/package-type-commonjs-esm-syntax/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +}