Skip to content

Commit ebf17f2

Browse files
src: reduce the nearest parent package JSON cache size
1 parent d079d8f commit ebf17f2

File tree

4 files changed

+51
-46
lines changed

4 files changed

+51
-46
lines changed

lib/internal/modules/package_json_reader.js

Lines changed: 51 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,70 @@ 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+
*/
141+
function findParentPackageJSON(checkPath) {
142+
const enabledPermission = permission.isEnabled();
143+
144+
const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, path.sep);
145+
let separatorIndex;
146+
147+
do {
148+
separatorIndex = StringPrototypeLastIndexOf(checkPath, path.sep);
149+
checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex);
150+
151+
if (enabledPermission && !permission.has('fs.read', checkPath + path.sep)) {
152+
return undefined;
153+
}
154+
155+
if (StringPrototypeEndsWith(checkPath, path.sep + 'node_modules')) {
156+
return undefined;
157+
}
158+
159+
const maybePackageJSONPath = checkPath + path.sep + 'package.json';
160+
const stat = internalFsBinding.internalModuleStat(checkPath + path.sep + 'package.json');
161+
162+
const packageJSONExists = stat === 0;
163+
if (packageJSONExists) {
164+
return maybePackageJSONPath;
165+
}
166+
} while (separatorIndex > rootSeparatorIndex);
167+
168+
return undefined;
169+
}
170+
130171
/**
131172
* Get the nearest parent package.json file from a given path.
132173
* Return the package.json data and the path to the package.json file, or undefined.
133174
* @param {string} checkPath The path to start searching from.
134175
* @returns {undefined | DeserializedPackageConfig}
135176
*/
136177
function getNearestParentPackageJSON(checkPath) {
137-
if (nearestParentPackageJSONCache.has(checkPath)) {
138-
return nearestParentPackageJSONCache.get(checkPath);
178+
const nearestParentPackageJSON = findParentPackageJSON(checkPath);
179+
180+
if (nearestParentPackageJSON === undefined) {
181+
return undefined;
182+
}
183+
184+
if (nearestParentPackageJSONCache.has(nearestParentPackageJSON)) {
185+
return nearestParentPackageJSONCache.get(nearestParentPackageJSON);
139186
}
140187

141-
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
188+
const result = modulesBinding.readPackageJSON(nearestParentPackageJSON);
142189

143190
if (result === undefined) {
144191
nearestParentPackageJSONCache.set(checkPath, undefined);
145192
return undefined;
146193
}
147194

148195
const packageConfig = deserializePackageJSON(checkPath, result);
149-
nearestParentPackageJSONCache.set(checkPath, packageConfig);
196+
nearestParentPackageJSONCache.set(nearestParentPackageJSON, packageConfig);
150197

151198
return packageConfig;
152199
}

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);
@@ -680,10 +646,6 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
680646
target,
681647
"getNearestParentPackageJSONType",
682648
GetNearestParentPackageJSONType);
683-
SetMethod(isolate,
684-
target,
685-
"getNearestParentPackageJSON",
686-
GetNearestParentPackageJSON);
687649
SetMethod(
688650
isolate, target, "getPackageScopeConfig", GetPackageScopeConfig<false>);
689651
SetMethod(isolate, target, "getPackageType", GetPackageScopeConfig<true>);
@@ -740,7 +702,6 @@ void BindingData::RegisterExternalReferences(
740702
ExternalReferenceRegistry* registry) {
741703
registry->Register(ReadPackageJSON);
742704
registry->Register(GetNearestParentPackageJSONType);
743-
registry->Register(GetNearestParentPackageJSON);
744705
registry->Register(GetPackageScopeConfig<false>);
745706
registry->Register(GetPackageScopeConfig<true>);
746707
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)