GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — module: cache nearest parent package.json per directory · nodejs/node@3157302

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • lib/internal/modules
    • package_json_reader.js
  • test/parallel
    • test-module-nearest-parent-package-json-cache.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/internal/modules/package_json_reader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/package_json_reader.js
+49-19Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const {
2323
ERR_MODULE_NOT_FOUND,
2424
},
2525
} = require('internal/errors');
26-
const { kEmptyObject } = require('internal/util');
26+
const { kEmptyObject, isWindows } = require('internal/util');
27+
const permission = require('internal/process/permission');
2728
const modulesBinding = internalBinding('modules');
2829
const path = require('path');
2930
const { validateString } = require('internal/validators');
@@ -140,10 +141,18 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
140141
}
141142

142143
/**
143-
* A cache mapping a module's path to its parent `package.json` file's path.
144-
* This is used in concert with `deserializedPackageJSONCache` to improve
145-
* the performance of `getNearestParentPackageJSON` when called repeatedly
146-
* on the same module paths.
144+
* A cache mapping a directory to the path of the nearest `package.json` at or
145+
* above it (`null` when there is none). The native traversal for a module
146+
* starts at the module's directory, so every module in a directory shares one
147+
* entry and one native call. Used in concert with
148+
* `deserializedPackageJSONCache`.
149+
*/
150+
const directoryToParentPackageJSONPathCache = new SafeMap();
151+
152+
/**
153+
* When the permission model is enabled the native traversal also depends on
154+
* the read permissions in effect at the time of the call, so results are only
155+
* remembered per exact module path, as before.
147156
*/
148157
const moduleToParentPackageJSONCache = new SafeMap();
149158

@@ -158,30 +167,51 @@ const moduleToParentPackageJSONCache = new SafeMap();
158167
*/
159168
const deserializedPackageJSONCache = new SafeMap();
160169

170+
/**
171+
* The directory the native nearest-parent traversal starts from for `checkPath`
172+
* (see BindingData::NormalizePath/TraverseParent): the path itself when it has
173+
* a trailing separator, its dirname otherwise.
174+
* @param {string} checkPath
175+
* @returns {string}
176+
*/
177+
function getTraversalStartDirectory(checkPath) {
178+
const last = checkPath[checkPath.length - 1];
179+
if (last === '/' || (isWindows && last === '\\')) {
180+
return StringPrototypeSlice(checkPath, 0, -1);
181+
}
182+
return path.dirname(checkPath);
183+
}
184+
161185
/**
162186
* Get the nearest parent package.json file from a given path.
163187
* Return the package.json data and the path to the package.json file, or undefined.
164188
* @param {string} checkPath The path to start searching from.
165189
* @returns {undefined | DeserializedPackageConfig}
166190
*/
167191
function getNearestParentPackageJSON(checkPath) {
168-
const parentPackageJSONPath = moduleToParentPackageJSONCache.get(checkPath);
169-
if (parentPackageJSONPath !== undefined) {
170-
return deserializedPackageJSONCache.get(parentPackageJSONPath);
192+
const permissionEnabled = permission.isEnabled();
193+
const cache = permissionEnabled ? moduleToParentPackageJSONCache : directoryToParentPackageJSONPathCache;
194+
const key = permissionEnabled ? checkPath : getTraversalStartDirectory(checkPath);
195+
let parentPackageJSONPath = cache.get(key);
196+
if (parentPackageJSONPath === undefined) {
197+
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
198+
if (result === undefined) {
199+
parentPackageJSONPath = null;
200+
} else {
201+
const packageConfig = deserializePackageJSON(checkPath, result);
202+
parentPackageJSONPath = packageConfig.path;
203+
if (!deserializedPackageJSONCache.has(parentPackageJSONPath)) {
204+
deserializedPackageJSONCache.set(parentPackageJSONPath, packageConfig);
205+
}
206+
}
207+
cache.set(key, parentPackageJSONPath);
171208
}
172209

173-
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
174-
const packageConfig = deserializePackageJSON(checkPath, result);
175-
176-
moduleToParentPackageJSONCache.set(checkPath, packageConfig.path);
177-
178-
const maybeCachedPackageConfig = deserializedPackageJSONCache.get(packageConfig.path);
179-
if (maybeCachedPackageConfig !== undefined) {
180-
return maybeCachedPackageConfig;
210+
if (parentPackageJSONPath === null) {
211+
// No package.json above this path: same shape as before, carrying the queried path.
212+
return deserializePackageJSON(checkPath, undefined);
181213
}
182-
183-
deserializedPackageJSONCache.set(packageConfig.path, packageConfig);
184-
return packageConfig;
214+
return deserializedPackageJSONCache.get(parentPackageJSONPath);
185215
}
186216

187217
/**
Collapse file

‎test/parallel/test-module-nearest-parent-package-json-cache.js‎

Copy file name to clipboard
+57Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
// Flags: --expose-internals
3+
// The nearest parent package.json lookup that every CommonJS module load
4+
// performs is answered once per directory, not once per file.
5+
const common = require('../common');
6+
const tmpdir = require('../common/tmpdir');
7+
const assert = require('assert');
8+
const fs = require('fs');
9+
const path = require('path');
10+
const { internalBinding } = require('internal/test/binding');
11+
const packageJsonReader = require('internal/modules/package_json_reader');
12+
13+
tmpdir.refresh();
14+
const root = tmpdir.resolve('pkg');
15+
const sub = path.join(root, 'lib', 'sub');
16+
fs.mkdirSync(sub, { recursive: true });
17+
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'pkg', type: 'commonjs' }));
18+
const files = [];
19+
for (const dir of [path.join(root, 'lib'), sub]) {
20+
for (let i = 0; i < 5; i++) {
21+
const file = path.join(dir, `m${i}.js`);
22+
fs.writeFileSync(file, 'module.exports = __filename;');
23+
files.push(file);
24+
}
25+
}
26+
27+
const modulesBinding = internalBinding('modules');
28+
const original = modulesBinding.getNearestParentPackageJSON;
29+
const calls = [];
30+
modulesBinding.getNearestParentPackageJSON = common.mustCallAtLeast((checkPath) => {
31+
calls.push(checkPath);
32+
return original(checkPath);
33+
}, 1);
34+
35+
for (const file of files) {
36+
assert.strictEqual(require(file), file);
37+
}
38+
// Ten modules in two directories: two lookups reach the binding.
39+
assert.strictEqual(calls.length, 2, `binding called for: ${calls.join(', ')}`);
40+
41+
// Same answer (and the same object) for every file of a directory, and for
42+
// the directory itself when asked with a trailing separator.
43+
const viaFile = packageJsonReader.getNearestParentPackageJSON(files[0]);
44+
assert.strictEqual(viaFile.data.name, 'pkg');
45+
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(files[1]), viaFile);
46+
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(root, 'lib') + path.sep), viaFile);
47+
assert.strictEqual(calls.length, 2);
48+
49+
// A directory that has not been seen yet is looked up once more.
50+
const other = path.join(root, 'other');
51+
fs.mkdirSync(other);
52+
fs.writeFileSync(path.join(other, 'x.js'), '');
53+
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(other, 'x.js')).data.name, 'pkg');
54+
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(other, 'y.js')).data.name, 'pkg');
55+
assert.strictEqual(calls.length, 3);
56+
57+
modulesBinding.getNearestParentPackageJSON = original;

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.