src: reduce the nearest parent package JSON cache size

PR-URL: https://github.com/nodejs/node/pull/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>
This commit is contained in:
Michael Smith 2025-09-17 06:26:52 -04:00 committed by GitHub
parent db926dc1fc
commit e596098e50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 52 additions and 46 deletions

View File

@ -6,7 +6,9 @@ const {
ObjectDefineProperty,
RegExpPrototypeExec,
SafeMap,
StringPrototypeEndsWith,
StringPrototypeIndexOf,
StringPrototypeLastIndexOf,
StringPrototypeSlice,
} = primordials;
const {
@ -26,6 +28,7 @@ const {
const { kEmptyObject } = require('internal/util');
const modulesBinding = internalBinding('modules');
const path = require('path');
const permission = require('internal/process/permission');
const { validateString } = require('internal/validators');
const internalFsBinding = internalBinding('fs');
@ -127,6 +130,45 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
};
}
/**
* Given a file path, walk the filesystem upwards until we find its closest parent
* `package.json` file, stopping when:
* 1. we find a `package.json` file;
* 2. we find a path that we do not have permission to read;
* 3. we find a containing `node_modules` directory;
* 4. or, we reach the filesystem root
* @returns {undefined | string}
*/
function findParentPackageJSON(checkPath) {
const enabledPermission = permission.isEnabled();
const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, path.sep);
let separatorIndex;
do {
separatorIndex = StringPrototypeLastIndexOf(checkPath, path.sep);
checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex);
if (enabledPermission && !permission.has('fs.read', checkPath + path.sep)) {
return undefined;
}
if (StringPrototypeEndsWith(checkPath, path.sep + 'node_modules')) {
return undefined;
}
const maybePackageJSONPath = checkPath + path.sep + 'package.json';
const stat = internalFsBinding.internalModuleStat(checkPath + path.sep + 'package.json');
const packageJSONExists = stat === 0;
if (packageJSONExists) {
return maybePackageJSONPath;
}
} while (separatorIndex > rootSeparatorIndex);
return undefined;
}
/**
* Get the nearest parent package.json file from a given path.
* Return the package.json data and the path to the package.json file, or undefined.
@ -134,11 +176,17 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
* @returns {undefined | DeserializedPackageConfig}
*/
function getNearestParentPackageJSON(checkPath) {
if (nearestParentPackageJSONCache.has(checkPath)) {
return nearestParentPackageJSONCache.get(checkPath);
const nearestParentPackageJSON = findParentPackageJSON(checkPath);
if (nearestParentPackageJSON === undefined) {
return undefined;
}
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
if (nearestParentPackageJSONCache.has(nearestParentPackageJSON)) {
return nearestParentPackageJSONCache.get(nearestParentPackageJSON);
}
const result = modulesBinding.readPackageJSON(nearestParentPackageJSON);
if (result === undefined) {
nearestParentPackageJSONCache.set(checkPath, undefined);
@ -146,7 +194,7 @@ function getNearestParentPackageJSON(checkPath) {
}
const packageConfig = deserializePackageJSON(checkPath, result);
nearestParentPackageJSONCache.set(checkPath, packageConfig);
nearestParentPackageJSONCache.set(nearestParentPackageJSON, packageConfig);
return packageConfig;
}

View File

@ -320,40 +320,6 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
return nullptr;
}
void BindingData::GetNearestParentPackageJSON(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_GE(args.Length(), 1);
CHECK(args[0]->IsString());
Realm* realm = Realm::GetCurrent(args);
BufferValue path_value(realm->isolate(), args[0]);
// Check if the path has a trailing slash. If so, add it after
// ToNamespacedPath() as it will be deleted by ToNamespacedPath()
bool slashCheck = path_value.ToStringView().ends_with(kPathSeparator);
ToNamespacedPath(realm->env(), &path_value);
std::string path_value_str = path_value.ToString();
if (slashCheck) {
path_value_str.push_back(kPathSeparator);
}
std::filesystem::path path;
#ifdef _WIN32
std::wstring wide_path = ConvertToWideString(path_value_str, GetACP());
path = std::filesystem::path(wide_path);
#else
path = std::filesystem::path(path_value_str);
#endif
auto package_json = TraverseParent(realm, path);
if (package_json != nullptr) {
args.GetReturnValue().Set(package_json->Serialize(realm));
}
}
void BindingData::GetNearestParentPackageJSONType(
const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 1);
@ -680,10 +646,6 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
target,
"getNearestParentPackageJSONType",
GetNearestParentPackageJSONType);
SetMethod(isolate,
target,
"getNearestParentPackageJSON",
GetNearestParentPackageJSON);
SetMethod(
isolate, target, "getPackageScopeConfig", GetPackageScopeConfig<false>);
SetMethod(isolate, target, "getPackageType", GetPackageScopeConfig<true>);
@ -740,7 +702,6 @@ void BindingData::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(ReadPackageJSON);
registry->Register(GetNearestParentPackageJSONType);
registry->Register(GetNearestParentPackageJSON);
registry->Register(GetPackageScopeConfig<false>);
registry->Register(GetPackageScopeConfig<true>);
registry->Register(EnableCompileCache);

View File

@ -55,8 +55,6 @@ class BindingData : public SnapshotableObject {
SET_MEMORY_INFO_NAME(BindingData)
static void ReadPackageJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetNearestParentPackageJSON(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetNearestParentPackageJSONType(
const v8::FunctionCallbackInfo<v8::Value>& args);
template <bool return_only_type>

View File

@ -23,7 +23,6 @@ export type SerializedPackageConfig = [
export interface ModulesBinding {
readPackageJSON(path: string): SerializedPackageConfig | undefined;
getNearestParentPackageJSONType(path: string): PackageConfig['type']
getNearestParentPackageJSON(path: string): SerializedPackageConfig | undefined
getPackageScopeConfig(path: string): SerializedPackageConfig | undefined
getPackageType(path: string): PackageConfig['type'] | undefined
enableCompileCache(path?: string): { status: number, message?: string, directory?: string }