Commit 45df6b73d59 for nodejs
commit 45df6b73d59e5133118df73e8d5537e30fa33440
Author: Gürgün Dayıoğlu <hey@gurgun.day>
Date: Mon Nov 2 17:51:36 2026 +0300
module: avoid reparsing package.json in esm format detection
Determining the format of a `.js` or `.ts` module called
`getPackageScopeConfig()`, which spread the deserialized package
configuration and thereby invoked its lazy `exports` and `imports`
getters. Every resolved module parsed those maps again, so load time
and memory grew with the number of modules in a package times the
size of its `exports` map.
Use `getPackageType()` for format detection and only look up the
package.json path when warning about a typeless package. Stop
spreading the configuration in `getPackageScopeConfig()` so that the
maps are parsed only when accessed.
Fixes: https://github.com/nodejs/node/issues/66485
Signed-off-by: Gürgün Dayıoğlu <hey@gurgun.day>
PR-URL: https://github.com/nodejs/node/pull/66982
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js
index e69a0a01d99..0b7e1ed438e 100644
--- a/lib/internal/modules/esm/get_format.js
+++ b/lib/internal/modules/esm/get_format.js
@@ -149,9 +149,17 @@ function underNodeModules(url) {
}
let typelessPackageJsonFilesWarnedAbout;
-function warnTypelessPackageJsonFile(pjsonPath, url) {
+/**
+ * Called for a module with a package.json without a `type` field (or none at all) and ESM syntax.
+ * Warns about the missing `type` field so that the user can avoid the performance penalty of detection.
+ * @param {URL} url
+ */
+function warnIfTypelessPackageJsonFile(url) {
+ if (underNodeModules(url)) { return; }
+ const { pjsonPath, exists } = getPackageScopeConfig(url);
+ if (!exists) { return; }
typelessPackageJsonFilesWarnedAbout ??= new SafeSet();
- if (!underNodeModules(url) && !typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
+ if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
const warning = `Module type of ${url} is not specified and it doesn't parse as CommonJS.\n` +
'Reparsing as ES module because module syntax was detected. This incurs a performance overhead.\n' +
`To eliminate this warning, add "type": "module" to ${pjsonPath}.`;
@@ -173,7 +181,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
const ext = extname(url);
if (ext === '.js') {
- const { type: packageType, pjsonPath, exists: foundPackageJson } = getPackageScopeConfig(url);
+ const packageType = getPackageType(url);
if (packageType !== 'none') {
return packageType;
}
@@ -183,15 +191,13 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
// For ambiguous files (.js, no type field) we return undefined from `resolve` and re-run the check in `load`.
const format = detectModuleFormat(source, url);
- if (format === 'module' && foundPackageJson) {
- // This module has a .js extension, a package.json with no `type` field, and ESM syntax.
- // Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
- warnTypelessPackageJsonFile(pjsonPath, url);
+ if (format === 'module') {
+ warnIfTypelessPackageJsonFile(url);
}
return format;
}
if (ext === '.ts' && getOptionValue('--strip-types')) {
- const { type: packageType, pjsonPath, exists: foundPackageJson } = getPackageScopeConfig(url);
+ const packageType = getPackageType(url);
if (packageType !== 'none') {
return `${packageType}-typescript`;
}
@@ -206,10 +212,8 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
const parsedSource = stripTypeScriptModuleTypes(stringifiedSource, fileURLToPath(url));
const detectedFormat = detectModuleFormat(parsedSource, url);
const format = `${detectedFormat}-typescript`;
- if (format === 'module-typescript' && foundPackageJson) {
- // This module has a .js extension, a package.json with no `type` field, and ESM syntax.
- // Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
- warnTypelessPackageJsonFile(pjsonPath, url);
+ if (format === 'module-typescript') {
+ warnIfTypelessPackageJsonFile(url);
}
return format;
}
diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js
index 2ef5d1ebed3..e4455da046a 100644
--- a/lib/internal/modules/package_json_reader.js
+++ b/lib/internal/modules/package_json_reader.js
@@ -264,12 +264,11 @@ function getPackageScopeConfig(resolved) {
if (ArrayIsArray(result)) {
const { data, exists, path } = deserializePackageJSON(`${resolved}`, result);
- return {
- __proto__: null,
- ...data,
- exists,
- pjsonPath: path,
- };
+ // `data` is not shared, so extend it in place instead of spreading it:
+ // spreading would invoke the lazy `imports` and `exports` getters.
+ data.exists = exists;
+ data.pjsonPath = path;
+ return data;
}
// This means that the response is a string