Skip to content

Commit fe6b124

Browse files
committed
src: improve package.json reader performance
1 parent b85a2b1 commit fe6b124

File tree

9 files changed

+220
-180
lines changed

9 files changed

+220
-180
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ const {
8282
pendingDeprecate,
8383
emitExperimentalWarning,
8484
kEmptyObject,
85-
filterOwnProperties,
8685
setOwnProperty,
8786
getLazy,
8887
} = require('internal/util');
@@ -353,36 +352,10 @@ function initializeCJS() {
353352
// -> a.<ext>
354353
// -> a/index.<ext>
355354

356-
const packageJsonCache = new SafeMap();
357-
358355
function readPackage(requestPath) {
359356
const jsonPath = path.resolve(requestPath, 'package.json');
360-
361-
const existing = packageJsonCache.get(jsonPath);
362-
if (existing !== undefined) return existing;
363-
364-
const result = packageJsonReader.read(jsonPath);
365-
const json = result.containsKeys === false ? '{}' : result.string;
366-
if (json === undefined) {
367-
packageJsonCache.set(jsonPath, false);
368-
return false;
369-
}
370-
371-
try {
372-
const filtered = filterOwnProperties(JSONParse(json), [
373-
'name',
374-
'main',
375-
'exports',
376-
'imports',
377-
'type',
378-
]);
379-
packageJsonCache.set(jsonPath, filtered);
380-
return filtered;
381-
} catch (e) {
382-
e.path = jsonPath;
383-
e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
384-
throw e;
385-
}
357+
// Return undefined or the filtered package.json as a JS object
358+
return packageJsonReader.read(jsonPath);
386359
}
387360

388361
let _readPackage = readPackage;

lib/internal/modules/esm/package_config.js

Lines changed: 14 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
'use strict';
22

33
const {
4-
JSONParse,
5-
ObjectPrototypeHasOwnProperty,
64
SafeMap,
75
StringPrototypeEndsWith,
86
} = primordials;
97
const { URL, fileURLToPath } = require('internal/url');
10-
const {
11-
ERR_INVALID_PACKAGE_CONFIG,
12-
} = require('internal/errors').codes;
13-
14-
const { filterOwnProperties } = require('internal/util');
15-
168

179
/**
1810
* @typedef {string | string[] | Record<string, unknown>} Exports
@@ -42,59 +34,23 @@ function getPackageConfig(path, specifier, base) {
4234
return existing;
4335
}
4436
const packageJsonReader = require('internal/modules/package_json_reader');
45-
const source = packageJsonReader.read(path).string;
46-
if (source === undefined) {
47-
const packageConfig = {
48-
pjsonPath: path,
49-
exists: false,
50-
main: undefined,
51-
name: undefined,
52-
type: 'none',
53-
exports: undefined,
54-
imports: undefined,
55-
};
56-
packageJSONCache.set(path, packageConfig);
57-
return packageConfig;
58-
}
59-
60-
let packageJSON;
61-
try {
62-
packageJSON = JSONParse(source);
63-
} catch (error) {
64-
throw new ERR_INVALID_PACKAGE_CONFIG(
65-
path,
66-
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
67-
error.message,
68-
);
69-
}
70-
71-
let { imports, main, name, type } = filterOwnProperties(packageJSON, ['imports', 'main', 'name', 'type']);
72-
const exports = ObjectPrototypeHasOwnProperty(packageJSON, 'exports') ? packageJSON.exports : undefined;
73-
if (typeof imports !== 'object' || imports === null) {
74-
imports = undefined;
75-
}
76-
if (typeof main !== 'string') {
77-
main = undefined;
78-
}
79-
if (typeof name !== 'string') {
80-
name = undefined;
81-
}
82-
// Ignore unknown types for forwards compatibility
83-
if (type !== 'module' && type !== 'commonjs') {
84-
type = 'none';
85-
}
37+
const result = packageJsonReader.read(path);
38+
const packageJSON = result ?? {
39+
main: undefined,
40+
name: undefined,
41+
type: 'none',
42+
exports: undefined,
43+
imports: undefined,
44+
};
8645

87-
const packageConfig = {
46+
const json = {
47+
__proto__: null,
8848
pjsonPath: path,
89-
exists: true,
90-
main,
91-
name,
92-
type,
93-
exports,
94-
imports,
49+
exists: result !== undefined,
50+
...packageJSON,
9551
};
96-
packageJSONCache.set(path, packageConfig);
97-
return packageConfig;
52+
packageJSONCache.set(path, json);
53+
return json;
9854
}
9955

10056

lib/internal/modules/esm/resolve.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -734,8 +734,7 @@ function packageResolve(specifier, base, conditions) {
734734
const packageConfig = getPackageScopeConfig(base);
735735
if (packageConfig.exists) {
736736
const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
737-
if (packageConfig.name === packageName &&
738-
packageConfig.exports !== undefined && packageConfig.exports !== null) {
737+
if (packageConfig.name === packageName && packageConfig.exports !== undefined) {
739738
return packageExportsResolve(
740739
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
741740
}
@@ -760,7 +759,7 @@ function packageResolve(specifier, base, conditions) {
760759

761760
// Package match.
762761
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
763-
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
762+
if (packageConfig.exports !== undefined) {
764763
return packageExportsResolve(
765764
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
766765
}

lib/internal/modules/package_json_reader.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
'use strict';
22

3-
const { SafeMap } = primordials;
3+
const {
4+
JSONParse,
5+
JSONStringify,
6+
SafeMap,
7+
} = primordials;
48
const { internalModuleReadJSON } = internalBinding('fs');
59
const { pathToFileURL } = require('url');
610
const { toNamespacedPath } = require('path');
@@ -10,30 +14,67 @@ const cache = new SafeMap();
1014
let manifest;
1115

1216
/**
13-
*
17+
* Returns undefined for all failure cases.
1418
* @param {string} jsonPath
19+
* @returns {{
20+
* name?: string,
21+
* main?: string,
22+
* exports?: string | Record<string, unknown>,
23+
* imports?: string | Record<string, unknown>,
24+
* type: 'commonjs' | 'module' | 'none' | unknown,
25+
* } | undefined}
1526
*/
1627
function read(jsonPath) {
1728
if (cache.has(jsonPath)) {
1829
return cache.get(jsonPath);
1930
}
2031

21-
const { 0: string, 1: containsKeys } = internalModuleReadJSON(
32+
const {
33+
0: includesKeys,
34+
1: name,
35+
2: main,
36+
3: exports,
37+
4: imports,
38+
5: type,
39+
6: parseExports,
40+
7: parseImports,
41+
} = internalModuleReadJSON(
2242
toNamespacedPath(jsonPath),
2343
);
24-
const result = { string, containsKeys };
25-
const { getOptionValue } = require('internal/options');
26-
if (string !== undefined) {
44+
45+
let result;
46+
47+
if (includesKeys !== undefined) {
48+
result = {
49+
__proto__: null,
50+
name,
51+
main,
52+
exports,
53+
imports,
54+
type,
55+
};
56+
57+
// Execute JSONParse on demand for improved performance
58+
if (parseExports) {
59+
result.exports = JSONParse(exports);
60+
}
61+
62+
if (parseImports) {
63+
result.imports = JSONParse(imports);
64+
}
65+
2766
if (manifest === undefined) {
67+
const { getOptionValue } = require('internal/options');
2868
manifest = getOptionValue('--experimental-policy') ?
2969
require('internal/process/policy').manifest :
3070
null;
3171
}
3272
if (manifest !== null) {
3373
const jsonURL = pathToFileURL(jsonPath);
34-
manifest.assertIntegrity(jsonURL, string);
74+
manifest.assertIntegrity(jsonURL, JSONStringify(result));
3575
}
3676
}
77+
3778
cache.set(jsonPath, result);
3879
return result;
3980
}

src/node_errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ void AppendExceptionLine(Environment* env,
6666
V(ERR_ILLEGAL_CONSTRUCTOR, Error) \
6767
V(ERR_INVALID_ADDRESS, Error) \
6868
V(ERR_INVALID_ARG_VALUE, TypeError) \
69+
V(ERR_INVALID_PACKAGE_CONFIG, SyntaxError) \
6970
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
7071
V(ERR_INVALID_ARG_TYPE, TypeError) \
7172
V(ERR_INVALID_OBJECT_DEFINE_PROPERTY, TypeError) \

0 commit comments

Comments
 (0)