mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
getNearestParentPackageJSON() memoized its answer per file, so every module loaded still made one native call, and TraverseParent() builds several std::filesystem::path temporaries per directory level and serializes the whole package.json, which the JS side then usually discarded because it already had that package.json deserialized. The native traversal starts at the directory of the given path, so the answer only depends on that directory: key the memo by it (following NormalizePath()'s trailing-separator rule), so that all modules in a directory share one native call. When the permission model is enabled the traversal also depends on the read permissions in effect at call time, so that configuration keeps the per-file cache. Loading a 1000-module tree spread over ~240 directories goes from 1000 to 236 native calls. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65326 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jordan Harband <ljharb@gmail.com>
58 lines
2.4 KiB
JavaScript
58 lines
2.4 KiB
JavaScript
'use strict';
|
|
// Flags: --expose-internals
|
|
// The nearest parent package.json lookup that every CommonJS module load
|
|
// performs is answered once per directory, not once per file.
|
|
const common = require('../common');
|
|
const tmpdir = require('../common/tmpdir');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { internalBinding } = require('internal/test/binding');
|
|
const packageJsonReader = require('internal/modules/package_json_reader');
|
|
|
|
tmpdir.refresh();
|
|
const root = tmpdir.resolve('pkg');
|
|
const sub = path.join(root, 'lib', 'sub');
|
|
fs.mkdirSync(sub, { recursive: true });
|
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'pkg', type: 'commonjs' }));
|
|
const files = [];
|
|
for (const dir of [path.join(root, 'lib'), sub]) {
|
|
for (let i = 0; i < 5; i++) {
|
|
const file = path.join(dir, `m${i}.js`);
|
|
fs.writeFileSync(file, 'module.exports = __filename;');
|
|
files.push(file);
|
|
}
|
|
}
|
|
|
|
const modulesBinding = internalBinding('modules');
|
|
const original = modulesBinding.getNearestParentPackageJSON;
|
|
const calls = [];
|
|
modulesBinding.getNearestParentPackageJSON = common.mustCallAtLeast((checkPath) => {
|
|
calls.push(checkPath);
|
|
return original(checkPath);
|
|
}, 1);
|
|
|
|
for (const file of files) {
|
|
assert.strictEqual(require(file), file);
|
|
}
|
|
// Ten modules in two directories: two lookups reach the binding.
|
|
assert.strictEqual(calls.length, 2, `binding called for: ${calls.join(', ')}`);
|
|
|
|
// Same answer (and the same object) for every file of a directory, and for
|
|
// the directory itself when asked with a trailing separator.
|
|
const viaFile = packageJsonReader.getNearestParentPackageJSON(files[0]);
|
|
assert.strictEqual(viaFile.data.name, 'pkg');
|
|
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(files[1]), viaFile);
|
|
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(root, 'lib') + path.sep), viaFile);
|
|
assert.strictEqual(calls.length, 2);
|
|
|
|
// A directory that has not been seen yet is looked up once more.
|
|
const other = path.join(root, 'other');
|
|
fs.mkdirSync(other);
|
|
fs.writeFileSync(path.join(other, 'x.js'), '');
|
|
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(other, 'x.js')).data.name, 'pkg');
|
|
assert.strictEqual(packageJsonReader.getNearestParentPackageJSON(path.join(other, 'y.js')).data.name, 'pkg');
|
|
assert.strictEqual(calls.length, 3);
|
|
|
|
modulesBinding.getNearestParentPackageJSON = original;
|