@@ -19,6 +19,7 @@ const formatLocation = require("../util/formatLocation");
1919const { dirname, join, readJson, relative } = require ( "../util/fs" ) ;
2020const { getGlobToRegExpSource } = require ( "../util/globUtils" ) ;
2121const {
22+ NODE_MODULES_REGEXP ,
2223 WINDOWS_PATH_SEPARATOR_REGEXP ,
2324 relativePathToRequest
2425} = require ( "../util/identifier" ) ;
@@ -43,6 +44,8 @@ const { CompilerHintNotationRegExp } = require("../util/magicComment");
4344/** @import ModuleGraphConnection from "../ModuleGraphConnection" */
4445/** @import { ExportInfo, TargetItemWithConnection } from "../ExportsInfo" */
4546/** @import JavascriptParser, { Range } from "../javascript/JavascriptParser" */
47+ /** @import { InputFileSystem } from "../util/fs" */
48+ /** @import { CreateData, ResolveData } from "../NormalModuleFactory" */
4649/**
4750 * @import {
4851 * JavascriptParserOptions
@@ -57,9 +60,7 @@ const { CompilerHintNotationRegExp } = require("../util/magicComment");
5760 * @property {boolean } checked if the export is conditional
5861 */
5962
60- /** @typedef {import("../util/fs").InputFileSystem } InputFileSystem */
61- /** @typedef {import("../util/fs").JsonObject } JsonObject */
62- /** @typedef {import("../NormalModuleFactory").CreateData } CreateData */
63+ /** @typedef {NonNullable<CreateData["resourceResolveData"]> } ResourceResolveData */
6364
6465/** @typedef {string | boolean | string[] | undefined } SideEffectsFlagValue */
6566
@@ -72,14 +73,21 @@ const { CompilerHintNotationRegExp } = require("../util/magicComment");
7273 */
7374
7475/**
75- * Owning-package sideEffects resolution cached per descriptionFileRoot.
76- * `null` means the walk finished with no applicable flag.
7776 * @typedef {object } OwningSideEffectsFlag
7877 * @property {SideEffectsFlagValue } sideEffects package.json sideEffects value
7978 * @property {string } packageRoot directory of the declaring package.json
8079 */
8180
82- /** @typedef {Map<string, OwningSideEffectsFlag | null> } OwningSideEffectsFlagCache */
81+ /**
82+ * A finished ancestor walk, kept per descriptionFileRoot. The package.json
83+ * paths it consulted are replayed as dependencies for every module reusing it.
84+ * @typedef {object } OwningSideEffectsFlagWalk
85+ * @property {OwningSideEffectsFlag | null } owning declaring package, `null` when the walk found none
86+ * @property {string[] } read package.json files the walk read
87+ * @property {string[] } missing package.json files the walk did not find
88+ */
89+
90+ /** @typedef {Map<string, OwningSideEffectsFlagWalk> } OwningSideEffectsFlagCache */
8391
8492/**
8593 * Whether a resolved re-export target is reached through an `import defer` edge.
@@ -98,30 +106,21 @@ const isDeferredTargetConnection = (connection) =>
98106/** @type {WeakMap<Compiler, CacheItem> } */
99107const globToRegexpCache = new WeakMap ( ) ;
100108
101- /** @type {WeakMap<EXPECTED_OBJECT , OwningSideEffectsFlagCache> } */
109+ /** @type {WeakMap<Compiler , OwningSideEffectsFlagCache> } */
102110const owningSideEffectsFlagCache = new WeakMap ( ) ;
103111
104112/** @type {WeakMap<Partial<CreateData>, ResolvedSideEffectsFlag> } */
105113const resolvedSideEffectsFlags = new WeakMap ( ) ;
106114
107115/**
108- * Ancestor package.json read failures that must not fail unrelated modules.
116+ * Whether a failed ancestor package.json read means nothing is there, as
117+ * opposed to something being there that cannot be used.
109118 * @param {NodeJS.ErrnoException | SyntaxError | Error } err error from readJson
110- * @returns {boolean } true when the walk should skip this directory
119+ * @returns {boolean } true when no file was found at that path
111120 */
112- const isSkippablePackageJsonError = ( err ) => {
113- if ( err instanceof SyntaxError ) return true ;
114- if ( ! ( "code" in err ) ) return false ;
115- switch ( err . code ) {
116- case "ENOENT" :
117- case "ENOTDIR" :
118- case "EISDIR" :
119- case "ENAMETOOLONG" :
120- return true ;
121- default :
122- return false ;
123- }
124- } ;
121+ const isMissingPackageJsonError = ( err ) =>
122+ "code" in err && err . code === "ENOENT" ;
123+
125124/**
126125 * Returns a regular expression.
127126 * @param {string } glob the pattern
@@ -166,26 +165,33 @@ const notSideEffectsTag = Symbol("NoSideEffects");
166165const RETURNS_FALSE = ( ) => false ;
167166
168167/**
169- * Resolve sideEffects from the owning package, inheriting past type-only nested
170- * package.json under node_modules. Globs are matched relative to that package root.
168+ * Resolve sideEffects from the package that declares it, inheriting past a nested
169+ * package.json that declares none. Globs are rebased on the declaring package root,
170+ * and each package.json the walk consults becomes a dependency of the resolve.
171171 * @param {InputFileSystem } fs file system
172+ * @param {ResolveData } resolveData resolve data
172173 * @param {string } descriptionFileRoot root directory of the nearest description file
173- * @param {JsonObject } descriptionFileData nearest description file data
174- * @param {string } relativePath path relative to descriptionFileRoot
175174 * @param {OwningSideEffectsFlagCache } owningCache walk cache keyed by descriptionFileRoot
176- * @param {(err?: null | Error, result?: ResolvedSideEffectsFlag) => void } callback callback
175+ * @param {(result?: ResolvedSideEffectsFlag) => void } callback callback
177176 * @returns {void }
178177 */
179178const resolveSideEffectsFlag = (
180179 fs ,
180+ resolveData ,
181181 descriptionFileRoot ,
182- descriptionFileData ,
183- relativePath ,
184182 owningCache ,
185183 callback
186184) => {
185+ const resolveInfo = /** @type {ResourceResolveData } */ (
186+ resolveData . createData . resourceResolveData
187+ ) ;
188+ const descriptionFileData =
189+ /** @type {NonNullable<ResourceResolveData["descriptionFileData"]> } */ (
190+ resolveInfo . descriptionFileData
191+ ) ;
192+ const relativePath = /** @type {string } */ ( resolveInfo . relativePath ) ;
187193 if ( descriptionFileData . sideEffects !== undefined ) {
188- return callback ( null , {
194+ return callback ( {
189195 sideEffects : /** @type {SideEffectsFlagValue } */ (
190196 descriptionFileData . sideEffects
191197 ) ,
@@ -196,82 +202,99 @@ const resolveSideEffectsFlag = (
196202 if ( descriptionFileData . name !== undefined ) {
197203 return callback ( ) ;
198204 }
199- // Only inherit inside node_modules; app-local nests must keep shadowing parents .
200- const normalizedRoot = descriptionFileRoot . replace (
201- WINDOWS_PATH_SEPARATOR_REGEXP ,
202- "/"
203- ) ;
204- if ( ! / (?: ^ | \/ ) n o d e _ m o d u l e s (?: \/ | $ ) / . test ( normalizedRoot ) ) {
205+ // Only inherit for a file living under node_modules; app-local nests keep shadowing.
206+ // Read the resolved path so a symlinked package flags all of its files alike.
207+ if (
208+ typeof resolveInfo . path !== "string" ||
209+ ! NODE_MODULES_REGEXP . test ( resolveInfo . path )
210+ ) {
205211 return callback ( ) ;
206212 }
207213
214+ // stays in descriptionFileRoot's space, which is the space packageRoot is found in
208215 const resourcePath = join ( fs , descriptionFileRoot , relativePath ) ;
209216
210217 /**
211- * @param {OwningSideEffectsFlag | null } owning owning package flag or null
218+ * @param {OwningSideEffectsFlagWalk } walk finished walk
212219 * @returns {void }
213220 */
214- const finishFromOwning = ( owning ) => {
221+ const finishFromWalk = ( walk ) => {
222+ for ( const file of walk . read ) resolveData . fileDependencies . add ( file ) ;
223+ for ( const file of walk . missing ) resolveData . missingDependencies . add ( file ) ;
224+ const owning = walk . owning ;
215225 if ( owning === null ) return callback ( ) ;
216226 const ancestorRelative = relative (
217227 fs ,
218228 owning . packageRoot ,
219229 resourcePath
220230 ) . replace ( WINDOWS_PATH_SEPARATOR_REGEXP , "/" ) ;
221- return callback ( null , {
231+ return callback ( {
222232 sideEffects : owning . sideEffects ,
223233 relativePath : relativePathToRequest ( ancestorRelative )
224234 } ) ;
225235 } ;
226236
227237 const cached = owningCache . get ( descriptionFileRoot ) ;
228238 if ( cached !== undefined ) {
229- return finishFromOwning ( cached ) ;
239+ return finishFromWalk ( cached ) ;
230240 }
231241
242+ /** @type {string[] } */
243+ const read = [ ] ;
244+ /** @type {string[] } */
245+ const missing = [ ] ;
246+
247+ /**
248+ * @param {OwningSideEffectsFlag | null } owning declaring package or null
249+ * @returns {void }
250+ */
251+ const done = ( owning ) => {
252+ /** @type {OwningSideEffectsFlagWalk } */
253+ const walk = { owning, read, missing } ;
254+ owningCache . set ( descriptionFileRoot , walk ) ;
255+ return finishFromWalk ( walk ) ;
256+ } ;
257+
232258 let dir = dirname ( fs , descriptionFileRoot ) ;
233259
234260 const next = ( ) => {
235261 const parent = dirname ( fs , dir ) ;
236- if ( ! parent || parent === dir ) {
237- owningCache . set ( descriptionFileRoot , null ) ;
238- return callback ( ) ;
239- }
262+ if ( ! parent || parent === dir ) return done ( null ) ;
240263 const baseName = relative ( fs , parent , dir ) . replace (
241264 WINDOWS_PATH_SEPARATOR_REGEXP ,
242265 "/"
243266 ) ;
244267 // Do not inherit an application's sideEffects across an install boundary.
245- if ( baseName === "node_modules" ) {
246- owningCache . set ( descriptionFileRoot , null ) ;
247- return callback ( ) ;
248- }
268+ if ( baseName === "node_modules" ) return done ( null ) ;
249269
250- readJson ( fs , join ( fs , dir , "package.json" ) , ( err , data ) => {
270+ const packageJsonPath = join ( fs , dir , "package.json" ) ;
271+
272+ readJson ( fs , packageJsonPath , ( err , data ) => {
251273 if ( err ) {
252- if ( isSkippablePackageJsonError ( err ) ) {
253- dir = parent ;
254- return next ( ) ;
274+ if ( ! isMissingPackageJsonError ( err ) ) {
275+ // it is there but unreadable, so what it declares is unknown:
276+ // decline rather than let an outer flag speak for it
277+ read . push ( packageJsonPath ) ;
278+ return done ( null ) ;
255279 }
256- return callback ( err ) ;
280+ missing . push ( packageJsonPath ) ;
281+ dir = parent ;
282+ return next ( ) ;
257283 }
284+ read . push ( packageJsonPath ) ;
258285 if ( ! data || typeof data !== "object" || Array . isArray ( data ) ) {
259286 dir = parent ;
260287 return next ( ) ;
261288 }
262289 if ( data . sideEffects !== undefined ) {
263- /** @type {OwningSideEffectsFlag } */
264- const owning = {
290+ return done ( {
265291 sideEffects : /** @type {SideEffectsFlagValue } */ ( data . sideEffects ) ,
266292 packageRoot : dir
267- } ;
268- owningCache . set ( descriptionFileRoot , owning ) ;
269- return finishFromOwning ( owning ) ;
270- }
271- if ( data . name !== undefined ) {
272- owningCache . set ( descriptionFileRoot , null ) ;
273- return callback ( ) ;
293+ } ) ;
274294 }
295+ // An unnamed manifest deeper in the package keeps the walk going; a named one
296+ // is a package of its own, and its silence is its answer.
297+ if ( data . name !== undefined ) return done ( null ) ;
275298 dir = parent ;
276299 next ( ) ;
277300 } ) ;
@@ -359,12 +382,10 @@ class SideEffectsFlagPlugin {
359382 if ( descriptionFileRoot === undefined ) return callback ( ) ;
360383 resolveSideEffectsFlag (
361384 inputFileSystem ,
385+ resolveData ,
362386 descriptionFileRoot ,
363- resolveInfo . descriptionFileData ,
364- resolveInfo . relativePath ,
365387 owningCache ,
366- ( err , resolved ) => {
367- if ( err ) return callback ( err ) ;
388+ ( resolved ) => {
368389 if ( resolved !== undefined ) {
369390 resolvedSideEffectsFlags . set ( createData , resolved ) ;
370391 }
0 commit comments