@@ -61,6 +61,7 @@ const parseJson = require("./util/parseJson");
6161/** @typedef {import("../declarations/WebpackOptions").ResolveOptions } ResolveOptions */
6262/** @typedef {import("../declarations/WebpackOptions").NoParse } NoParse */
6363/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults } WebpackOptions */
64+ /** @typedef {import("./Dependency") } Dependency */
6465/** @typedef {import("./Dependency").UpdateHashContext } UpdateHashContext */
6566/** @typedef {import("./Generator") } Generator */
6667/** @typedef {import("./Generator").GenerateErrorFn } GenerateErrorFn */
@@ -85,6 +86,7 @@ const parseJson = require("./util/parseJson");
8586/** @typedef {import("./Module").UnsafeCacheData } UnsafeCacheData */
8687/** @typedef {import("./ModuleGraph") } ModuleGraph */
8788/** @typedef {import("./ModuleGraphConnection").ConnectionState } ConnectionState */
89+ /** @typedef {Iterator<SideEffectsWalk, ConnectionState, ConnectionState> } SideEffectsWalk */
8890/** @typedef {import("./NormalModuleFactory") } NormalModuleFactory */
8991/** @typedef {import("./NormalModuleFactory").NormalModuleTypes } NormalModuleTypes */
9092/** @typedef {import("./NormalModuleFactory").ResourceSchemeData } ResourceSchemeData */
@@ -126,6 +128,87 @@ const getExtractSourceMap = memoize(() => require("./util/extractSourceMap"));
126128
127129const getValidate = memoize ( ( ) => require ( "schema-utils" ) . validate ) ;
128130
131+ const getHarmonyImportSideEffectDependency = memoize ( ( ) =>
132+ require ( "./dependencies/HarmonyImportSideEffectDependency" )
133+ ) ;
134+
135+ /**
136+ * @param {NormalModule } mod the module
137+ * @param {ModuleGraph } moduleGraph the module graph
138+ * @param {Dependency } dep the dep that triggered the bailout
139+ */
140+ const recordSideEffectsBailout = ( mod , moduleGraph , dep ) => {
141+ if ( mod . _addedSideEffectsBailout === undefined ) {
142+ mod . _addedSideEffectsBailout = new WeakSet ( ) ;
143+ } else if ( mod . _addedSideEffectsBailout . has ( moduleGraph ) ) {
144+ return ;
145+ }
146+ mod . _addedSideEffectsBailout . add ( moduleGraph ) ;
147+ moduleGraph
148+ . getOptimizationBailout ( mod )
149+ . push (
150+ ( ) =>
151+ `Dependency (${ dep . type } ) with side effects at ${ formatLocation ( dep . loc ) } `
152+ ) ;
153+ } ;
154+
155+ /**
156+ * Generator form of `getSideEffectsConnectionState` — descends through
157+ * `HarmonyImportSideEffectDependency` via `yield` so the trampoline in
158+ * `getSideEffectsConnectionState` can drive the walk iteratively (#20986).
159+ * @param {NormalModule } mod the module being evaluated
160+ * @param {ModuleGraph } moduleGraph the module graph
161+ * @returns {SideEffectsWalk } the generator
162+ */
163+ function * walkSideEffects ( mod , moduleGraph ) {
164+ if ( mod . factoryMeta !== undefined ) {
165+ if ( mod . factoryMeta . sideEffectFree ) return false ;
166+ if ( mod . factoryMeta . sideEffectFree === false ) return true ;
167+ }
168+ if ( ! ( mod . buildMeta !== undefined && mod . buildMeta . sideEffectFree ) ) {
169+ return true ;
170+ }
171+ if ( mod . _isEvaluatingSideEffects ) {
172+ return ModuleGraphConnection . CIRCULAR_CONNECTION ;
173+ }
174+
175+ const SideEffectDep = getHarmonyImportSideEffectDependency ( ) ;
176+ mod . _isEvaluatingSideEffects = true ;
177+ /** @type {ConnectionState } */
178+ let current = false ;
179+
180+ for ( const dep of mod . dependencies ) {
181+ /** @type {ConnectionState } */
182+ let state ;
183+ if ( dep instanceof SideEffectDep ) {
184+ const refModule = moduleGraph . getModule ( dep ) ;
185+ if ( ! refModule ) {
186+ state = true ;
187+ } else if ( refModule instanceof NormalModule ) {
188+ state = yield walkSideEffects ( refModule , moduleGraph ) ;
189+ } else {
190+ state = refModule . getSideEffectsConnectionState ( moduleGraph ) ;
191+ }
192+ } else {
193+ state = dep . getModuleEvaluationSideEffectsState ( moduleGraph ) ;
194+ }
195+
196+ if ( state === true ) {
197+ recordSideEffectsBailout ( mod , moduleGraph , dep ) ;
198+ mod . _isEvaluatingSideEffects = false ;
199+ return true ;
200+ }
201+ if ( state !== ModuleGraphConnection . CIRCULAR_CONNECTION ) {
202+ current = ModuleGraphConnection . addConnectionStates ( current , state ) ;
203+ }
204+ }
205+
206+ mod . _isEvaluatingSideEffects = false ;
207+ // When caching is implemented here, make sure to not cache when
208+ // at least one circular connection was folded into `current`.
209+ return current ;
210+ }
211+
129212const ABSOLUTE_PATH_REGEX = / ^ (?: [ a - z ] : \\ | \\ \\ | \/ ) / i;
130213
131214/**
@@ -405,12 +488,10 @@ class NormalModule extends Module {
405488 */
406489 this . _forceBuild = true ;
407490 /**
408- * @private
409491 * @type {boolean }
410492 */
411493 this . _isEvaluatingSideEffects = false ;
412494 /**
413- * @private
414495 * @type {WeakSet<ModuleGraph> | undefined }
415496 */
416497 this . _addedSideEffectsBailout = undefined ;
@@ -1453,47 +1534,21 @@ class NormalModule extends Module {
14531534 * @returns {ConnectionState } how this module should be connected to referencing modules when consumed for side-effects only
14541535 */
14551536 getSideEffectsConnectionState ( moduleGraph ) {
1456- if ( this . factoryMeta !== undefined ) {
1457- if ( this . factoryMeta . sideEffectFree ) return false ;
1458- if ( this . factoryMeta . sideEffectFree === false ) return true ;
1459- }
1460- if ( this . buildMeta !== undefined && this . buildMeta . sideEffectFree ) {
1461- if ( this . _isEvaluatingSideEffects ) {
1462- return ModuleGraphConnection . CIRCULAR_CONNECTION ;
1463- }
1464- this . _isEvaluatingSideEffects = true ;
1465- /** @type {ConnectionState } */
1466- let current = false ;
1467- for ( const dep of this . dependencies ) {
1468- const state = dep . getModuleEvaluationSideEffectsState ( moduleGraph ) ;
1469- if ( state === true ) {
1470- if (
1471- this . _addedSideEffectsBailout === undefined
1472- ? ( ( this . _addedSideEffectsBailout = new WeakSet ( ) ) , true )
1473- : ! this . _addedSideEffectsBailout . has ( moduleGraph )
1474- ) {
1475- this . _addedSideEffectsBailout . add ( moduleGraph ) ;
1476- moduleGraph
1477- . getOptimizationBailout ( this )
1478- . push (
1479- ( ) =>
1480- `Dependency (${
1481- dep . type
1482- } ) with side effects at ${ formatLocation ( dep . loc ) } `
1483- ) ;
1484- }
1485- this . _isEvaluatingSideEffects = false ;
1486- return true ;
1487- } else if ( state !== ModuleGraphConnection . CIRCULAR_CONNECTION ) {
1488- current = ModuleGraphConnection . addConnectionStates ( current , state ) ;
1489- }
1537+ // Trampoline `walkSideEffects` so the descent doesn't consume the
1538+ // call stack (#20986).
1539+ const stack = [ walkSideEffects ( this , moduleGraph ) ] ;
1540+ /** @type {ConnectionState } */
1541+ let r = false ;
1542+ while ( stack . length > 0 ) {
1543+ const step = stack [ stack . length - 1 ] . next ( r ) ;
1544+ if ( step . done ) {
1545+ stack . pop ( ) ;
1546+ r = step . value ;
1547+ } else {
1548+ stack . push ( step . value ) ;
14901549 }
1491- this . _isEvaluatingSideEffects = false ;
1492- // When caching is implemented here, make sure to not cache when
1493- // at least one circular connection was in the loop above
1494- return current ;
14951550 }
1496- return true ;
1551+ return r ;
14971552 }
14981553
14991554 /**
0 commit comments