Skip to content

Commit ac78491

Browse files
michaelsmithxyztargos
authored andcommitted
src: reduce the nearest parent package JSON cache size
PR-URL: #59888 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent abecdcb commit ac78491

File tree

4 files changed

+52
-46
lines changed

4 files changed

+52
-46
lines changed

lib/internal/modules/package_json_reader.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ const {
66
ObjectDefineProperty,
77
RegExpPrototypeExec,
88
SafeMap,
9+
StringPrototypeEndsWith,
910
StringPrototypeIndexOf,
11+
StringPrototypeLastIndexOf,
1012
StringPrototypeSlice,
1113
} = primordials;
1214
const {
@@ -26,6 +28,7 @@ const {
2628
const { kEmptyObject } = require('internal/util');
2729
const modulesBinding = internalBinding('modules');
2830
const path = require('path');
31+
const permission = require('internal/process/permission');
2932
const { validateString } = require('internal/validators');
3033
const internalFsBinding = internalBinding('fs');
3134

@@ -127,26 +130,71 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
127130
};
128131
}
129132

133+
/**
134+
* Given a file path, walk the filesystem upwards until we find its closest parent
135+
* `package.json` file, stopping when:
136+
* 1. we find a `package.json` file;
137+
* 2. we find a path that we do not have permission to read;
138+
* 3. we find a containing `node_modules` directory;
139+
* 4. or, we reach the filesystem root
140+
* @returns {undefined | string}
141+
*/
142+
function findParentPackageJSON(checkPath) {
143+
const enabledPermission = permission.isEnabled();
144+
145+
const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, path.sep);
146+
let separatorIndex;
147+
148+
do {
149+
separatorIndex = StringPrototypeLastIndexOf(checkPath, path.sep);
150+
checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex);
151+
152+
if (enabledPermission && !permission.has('fs.read', checkPath + path.sep)) {
153+
return undefined;
154+
}
155+
156+
if (StringPrototypeEndsWith(checkPath, path.sep + 'node_modules')) {
157+
return undefined;
158+
}
159+
160+
const maybePackageJSONPath = checkPath + path.sep + 'package.json';
161+
const stat = internalFsBinding.internalModuleStat(checkPath + path.sep + 'package.json');
162+
163+
const packageJSONExists = stat === 0;
164+
if (packageJSONExists) {
165+
return maybePackageJSONPath;
166+
}
167+
} while (separatorIndex > rootSeparatorIndex);
168+
169+
return undefined;
170+
}
171+
130172
/**
131173
* Get the nearest parent package.json file from a given path.
132174
* Return the package.json data and the path to the package.json file, or undefined.
133175
* @param {string} checkPath The path to start searching from.
134176
* @returns {undefined | DeserializedPackageConfig}
135177
*/
136178
function getNearestParentPackageJSON(checkPath) {
137-
if (nearestParentPackageJSONCache.has(checkPath)) {
138-
return nearestParentPackageJSONCache.get(checkPath);
179+
const nearestParentPackageJSON = findParentPackageJSON(checkPath);
180+
181+
if (nearestParentPackageJSON === undefined) {
182+
return undefined;
183+
}
184+
185+
if (nearestParentPackageJSONCache.has(nearestParentPackageJSON)) {
186+
return nearestParentPackageJSONCache.get(nearestParentPackageJSON);
139187
}
140188

141-
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
189+
const result = modulesBinding.readPackageJSON(nearestParentPackageJSON);
142190

143191
if (result === undefined) {
144192
nearestParentPackageJSONCache.set(checkPath, undefined);
145193
return undefined;
146194
}
147195

148196
const packageConfig = deserializePackageJSON(checkPath, result);
149-
nearestParentPackageJSONCache.set(checkPath, packageConfig);
197+
nearestParentPackageJSONCache.set(nearestParentPackageJSON, packageConfig);
150198

151199
return packageConfig;
152200
}

src/node_modules.cc

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -320,40 +320,6 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
320320
return nullptr;
321321
}
322322

323-
void BindingData::GetNearestParentPackageJSON(
324-
const v8::FunctionCallbackInfo<v8::Value>& args) {
325-
CHECK_GE(args.Length(), 1);
326-
CHECK(args[0]->IsString());
327-
328-
Realm* realm = Realm::GetCurrent(args);
329-
BufferValue path_value(realm->isolate(), args[0]);
330-
// Check if the path has a trailing slash. If so, add it after
331-
// ToNamespacedPath() as it will be deleted by ToNamespacedPath()
332-
bool slashCheck = path_value.ToStringView().ends_with(kPathSeparator);
333-
334-
ToNamespacedPath(realm->env(), &path_value);
335-
336-
std::string path_value_str = path_value.ToString();
337-
if (slashCheck) {
338-
path_value_str.push_back(kPathSeparator);
339-
}
340-
341-
std::filesystem::path path;
342-
343-
#ifdef _WIN32
344-
std::wstring wide_path = ConvertToWideString(path_value_str, GetACP());
345-
path = std::filesystem::path(wide_path);
346-
#else
347-
path = std::filesystem::path(path_value_str);
348-
#endif
349-
350-
auto package_json = TraverseParent(realm, path);
351-
352-
if (package_json != nullptr) {
353-
args.GetReturnValue().Set(package_json->Serialize(realm));
354-
}
355-
}
356-
357323
void BindingData::GetNearestParentPackageJSONType(
358324
const FunctionCallbackInfo<Value>& args) {
359325
CHECK_GE(args.Length(), 1);
@@ -674,10 +640,6 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
674640
target,
675641
"getNearestParentPackageJSONType",
676642
GetNearestParentPackageJSONType);
677-
SetMethod(isolate,
678-
target,
679-
"getNearestParentPackageJSON",
680-
GetNearestParentPackageJSON);
681643
SetMethod(
682644
isolate, target, "getPackageScopeConfig", GetPackageScopeConfig<false>);
683645
SetMethod(isolate, target, "getPackageType", GetPackageScopeConfig<true>);
@@ -734,7 +696,6 @@ void BindingData::RegisterExternalReferences(
734696
ExternalReferenceRegistry* registry) {
735697
registry->Register(ReadPackageJSON);
736698
registry->Register(GetNearestParentPackageJSONType);
737-
registry->Register(GetNearestParentPackageJSON);
738699
registry->Register(GetPackageScopeConfig<false>);
739700
registry->Register(GetPackageScopeConfig<true>);
740701
registry->Register(EnableCompileCache);

src/node_modules.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ class BindingData : public SnapshotableObject {
5555
SET_MEMORY_INFO_NAME(BindingData)
5656

5757
static void ReadPackageJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
58-
static void GetNearestParentPackageJSON(
59-
const v8::FunctionCallbackInfo<v8::Value>& args);
6058
static void GetNearestParentPackageJSONType(
6159
const v8::FunctionCallbackInfo<v8::Value>& args);
6260
template <bool return_only_type>

typings/internalBinding/modules.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export type SerializedPackageConfig = [
2323
export interface ModulesBinding {
2424
readPackageJSON(path: string): SerializedPackageConfig | undefined;
2525
getNearestParentPackageJSONType(path: string): PackageConfig['type']
26-
getNearestParentPackageJSON(path: string): SerializedPackageConfig | undefined
2726
getPackageScopeConfig(path: string): SerializedPackageConfig | undefined
2827
getPackageType(path: string): PackageConfig['type'] | undefined
2928
enableCompileCache(path?: string): { status: number, message?: string, directory?: string }

0 commit comments

Comments
 (0)