diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 9712890139596d..4665bff9707da2 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -74,7 +74,7 @@ function getLegacyExtensionFormat(ext) { function getFileProtocolModuleFormat(url, ignoreErrors) { const ext = extname(url.pathname); - if (ext === '.js') { + if (ext === '.js' || !ext) { return getPackageType(url) === 'module' ? 'module' : 'commonjs'; } diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 924c4836bb2aa2..fe2c35790cc460 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -28,20 +28,26 @@ function resolveMainPath(main) { } function shouldUseESMLoader(mainPath) { + // General indicators of user intention + const userLoader = getOptionValue('--experimental-loader'); - if (userLoader) - return true; + // This flag is a user's only way to opt into ESMLoader (which can handle both + // CJS & ESM), so assume that's what is happening here. + if (userLoader) return true; + const esModuleSpecifierResolution = getOptionValue('--experimental-specifier-resolution'); - if (esModuleSpecifierResolution === 'node') - return true; - // Determine the module format of the main - if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) - return true; - if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) - return false; + // This flag is only applicable to ESM, so assume it signals opting in. + if (esModuleSpecifierResolution === 'node') return true; + + // Specific indicators of user intention + + // Check trump-cards + if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) return true; + if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) return false; + const pkg = readPackageScope(mainPath); - return pkg && pkg.data.type === 'module'; + return pkg?.data?.type === 'module'; } function runMainESM(mainPath) { diff --git a/test/es-module/test-esm-resolve-type.js b/test/es-module/test-esm-resolve-type.js index ba4dea03c8ac48..b71e2cb3a9b1b1 100644 --- a/test/es-module/test-esm-resolve-type.js +++ b/test/es-module/test-esm-resolve-type.js @@ -38,13 +38,28 @@ try { [ [ '/es-modules/package-type-module/index.js', 'module' ], [ '/es-modules/package-type-commonjs/index.js', 'commonjs' ], + // "commonjs" is the default type, so it is assumed when there is no "type" + // or specific file extension (`.cjs` or `.mjs`) + // It happens to be "correct" here: + [ '/es-modules/packages-with-bin/no-type-but-commonjs/bin/foo', 'commonjs'], + // It happens to be "wrong" here: + [ '/es-modules/packages-with-bin/no-type-but-module/bin/foo', 'commonjs' ], + [ '/es-modules/packages-with-bin/type-commonjs/bin/foo', 'commonjs' ], + [ '/es-modules/packages-with-bin/type-module/bin/foo', 'module' ], [ '/es-modules/package-without-type/index.js', 'commonjs' ], [ '/es-modules/package-without-pjson/index.js', 'commonjs' ], - ].forEach((testVariant) => { - const [ testScript, expectedType ] = testVariant; + ].forEach(([ testScript, expectedType ]) => { const resolvedPath = path.resolve(fixtures.path(testScript)); const resolveResult = resolve(url.pathToFileURL(resolvedPath)); - assert.strictEqual(resolveResult.format, expectedType); + assert.strictEqual( + resolveResult.format, + expectedType, + new assert.AssertionError({ + actual: resolveResult?.format, + expected: expectedType, + message: `Expectation failed for "${testScript}"` + }) + ); }); /** diff --git a/test/es-module/test-esm-unknown-or-no-extension.js b/test/es-module/test-esm-unknown-or-no-extension.js index 3b1802a4dcedbd..64676b458d2cf1 100644 --- a/test/es-module/test-esm-unknown-or-no-extension.js +++ b/test/es-module/test-esm-unknown-or-no-extension.js @@ -5,13 +5,11 @@ const fixtures = require('../common/fixtures'); const { spawn } = require('child_process'); const assert = require('assert'); -// In a "type": "module" package scope, files with unknown extensions or no -// extensions should throw; both when used as a main entry point and also when -// referenced via `import`. +// In a "type": "module" package scope, files with unknown extensions should +// throw; both when used as a main entry point and also when referenced via +// `import`. [ - '/es-modules/package-type-module/noext-esm', - '/es-modules/package-type-module/imports-noext.mjs', '/es-modules/package-type-module/extension.unknown', '/es-modules/package-type-module/imports-unknownext.mjs', ].forEach((fixturePath) => { diff --git a/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/bin/foo b/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/bin/foo new file mode 100644 index 00000000000000..5538f0eca09dad --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/bin/foo @@ -0,0 +1 @@ +require('assert'); diff --git a/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/package.json b/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/package.json new file mode 100644 index 00000000000000..3b9f4b34d63661 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/no-type-but-commonjs/package.json @@ -0,0 +1,4 @@ +{ + "bin": "./bin/foo", + "name": "bin-without-type" +} diff --git a/test/fixtures/es-modules/packages-with-bin/no-type-but-module/bin/foo b/test/fixtures/es-modules/packages-with-bin/no-type-but-module/bin/foo new file mode 100644 index 00000000000000..f7a25c051fbcb2 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/no-type-but-module/bin/foo @@ -0,0 +1 @@ +import 'assert'; diff --git a/test/fixtures/es-modules/packages-with-bin/no-type-but-module/package.json b/test/fixtures/es-modules/packages-with-bin/no-type-but-module/package.json new file mode 100644 index 00000000000000..3b9f4b34d63661 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/no-type-but-module/package.json @@ -0,0 +1,4 @@ +{ + "bin": "./bin/foo", + "name": "bin-without-type" +} diff --git a/test/fixtures/es-modules/packages-with-bin/type-commonjs/bin/foo b/test/fixtures/es-modules/packages-with-bin/type-commonjs/bin/foo new file mode 100644 index 00000000000000..5538f0eca09dad --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/type-commonjs/bin/foo @@ -0,0 +1 @@ +require('assert'); diff --git a/test/fixtures/es-modules/packages-with-bin/type-commonjs/package.json b/test/fixtures/es-modules/packages-with-bin/type-commonjs/package.json new file mode 100644 index 00000000000000..c10e9bc96a64c2 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/type-commonjs/package.json @@ -0,0 +1,5 @@ +{ + "bin": "./bin/foo", + "name": "bin-without-type", + "type": "commonjs" +} diff --git a/test/fixtures/es-modules/packages-with-bin/type-module/bin/foo b/test/fixtures/es-modules/packages-with-bin/type-module/bin/foo new file mode 100644 index 00000000000000..f7a25c051fbcb2 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/type-module/bin/foo @@ -0,0 +1 @@ +import 'assert'; diff --git a/test/fixtures/es-modules/packages-with-bin/type-module/package.json b/test/fixtures/es-modules/packages-with-bin/type-module/package.json new file mode 100644 index 00000000000000..653aca59ddf879 --- /dev/null +++ b/test/fixtures/es-modules/packages-with-bin/type-module/package.json @@ -0,0 +1,5 @@ +{ + "bin": "./bin/foo", + "name": "bin-without-type", + "type": "module" +}