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" +}