66 ObjectDefineProperty,
77 RegExpPrototypeExec,
88 SafeMap,
9- StringPrototypeEndsWith,
109 StringPrototypeIndexOf,
11- StringPrototypeLastIndexOf,
1210 StringPrototypeSlice,
1311} = primordials ;
1412const {
@@ -28,11 +26,9 @@ const {
2826const { kEmptyObject } = require ( 'internal/util' ) ;
2927const modulesBinding = internalBinding ( 'modules' ) ;
3028const path = require ( 'path' ) ;
31- const permission = require ( 'internal/process/permission' ) ;
3229const { validateString } = require ( 'internal/validators' ) ;
3330const internalFsBinding = internalBinding ( 'fs' ) ;
3431
35- const nearestParentPackageJSONCache = new SafeMap ( ) ;
3632
3733/**
3834 * @typedef {import('typings/internalBinding/modules').DeserializedPackageConfig } DeserializedPackageConfig
@@ -68,28 +64,41 @@ function deserializePackageJSON(path, contents) {
6864
6965 const pjsonPath = optionalFilePath ?? path ;
7066
71- return {
72- data : {
67+ const data = {
68+ __proto__ : null ,
69+ ...( name != null && { name } ) ,
70+ ...( main != null && { main } ) ,
71+ ...( type != null && { type } ) ,
72+ } ;
73+
74+ if ( plainExports !== null ) {
75+ ObjectDefineProperty ( data , 'exports' , {
76+ __proto__ : null ,
77+ configurable : true ,
78+ enumerable : true ,
79+ get ( ) {
80+ const value = requiresJSONParse ( plainExports ) ? JSONParse ( plainExports ) : plainExports ;
81+ ObjectDefineProperty ( data , 'exports' , { __proto__ : null , enumerable : true , value } ) ;
82+ return value ;
83+ } ,
84+ } ) ;
85+ }
86+
87+ if ( plainImports !== null ) {
88+ ObjectDefineProperty ( data , 'imports' , {
7389 __proto__ : null ,
74- ...( name != null && { name } ) ,
75- ...( main != null && { main } ) ,
76- ...( type != null && { type } ) ,
77- ...( plainImports != null && {
78- // This getters are used to lazily parse the imports and exports fields.
79- get imports ( ) {
80- const value = requiresJSONParse ( plainImports ) ? JSONParse ( plainImports ) : plainImports ;
81- ObjectDefineProperty ( this , 'imports' , { __proto__ : null , value } ) ;
82- return this . imports ;
83- } ,
84- } ) ,
85- ...( plainExports != null && {
86- get exports ( ) {
87- const value = requiresJSONParse ( plainExports ) ? JSONParse ( plainExports ) : plainExports ;
88- ObjectDefineProperty ( this , 'exports' , { __proto__ : null , value } ) ;
89- return this . exports ;
90- } ,
91- } ) ,
92- } ,
90+ configurable : true ,
91+ enumerable : true ,
92+ get ( ) {
93+ const value = requiresJSONParse ( plainImports ) ? JSONParse ( plainImports ) : plainImports ;
94+ ObjectDefineProperty ( data , 'imports' , { __proto__ : null , enumerable : true , value } ) ;
95+ return value ;
96+ } ,
97+ } ) ;
98+ }
99+
100+ return {
101+ data,
93102 exists : true ,
94103 path : pjsonPath ,
95104 } ;
@@ -131,43 +140,23 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
131140}
132141
133142/**
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- * @returns {undefined | string }
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.
141147 */
142- function findParentPackageJSON ( checkPath ) {
143- const enabledPermission = permission . isEnabled ( ) ;
144-
145- const rootSeparatorIndex = StringPrototypeIndexOf ( checkPath , path . sep ) ;
146- let separatorIndex ;
147-
148- do {
149- separatorIndex = StringPrototypeLastIndexOf ( checkPath , path . sep ) ;
150- checkPath = StringPrototypeSlice ( checkPath , 0 , separatorIndex ) ;
148+ const moduleToParentPackageJSONCache = new SafeMap ( ) ;
151149
152- if ( enabledPermission && ! permission . has ( 'fs.read' , checkPath + path . sep ) ) {
153- return undefined ;
154- }
155-
156- if ( StringPrototypeEndsWith ( checkPath , path . sep + 'node_modules' ) ) {
157- return undefined ;
158- }
159-
160- const maybePackageJSONPath = checkPath + path . sep + 'package.json' ;
161- const stat = internalFsBinding . internalModuleStat ( checkPath + path . sep + 'package.json' ) ;
162-
163- const packageJSONExists = stat === 0 ;
164- if ( packageJSONExists ) {
165- return maybePackageJSONPath ;
166- }
167- } while ( separatorIndex > rootSeparatorIndex ) ;
168-
169- return undefined ;
170- }
150+ /**
151+ * A cache mapping the path of a `package.json` file to its
152+ * {@link DeserializedPackageConfig deserialized representation},
153+ * as produced by {@link deserializedPackageJSONCache}. The purpose of this
154+ * cache is to ensure that we always return the same
155+ * {@link DeserializedPackageConfig} instance for a given `package.json`,
156+ * which is necessary to ensure that we don't re-parse `imports` and
157+ * `exports` redundantly.
158+ */
159+ const deserializedPackageJSONCache = new SafeMap ( ) ;
171160
172161/**
173162 * Get the nearest parent package.json file from a given path.
@@ -176,26 +165,22 @@ function findParentPackageJSON(checkPath) {
176165 * @returns {undefined | DeserializedPackageConfig }
177166 */
178167function getNearestParentPackageJSON ( checkPath ) {
179- const nearestParentPackageJSON = findParentPackageJSON ( checkPath ) ;
180-
181- if ( nearestParentPackageJSON === undefined ) {
182- return undefined ;
168+ const parentPackageJSONPath = moduleToParentPackageJSONCache . get ( checkPath ) ;
169+ if ( parentPackageJSONPath !== undefined ) {
170+ return deserializedPackageJSONCache . get ( parentPackageJSONPath ) ;
183171 }
184172
185- if ( nearestParentPackageJSONCache . has ( nearestParentPackageJSON ) ) {
186- return nearestParentPackageJSONCache . get ( nearestParentPackageJSON ) ;
187- }
173+ const result = modulesBinding . getNearestParentPackageJSON ( checkPath ) ;
174+ const packageConfig = deserializePackageJSON ( checkPath , result ) ;
188175
189- const result = modulesBinding . readPackageJSON ( nearestParentPackageJSON ) ;
176+ moduleToParentPackageJSONCache . set ( checkPath , packageConfig . path ) ;
190177
191- if ( result === undefined ) {
192- nearestParentPackageJSONCache . set ( checkPath , undefined ) ;
193- return undefined ;
178+ const maybeCachedPackageConfig = deserializedPackageJSONCache . get ( packageConfig . path ) ;
179+ if ( maybeCachedPackageConfig !== undefined ) {
180+ return maybeCachedPackageConfig ;
194181 }
195182
196- const packageConfig = deserializePackageJSON ( checkPath , result ) ;
197- nearestParentPackageJSONCache . set ( nearestParentPackageJSON , packageConfig ) ;
198-
183+ deserializedPackageJSONCache . set ( packageConfig . path , packageConfig ) ;
199184 return packageConfig ;
200185}
201186
0 commit comments