| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 77fedbd commit 43757d0
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,7 +23,8 @@ const { | |||
| 23 | 23 | ERR_MODULE_NOT_FOUND, | |
| 24 | 24 | }, | |
| 25 | 25 | } = require('internal/errors'); | |
| 26 | - const { kEmptyObject } = require('internal/util'); | ||
| 26 | + const { kEmptyObject, isWindows } = require('internal/util'); | ||
| 27 | + const permission = require('internal/process/permission'); | ||
| 27 | 28 | const modulesBinding = internalBinding('modules'); | |
| 28 | 29 | const path = require('path'); | |
| 29 | 30 | const { validateString } = require('internal/validators'); | |
@@ -140,10 +141,18 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { | |||
| 140 | 141 | } | |
| 141 | 142 | ||
| 142 | 143 | /** | |
| 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. | ||
| 147 | 156 | */ | |
| 148 | 157 | const moduleToParentPackageJSONCache = new SafeMap(); | |
| 149 | 158 | ||
@@ -158,30 +167,51 @@ const moduleToParentPackageJSONCache = new SafeMap(); | |||
| 158 | 167 | */ | |
| 159 | 168 | const deserializedPackageJSONCache = new SafeMap(); | |
| 160 | 169 | ||
| 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 | + | ||
| 161 | 185 | /** | |
| 162 | 186 | * Get the nearest parent package.json file from a given path. | |
| 163 | 187 | * Return the package.json data and the path to the package.json file, or undefined. | |
| 164 | 188 | * @param {string} checkPath The path to start searching from. | |
| 165 | 189 | * @returns {undefined | DeserializedPackageConfig} | |
| 166 | 190 | */ | |
| 167 | 191 | 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); | ||
| 171 | 208 | } | |
| 172 | 209 | ||
| 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); | ||
| 181 | 213 | } | |
| 182 | - | ||
| 183 | - deserializedPackageJSONCache.set(packageConfig.path, packageConfig); | ||
| 184 | - return packageConfig; | ||
| 214 | + return deserializedPackageJSONCache.get(parentPackageJSONPath); | ||
| 185 | 215 | } | |
| 186 | 216 | ||
| 187 | 217 | /** | |
| Original file line number | Diff line number | Diff 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; | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments