Skip to content

Commit 9b434b2

Browse files
gurgundayaduh95
authored andcommitted
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: #66485 Signed-off-by: Gürgün Dayıoğlu <hey@gurgun.day> PR-URL: #66982 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent ab9d401 commit 9b434b2

2 files changed

Lines changed: 21 additions & 18 deletions

File tree

‎lib/internal/modules/esm/get_format.js‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,17 @@ function underNodeModules(url) {
149149
}
150150

151151
let typelessPackageJsonFilesWarnedAbout;
152-
function warnTypelessPackageJsonFile(pjsonPath, url) {
152+
/**
153+
* Called for a module with a package.json without a `type` field (or none at all) and ESM syntax.
154+
* Warns about the missing `type` field so that the user can avoid the performance penalty of detection.
155+
* @param {URL} url
156+
*/
157+
function warnIfTypelessPackageJsonFile(url) {
158+
if (underNodeModules(url)) { return; }
159+
const { pjsonPath, exists } = getPackageScopeConfig(url);
160+
if (!exists) { return; }
153161
typelessPackageJsonFilesWarnedAbout ??= new SafeSet();
154-
if (!underNodeModules(url) && !typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
162+
if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
155163
const warning = `Module type of ${url} is not specified and it doesn't parse as CommonJS.\n` +
156164
'Reparsing as ES module because module syntax was detected. This incurs a performance overhead.\n' +
157165
`To eliminate this warning, add "type": "module" to ${pjsonPath}.`;
@@ -173,7 +181,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
173181
const ext = extname(url);
174182

175183
if (ext === '.js') {
176-
const { type: packageType, pjsonPath, exists: foundPackageJson } = getPackageScopeConfig(url);
184+
const packageType = getPackageType(url);
177185
if (packageType !== 'none') {
178186
return packageType;
179187
}
@@ -183,15 +191,13 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
183191
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
184192
// For ambiguous files (.js, no type field) we return undefined from `resolve` and re-run the check in `load`.
185193
const format = detectModuleFormat(source, url);
186-
if (format === 'module' && foundPackageJson) {
187-
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
188-
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
189-
warnTypelessPackageJsonFile(pjsonPath, url);
194+
if (format === 'module') {
195+
warnIfTypelessPackageJsonFile(url);
190196
}
191197
return format;
192198
}
193199
if (ext === '.ts' && getOptionValue('--strip-types')) {
194-
const { type: packageType, pjsonPath, exists: foundPackageJson } = getPackageScopeConfig(url);
200+
const packageType = getPackageType(url);
195201
if (packageType !== 'none') {
196202
return `${packageType}-typescript`;
197203
}
@@ -206,10 +212,8 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
206212
const parsedSource = stripTypeScriptModuleTypes(stringifiedSource, fileURLToPath(url));
207213
const detectedFormat = detectModuleFormat(parsedSource, url);
208214
const format = `${detectedFormat}-typescript`;
209-
if (format === 'module-typescript' && foundPackageJson) {
210-
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
211-
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
212-
warnTypelessPackageJsonFile(pjsonPath, url);
215+
if (format === 'module-typescript') {
216+
warnIfTypelessPackageJsonFile(url);
213217
}
214218
return format;
215219
}

‎lib/internal/modules/package_json_reader.js‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,11 @@ function getPackageScopeConfig(resolved) {
264264
if (ArrayIsArray(result)) {
265265
const { data, exists, path } = deserializePackageJSON(`${resolved}`, result);
266266

267-
return {
268-
__proto__: null,
269-
...data,
270-
exists,
271-
pjsonPath: path,
272-
};
267+
// `data` is not shared, so extend it in place instead of spreading it:
268+
// spreading would invoke the lazy `imports` and `exports` getters.
269+
data.exists = exists;
270+
data.pjsonPath = path;
271+
return data;
273272
}
274273

275274
// This means that the response is a string

0 commit comments

Comments
 (0)