Skip to content

Commit 1138d7a

Browse files
committedMay 23, 2023
src: improve package.json reader performance
1 parent 4f6b9e3 commit 1138d7a

File tree

7 files changed

+179
-168
lines changed

7 files changed

+179
-168
lines changed
 

‎lib/internal/modules/cjs/loader.js

+4-31
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;
@@ -423,7 +396,7 @@ function readPackageScope(checkPath) {
423396
function tryPackage(requestPath, exts, isMain, originalPath) {
424397
const pkg = _readPackage(requestPath)?.main;
425398

426-
if (!pkg) {
399+
if (pkg === undefined) {
427400
return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
428401
}
429402

@@ -563,7 +536,7 @@ function resolveExports(nmPath, request) {
563536
return;
564537
const pkgPath = path.resolve(nmPath, name);
565538
const pkg = _readPackage(pkgPath);
566-
if (pkg?.exports != null) {
539+
if (pkg?.exports !== undefined) {
567540
try {
568541
const { packageExportsResolve } = require('internal/modules/esm/resolve');
569542
return finalizeEsmResolution(packageExportsResolve(

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

+12-48
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,9 +34,10 @@ 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 = {
37+
const packageJSON = packageJsonReader.read(path);
38+
39+
if (packageJSON === undefined) {
40+
const json = {
4841
pjsonPath: path,
4942
exists: false,
5043
main: undefined,
@@ -53,48 +46,19 @@ function getPackageConfig(path, specifier, base) {
5346
exports: undefined,
5447
imports: undefined,
5548
};
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-
);
49+
packageJSONCache.set(path, json);
50+
return json;
6951
}
7052

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-
}
8253
// Ignore unknown types for forwards compatibility
83-
if (type !== 'module' && type !== 'commonjs') {
84-
type = 'none';
54+
if (packageJSON.type !== 'module' && packageJSON.type !== 'commonjs') {
55+
packageJSON.type = 'none';
8556
}
8657

87-
const packageConfig = {
88-
pjsonPath: path,
89-
exists: true,
90-
main,
91-
name,
92-
type,
93-
exports,
94-
imports,
95-
};
96-
packageJSONCache.set(path, packageConfig);
97-
return packageConfig;
58+
packageJSON.pjsonPath = path;
59+
packageJSON.exists = true;
60+
packageJSONCache.set(path, packageJSON);
61+
return packageJSON;
9862
}
9963

10064

‎lib/internal/modules/package_json_reader.js

+34-6
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,28 +14,52 @@ const cache = new SafeMap();
1014
let manifest;
1115

1216
/**
13-
*
17+
* Returns undefined for all failure cases.
1418
* @param {string} jsonPath
1519
*/
1620
function read(jsonPath) {
1721
if (cache.has(jsonPath)) {
1822
return cache.get(jsonPath);
1923
}
2024

21-
const { 0: string, 1: containsKeys } = internalModuleReadJSON(
25+
const {
26+
0: includes_keys,
27+
1: name,
28+
2: main,
29+
3: exports,
30+
4: imports,
31+
5: type,
32+
6: shouldParseExports,
33+
7: shouldParseImports,
34+
} = internalModuleReadJSON(
2235
toNamespacedPath(jsonPath),
2336
);
24-
const result = { string, containsKeys };
37+
38+
let result;
39+
40+
if (includes_keys !== undefined) {
41+
result = { name, main, exports, imports, type, includes_keys };
42+
43+
// Execute JSONParse on demand for improving performance
44+
if (shouldParseExports && exports !== undefined) {
45+
result.exports = JSONParse(exports);
46+
}
47+
48+
if (shouldParseImports && imports !== undefined) {
49+
result.imports = JSONParse(imports);
50+
}
51+
}
52+
2553
const { getOptionValue } = require('internal/options');
26-
if (string !== undefined) {
54+
if (name !== undefined) {
2755
if (manifest === undefined) {
2856
manifest = getOptionValue('--experimental-policy') ?
2957
require('internal/process/policy').manifest :
3058
null;
3159
}
3260
if (manifest !== null) {
3361
const jsonURL = pathToFileURL(jsonPath);
34-
manifest.assertIntegrity(jsonURL, string);
62+
manifest.assertIntegrity(jsonURL, JSONStringify(result));
3563
}
3664
}
3765
cache.set(jsonPath, result);

‎src/node_file.cc

+99-29
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
#include "tracing/trace_event.h"
3333

3434
#include "req_wrap-inl.h"
35+
#include "simdjson.h"
3536
#include "stream_base-inl.h"
3637
#include "string_bytes.h"
38+
#include "v8-primitive.h"
3739

3840
#include <fcntl.h>
3941
#include <sys/types.h>
@@ -1079,41 +1081,109 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
10791081
}
10801082

10811083
const size_t size = offset - start;
1082-
char* p = &chars[start];
1083-
char* pe = &chars[size];
1084-
char* pos[2];
1085-
char** ppos = &pos[0];
1086-
1087-
while (p < pe) {
1088-
char c = *p++;
1089-
if (c == '\\' && p < pe && *p == '"') p++;
1090-
if (c != '"') continue;
1091-
*ppos++ = p;
1092-
if (ppos < &pos[2]) continue;
1093-
ppos = &pos[0];
1094-
1095-
char* s = &pos[0][0];
1096-
char* se = &pos[1][-1]; // Exclude quote.
1097-
size_t n = se - s;
1098-
1099-
if (n == 4) {
1100-
if (0 == memcmp(s, "main", 4)) break;
1101-
if (0 == memcmp(s, "name", 4)) break;
1102-
if (0 == memcmp(s, "type", 4)) break;
1103-
} else if (n == 7) {
1104-
if (0 == memcmp(s, "exports", 7)) break;
1105-
if (0 == memcmp(s, "imports", 7)) break;
1084+
simdjson::ondemand::parser parser;
1085+
simdjson::padded_string json_string(chars.data() + start, size);
1086+
simdjson::ondemand::document document;
1087+
simdjson::ondemand::object obj;
1088+
auto error = parser.iterate(json_string).get(document);
1089+
1090+
if (error || document.get_object().get(obj)) {
1091+
args.GetReturnValue().Set(Array::New(isolate));
1092+
return;
1093+
}
1094+
1095+
auto js_string = [&](std::string_view sv) {
1096+
return ToV8Value(env->context(), sv, isolate).ToLocalChecked();
1097+
};
1098+
1099+
bool includes_keys{false};
1100+
Local<Value> name = Undefined(isolate);
1101+
Local<Value> main = Undefined(isolate);
1102+
Local<Value> exports = Undefined(isolate);
1103+
Local<Value> imports = Undefined(isolate);
1104+
Local<Value> type = Undefined(isolate);
1105+
bool should_parse_exports{false};
1106+
bool should_parse_imports{false};
1107+
1108+
// Check for "name" field
1109+
std::string_view name_value{};
1110+
if (!obj["name"].get_string().get(name_value)) {
1111+
name = js_string(name_value);
1112+
includes_keys = true;
1113+
}
1114+
1115+
// Check for "main" field
1116+
std::string_view main_value{};
1117+
if (!obj["main"].get_string().get(main_value)) {
1118+
main = js_string(main_value);
1119+
includes_keys = true;
1120+
}
1121+
1122+
// Check for "exports" field
1123+
simdjson::ondemand::value exports_value{};
1124+
if (!obj["exports"].get(exports_value)) {
1125+
simdjson::ondemand::json_type exports_type{};
1126+
if (!exports_value.type().get(exports_type)) {
1127+
std::string_view exports_string_value{};
1128+
if (exports_type == simdjson::ondemand::json_type::string) {
1129+
if (!exports_value.get_string().get(exports_string_value)) {
1130+
exports = js_string(exports_string_value);
1131+
includes_keys = true;
1132+
}
1133+
} else if (exports_type == simdjson::ondemand::json_type::object) {
1134+
simdjson::ondemand::object subobject;
1135+
if (!exports_value.get_object().get(subobject)) {
1136+
if (!subobject.raw_json().get(exports_string_value)) {
1137+
exports = js_string(exports_string_value);
1138+
should_parse_exports = true;
1139+
includes_keys = true;
1140+
}
1141+
}
1142+
}
1143+
}
1144+
}
1145+
1146+
// Check for "imports" field
1147+
simdjson::ondemand::value imports_value{};
1148+
if (!obj["imports"].get(imports_value)) {
1149+
simdjson::ondemand::json_type imports_type{};
1150+
if (!imports_value.type().get(imports_type)) {
1151+
std::string_view imports_string_value{};
1152+
if (imports_type == simdjson::ondemand::json_type::string) {
1153+
if (!imports_value.get_string().get(imports_string_value)) {
1154+
imports = js_string(imports_string_value);
1155+
includes_keys = true;
1156+
}
1157+
} else if (imports_type == simdjson::ondemand::json_type::object) {
1158+
simdjson::ondemand::object subobject;
1159+
if (!imports_value.get_object().get(subobject)) {
1160+
if (!subobject.raw_json().get(imports_string_value)) {
1161+
imports = js_string(imports_string_value);
1162+
should_parse_imports = true;
1163+
includes_keys = true;
1164+
}
1165+
}
1166+
}
11061167
}
11071168
}
11081169

1170+
std::string_view type_value{};
1171+
if (!obj["type"].get(type_value)) {
1172+
type = js_string(type_value);
1173+
includes_keys = true;
1174+
}
11091175

11101176
Local<Value> return_value[] = {
1111-
String::NewFromUtf8(isolate,
1112-
&chars[start],
1113-
v8::NewStringType::kNormal,
1114-
size).ToLocalChecked(),
1115-
Boolean::New(isolate, p < pe ? true : false)
1177+
Boolean::New(isolate, includes_keys),
1178+
name,
1179+
main,
1180+
exports,
1181+
imports,
1182+
type,
1183+
Boolean::New(isolate, should_parse_exports),
1184+
Boolean::New(isolate, should_parse_imports),
11161185
};
1186+
11171187
args.GetReturnValue().Set(
11181188
Array::New(isolate, return_value, arraysize(return_value)));
11191189
}

0 commit comments

Comments
 (0)