@@ -21,47 +21,107 @@ class ModuleConcatenationPlugin {
2121 parser . state . module . meta . hasEval = true ;
2222 } ) ;
2323 } ) ;
24+ const bailoutReasonMap = new Map ( ) ;
25+ function setBailoutReason ( module , reason ) {
26+ bailoutReasonMap . set ( module , reason ) ;
27+ module . optimizationBailout . push ( reason ) ;
28+ }
29+ function getBailoutReason ( module , requestShortener ) {
30+ const reason = bailoutReasonMap . get ( module ) ;
31+ if ( typeof reason === "function" ) return reason ( requestShortener ) ;
32+ return reason ;
33+ }
2434 compilation . plugin ( "optimize-chunk-modules" , ( chunks , modules ) => {
2535 chunks . forEach ( chunk => {
26- const relevantModules = chunk . mapModules ( m => m ) . filter ( module => {
36+ const relevantModules = [ ] ;
37+ const possibleInners = new Set ( ) ;
38+ for ( const module of chunk . modulesIterable ) {
39+ // Only harmony modules are valid for optimization
40+ if ( ! module . meta || ! module . meta . harmonyModule ) {
41+ continue ;
42+ }
43+
2744 // Module must not be in other chunks
2845 // TODO add an option to allow module to be in other entry points
29- if ( module . getNumberOfChunks ( ) !== 1 )
30- return false ;
46+ if ( module . getNumberOfChunks ( ) !== 1 ) {
47+ setBailoutReason ( module , "ModuleConcatenation: module is in multiple chunks" ) ;
48+ continue ;
49+ }
3150
3251 // Because of variable renaming we can't use modules with eval
33- if ( module . meta && module . meta . hasEval )
34- return false ;
52+ if ( module . meta && module . meta . hasEval ) {
53+ setBailoutReason ( module , "ModuleConcatenation: eval is used in the module" ) ;
54+ continue ;
55+ }
56+
57+ relevantModules . push ( module ) ;
3558
36- return true ;
37- } ) ;
38- const possibleInners = new Set ( relevantModules . filter ( module => {
3959 // Module must not be the entry points
40- if ( chunk . entryModule === module )
41- return false ;
60+ if ( chunk . entryModule === module ) {
61+ setBailoutReason ( module , "ModuleConcatenation (inner): module is an entrypoint" ) ;
62+ continue ;
63+ }
4264
4365 // Exports must be known (and not dynamic)
44- if ( ! Array . isArray ( module . providedExports ) )
45- return false ;
66+ if ( ! Array . isArray ( module . providedExports ) ) {
67+ setBailoutReason ( module , "ModuleConcatenation (inner): exports are not known" ) ;
68+ continue ;
69+ }
4670
4771 // Using dependency variables is not possible as this wraps the code in a function
48- if ( module . variables . length > 0 )
49- return false ;
72+ if ( module . variables . length > 0 ) {
73+ setBailoutReason ( module , "ModuleConcatenation (inner): dependency variables are used (i. e. ProvidePlugin)" ) ;
74+ continue ;
75+ }
5076
5177 // Module must only be used by Harmony Imports
52- if ( ! module . reasons . every ( reason => reason . dependency instanceof HarmonyImportDependency ) )
53- return false ;
78+ const nonHarmonyReasons = module . reasons . filter ( reason => ! ( reason . dependency instanceof HarmonyImportDependency ) ) ;
79+ if ( nonHarmonyReasons . length > 0 ) {
80+ const importingModules = new Set ( nonHarmonyReasons . map ( r => r . module ) ) ;
81+ setBailoutReason ( module , ( requestShortener ) => {
82+ const names = Array . from ( importingModules ) . map ( m => m . readableIdentifier ( requestShortener ) ) ;
83+ return `ModuleConcatenation (inner): module is used with non-harmony imports from ${ names . join ( ", " ) } ` ;
84+ } ) ;
85+ continue ;
86+ }
5487
55- return true ;
56- } ) ) ;
88+ possibleInners . add ( module ) ;
89+ }
90+ // sort by depth
91+ // modules with lower depth are more likly suited as roots
92+ // this improves performance, because modules already selected as inner are skipped
93+ relevantModules . sort ( ( a , b ) => {
94+ return a . depth - b . depth ;
95+ } ) ;
5796 const concatConfigurations = [ ] ;
97+ const usedAsInner = new Set ( ) ;
5898 for ( const currentRoot of relevantModules ) {
99+ // when used by another configuration as inner:
100+ // the other configuration is better and we can skip this one
101+ if ( usedAsInner . has ( currentRoot ) )
102+ continue ;
103+
104+ // create a configuration with the root
59105 const currentConfiguration = new ConcatConfiguration ( currentRoot ) ;
106+
107+ // cache failures to add modules
108+ const failureCache = new Map ( ) ;
109+
110+ // try to add all imports
60111 for ( const imp of this . getImports ( currentRoot ) ) {
61- this . tryToAdd ( currentConfiguration , imp , possibleInners ) ;
112+ const problem = this . tryToAdd ( currentConfiguration , imp , possibleInners , failureCache ) ;
113+ if ( problem ) {
114+ failureCache . set ( imp , problem ) ;
115+ currentConfiguration . addWarning ( imp , problem ) ;
116+ }
62117 }
63- if ( ! currentConfiguration . isEmpty ( ) )
118+ if ( ! currentConfiguration . isEmpty ( ) ) {
64119 concatConfigurations . push ( currentConfiguration ) ;
120+ for ( const module of currentConfiguration . modules ) {
121+ if ( module !== currentConfiguration . rootModule )
122+ usedAsInner . add ( module ) ;
123+ }
124+ }
65125 }
66126 // HACK: Sort configurations by length and start with the longest one
67127 // to get the biggers groups possible. Used modules are marked with usedModules
@@ -77,6 +137,16 @@ class ModuleConcatenationPlugin {
77137 const orderedModules = new Set ( ) ;
78138 this . addInOrder ( concatConfiguration . rootModule , concatConfiguration . modules , orderedModules ) ;
79139 const newModule = new ConcatenatedModule ( concatConfiguration . rootModule , Array . from ( orderedModules ) ) ;
140+ for ( const warning of concatConfiguration . warnings ) {
141+ newModule . optimizationBailout . push ( ( requestShortener ) => {
142+ const reason = getBailoutReason ( warning [ 0 ] , requestShortener ) ;
143+ const reasonPrefix = reason ? `: ${ reason } ` : "" ;
144+ if ( warning [ 0 ] === warning [ 1 ] )
145+ return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
146+ else
147+ return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } because of ${ warning [ 1 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
148+ } ) ;
149+ }
80150 for ( const m of orderedModules ) {
81151 usedModules . add ( m ) ;
82152 chunk . removeModule ( m ) ;
@@ -85,10 +155,7 @@ class ModuleConcatenationPlugin {
85155 compilation . modules . push ( newModule ) ;
86156 if ( chunk . entryModule === concatConfiguration . rootModule )
87157 chunk . entryModule = newModule ;
88- concatConfiguration . rootModule . reasons . forEach ( reason => {
89- if ( ! concatConfiguration . modules . has ( reason . module ) )
90- reason . dependency . module = newModule ;
91- } ) ;
158+ newModule . reasons . forEach ( reason => reason . dependency . module = newModule ) ;
92159 }
93160 compilation . modules = compilation . modules . filter ( m => ! usedModules . has ( m ) ) ;
94161 } ) ;
@@ -117,19 +184,20 @@ class ModuleConcatenationPlugin {
117184 ) ) ;
118185 }
119186
120- tryToAdd ( config , module , possibleModules ) {
121- // console.log("tryToAdd", module.debugId, module.resource);
187+ tryToAdd ( config , module , possibleModules , failureCache ) {
188+ const cacheEntry = failureCache . get ( module ) ;
189+ if ( cacheEntry ) {
190+ return cacheEntry ;
191+ }
122192
123193 // Already added?
124194 if ( config . has ( module ) ) {
125- // console.log("already added");
126- return true ;
195+ return null ;
127196 }
128197
129198 // Not possible to add?
130199 if ( ! possibleModules . has ( module ) ) {
131- // console.log("not possible");
132- return false ;
200+ return module ;
133201 }
134202
135203 // Clone config to make experimental changes
@@ -139,25 +207,25 @@ class ModuleConcatenationPlugin {
139207 testConfig . add ( module ) ;
140208
141209 // Every module which depends on the added module must be in the configuration too.
142- // console.log("reasons start");
143210 for ( const reason of module . reasons ) {
144- if ( ! this . tryToAdd ( testConfig , reason . module , possibleModules ) ) {
145- // console.log("reason failed");
146- return false ;
211+ const problem = this . tryToAdd ( testConfig , reason . module , possibleModules , failureCache ) ;
212+ if ( problem ) {
213+ failureCache . set ( module , problem ) ; // cache failures for performance
214+ return problem ;
147215 }
148216 }
149- // console.log("reasons end");
150217
151218 // Eagerly try to add imports too if possible
152- // console.log("imports start");
153- for ( const imp of this . getImports ( module ) )
154- this . tryToAdd ( testConfig , imp , possibleModules ) ;
155- // console.log("imports end");
219+ for ( const imp of this . getImports ( module ) ) {
220+ const problem = this . tryToAdd ( testConfig , imp , possibleModules , failureCache ) ;
221+ if ( problem ) {
222+ config . addWarning ( module , problem ) ;
223+ }
224+ }
156225
157- // console.log("commit");
158226 // Commit experimental changes
159227 config . set ( testConfig ) ;
160- return true ;
228+ return null ;
161229 }
162230
163231 addInOrder ( module , unorderedSet , orderedSet ) {
@@ -175,6 +243,7 @@ class ConcatConfiguration {
175243 constructor ( rootModule ) {
176244 this . rootModule = rootModule ;
177245 this . modules = new Set ( [ rootModule ] ) ;
246+ this . warnings = new Map ( ) ;
178247 }
179248
180249 add ( module ) {
@@ -189,16 +258,23 @@ class ConcatConfiguration {
189258 return this . modules . size === 1 ;
190259 }
191260
261+ addWarning ( module , problem ) {
262+ this . warnings . set ( module , problem ) ;
263+ }
264+
192265 clone ( ) {
193266 const clone = new ConcatConfiguration ( this . rootModule ) ;
194267 for ( const module of this . modules )
195268 clone . add ( module ) ;
269+ for ( const pair of this . warnings )
270+ clone . addWarning ( pair [ 0 ] , pair [ 1 ] ) ;
196271 return clone ;
197272 }
198273
199274 set ( config ) {
200275 this . rootModule = config . rootModule ;
201276 this . modules = new Set ( config . modules ) ;
277+ this . warnings = new Map ( config . warnings ) ;
202278 }
203279}
204280
0 commit comments