From 6ed6c3fa0d85e8f34d9ee036c7611387881793c2 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 a SyntaxError for main modules with ESM syntax in explicitly CommonJS-typed packages, matching the error Node.js throws when import syntax appears in other CommonJS contexts. Fixes: https://github.com/nodejs/node/issues/61104 --- lib/internal/modules/cjs/loader.js | 5 +++ test/es-module/test-esm-syntax-in-cjs-main.js | 31 +++++++++++++++++++ .../esm-script.js | 4 +++ .../package.json | 3 ++ 4 files changed, 43 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..b5e8923bea7bc3 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,10 @@ 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]) { + throw new SyntaxError('Cannot use import statement outside a module'); + } 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..2fd6d9a65844ab --- /dev/null +++ b/test/es-module/test-esm-syntax-in-cjs-main.js @@ -0,0 +1,31 @@ +// 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 SyntaxError 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 SyntaxError about import statement + assert.match(stderr, /SyntaxError/, + 'Expected error to be a SyntaxError'); + assert.match(stderr, /Cannot use import statement outside a module/, + 'Expected error message about import statement'); + assert.match(stderr, /esm-script\.js/, + 'Expected error message to mention the script filename'); + }); +}); 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" +}