Skip to content

Commit f4a0a3b

Browse files
module: warn on detection in typeless package
PR-URL: #52168 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 3fcaf7b commit f4a0a3b

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

lib/internal/modules/esm/get_format.js

+19-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
ObjectPrototypeHasOwnProperty,
66
PromisePrototypeThen,
77
PromiseResolve,
8+
SafeSet,
89
StringPrototypeIncludes,
910
StringPrototypeCharCodeAt,
1011
StringPrototypeSlice,
@@ -19,7 +20,7 @@ const {
1920
const experimentalNetworkImports =
2021
getOptionValue('--experimental-network-imports');
2122
const { containsModuleSyntax } = internalBinding('contextify');
22-
const { getPackageType } = require('internal/modules/package_json_reader');
23+
const { getPackageScopeConfig, getPackageType } = require('internal/modules/package_json_reader');
2324
const { fileURLToPath } = require('internal/url');
2425
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
2526

@@ -81,6 +82,7 @@ function underNodeModules(url) {
8182
return StringPrototypeIncludes(url.pathname, '/node_modules/');
8283
}
8384

85+
let typelessPackageJsonFilesWarnedAbout;
8486
/**
8587
* @param {URL} url
8688
* @param {{parentURL: string; source?: Buffer}} context
@@ -92,7 +94,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
9294
const ext = extname(url);
9395

9496
if (ext === '.js') {
95-
const packageType = getPackageType(url);
97+
const { type: packageType, pjsonPath } = getPackageScopeConfig(url);
9698
if (packageType !== 'none') {
9799
return packageType;
98100
}
@@ -111,9 +113,23 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
111113
// `source` is undefined when this is called from `defaultResolve`;
112114
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
113115
if (getOptionValue('--experimental-detect-module')) {
114-
return source ?
116+
const format = source ?
115117
(containsModuleSyntax(`${source}`, fileURLToPath(url)) ? 'module' : 'commonjs') :
116118
null;
119+
if (format === 'module') {
120+
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
121+
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
122+
typelessPackageJsonFilesWarnedAbout ??= new SafeSet();
123+
if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
124+
const warning = `${url} parsed as an ES module because module syntax was detected;` +
125+
` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`;
126+
process.emitWarning(warning, {
127+
code: 'MODULE_TYPELESS_PACKAGE_JSON',
128+
});
129+
typelessPackageJsonFilesWarnedAbout.add(pjsonPath);
130+
}
131+
}
132+
return format;
117133
}
118134
return 'commonjs';
119135
}

test/es-module/test-esm-detect-ambiguous.mjs

+45
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ describe('--experimental-detect-module', { concurrency: true }, () => {
101101
]) {
102102
it(testName, async () => {
103103
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
104+
'--no-warnings',
104105
'--experimental-detect-module',
105106
entryPath,
106107
]);
@@ -142,6 +143,7 @@ describe('--experimental-detect-module', { concurrency: true }, () => {
142143
]) {
143144
it(testName, async () => {
144145
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
146+
'--no-warnings',
145147
'--experimental-detect-module',
146148
entryPath,
147149
]);
@@ -291,6 +293,7 @@ describe('--experimental-detect-module', { concurrency: true }, () => {
291293

292294
it('permits declaration of CommonJS module variables', async () => {
293295
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
296+
'--no-warnings',
294297
'--experimental-detect-module',
295298
fixtures.path('es-modules/package-without-type/commonjs-wrapper-variables.js'),
296299
]);
@@ -327,6 +330,48 @@ describe('--experimental-detect-module', { concurrency: true }, () => {
327330
strictEqual(signal, null);
328331
});
329332
});
333+
334+
describe('warn about typeless packages for .js files with ESM syntax', { concurrency: true }, () => {
335+
for (const { testName, entryPath } of [
336+
{
337+
testName: 'warns for ESM syntax in a .js entry point in a typeless package',
338+
entryPath: fixtures.path('es-modules/package-without-type/module.js'),
339+
},
340+
{
341+
testName: 'warns for ESM syntax in a .js file imported by a CommonJS entry point in a typeless package',
342+
entryPath: fixtures.path('es-modules/package-without-type/imports-esm.js'),
343+
},
344+
{
345+
testName: 'warns for ESM syntax in a .js file imported by an ESM entry point in a typeless package',
346+
entryPath: fixtures.path('es-modules/package-without-type/imports-esm.mjs'),
347+
},
348+
]) {
349+
it(testName, async () => {
350+
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
351+
'--experimental-detect-module',
352+
entryPath,
353+
]);
354+
355+
match(stderr, /MODULE_TYPELESS_PACKAGE_JSON/);
356+
strictEqual(stdout, 'executed\n');
357+
strictEqual(code, 0);
358+
strictEqual(signal, null);
359+
});
360+
}
361+
362+
it('warns only once for a package.json that affects multiple files', async () => {
363+
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
364+
'--experimental-detect-module',
365+
fixtures.path('es-modules/package-without-type/detected-as-esm.js'),
366+
]);
367+
368+
match(stderr, /MODULE_TYPELESS_PACKAGE_JSON/);
369+
strictEqual(stderr.match(/MODULE_TYPELESS_PACKAGE_JSON/g).length, 1);
370+
strictEqual(stdout, 'executed\nexecuted\n');
371+
strictEqual(code, 0);
372+
strictEqual(signal, null);
373+
});
374+
});
330375
});
331376

332377
// Validate temporarily disabling `--abort-on-uncaught-exception`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import './module.js';
2+
console.log('executed');

0 commit comments

Comments
 (0)