@@ -23,9 +23,9 @@ class ModuleConcatenationPlugin {
2323 } ) ;
2424 const bailoutReasonMap = new Map ( ) ;
2525
26- function setBailoutReason ( module , reason ) {
26+ function setBailoutReason ( module , prefix , reason ) {
2727 bailoutReasonMap . set ( module , reason ) ;
28- module . optimizationBailout . push ( reason ) ;
28+ module . optimizationBailout . push ( typeof reason === "function" ? ( rs ) => ` ${ prefix } : ${ reason ( rs ) } ` : ` ${ prefix } : ${ reason } ` ) ;
2929 }
3030
3131 function getBailoutReason ( module , requestShortener ) {
@@ -35,141 +35,136 @@ class ModuleConcatenationPlugin {
3535 }
3636
3737 compilation . plugin ( "optimize-chunk-modules" , ( chunks , modules ) => {
38- chunks . forEach ( chunk => {
39- const relevantModules = [ ] ;
40- const possibleInners = new Set ( ) ;
41- for ( const module of chunk . modulesIterable ) {
42- // Only harmony modules are valid for optimization
43- if ( ! module . meta || ! module . meta . harmonyModule ) {
44- continue ;
45- }
38+ const relevantModules = [ ] ;
39+ const possibleInners = new Set ( ) ;
40+ for ( const module of modules ) {
41+ // Only harmony modules are valid for optimization
42+ if ( ! module . meta || ! module . meta . harmonyModule ) {
43+ continue ;
44+ }
4645
47- // Module must not be in other chunks
48- // TODO add an option to allow module to be in other entry points
49- if ( module . getNumberOfChunks ( ) !== 1 ) {
50- setBailoutReason ( module , "ModuleConcatenation: module is in multiple chunks" ) ;
51- continue ;
52- }
46+ // Because of variable renaming we can't use modules with eval
47+ if ( module . meta && module . meta . hasEval ) {
48+ setBailoutReason ( module , "ModuleConcatenation" , "eval is used in the module" ) ;
49+ continue ;
50+ }
5351
54- // Because of variable renaming we can't use modules with eval
55- if ( module . meta && module . meta . hasEval ) {
56- setBailoutReason ( module , "ModuleConcatenation: eval is used in the module" ) ;
57- continue ;
58- }
52+ relevantModules . push ( module ) ;
5953
60- relevantModules . push ( module ) ;
54+ // Module must not be the entry points
55+ if ( module . getChunks ( ) . some ( chunk => chunk . entryModule === module ) ) {
56+ setBailoutReason ( module , "ModuleConcatenation (inner)" , "module is an entrypoint" ) ;
57+ continue ;
58+ }
6159
62- // Module must not be the entry points
63- if ( chunk . entryModule === module ) {
64- setBailoutReason ( module , "ModuleConcatenation (inner): module is an entrypoint " ) ;
65- continue ;
66- }
60+ // Exports must be known (and not dynamic)
61+ if ( ! Array . isArray ( module . providedExports ) ) {
62+ setBailoutReason ( module , "ModuleConcatenation (inner)" , "exports are not known ") ;
63+ continue ;
64+ }
6765
68- // Exports must be known (and not dynamic)
69- if ( ! Array . isArray ( module . providedExports ) ) {
70- setBailoutReason ( module , "ModuleConcatenation (inner): exports are not known " ) ;
71- continue ;
72- }
66+ // Using dependency variables is not possible as this wraps the code in a function
67+ if ( module . variables . length > 0 ) {
68+ setBailoutReason ( module , "ModuleConcatenation (inner)" , "dependency variables are used (i. e. ProvidePlugin) ") ;
69+ continue ;
70+ }
7371
74- // Using dependency variables is not possible as this wraps the code in a function
75- if ( module . variables . length > 0 ) {
76- setBailoutReason ( module , "ModuleConcatenation (inner): dependency variables are used (i. e. ProvidePlugin)" ) ;
77- continue ;
78- }
72+ // Module must only be used by Harmony Imports
73+ const nonHarmonyReasons = module . reasons . filter ( reason => ! ( reason . dependency instanceof HarmonyImportDependency ) ) ;
74+ if ( nonHarmonyReasons . length > 0 ) {
75+ const importingModules = new Set ( nonHarmonyReasons . map ( r => r . module ) ) ;
76+ setBailoutReason ( module , "ModuleConcatenation (inner)" , ( requestShortener ) => {
77+ const names = Array . from ( importingModules ) . map ( m => m . readableIdentifier ( requestShortener ) ) . sort ( ) ;
78+ return `module is used with non-harmony imports from ${ names . join ( ", " ) } ` ;
79+ } ) ;
80+ continue ;
81+ }
7982
80- // Module must only be used by Harmony Imports
81- const nonHarmonyReasons = module . reasons . filter ( reason => ! ( reason . dependency instanceof HarmonyImportDependency ) ) ;
82- if ( nonHarmonyReasons . length > 0 ) {
83- const importingModules = new Set ( nonHarmonyReasons . map ( r => r . module ) ) ;
84- setBailoutReason ( module , ( requestShortener ) => {
85- const names = Array . from ( importingModules ) . map ( m => m . readableIdentifier ( requestShortener ) ) ;
86- return `ModuleConcatenation (inner): module is used with non-harmony imports from ${ names . join ( ", " ) } ` ;
87- } ) ;
88- continue ;
83+ possibleInners . add ( module ) ;
84+ }
85+ // sort by depth
86+ // modules with lower depth are more likly suited as roots
87+ // this improves performance, because modules already selected as inner are skipped
88+ relevantModules . sort ( ( a , b ) => {
89+ return a . depth - b . depth ;
90+ } ) ;
91+ const concatConfigurations = [ ] ;
92+ const usedAsInner = new Set ( ) ;
93+ for ( const currentRoot of relevantModules ) {
94+ // when used by another configuration as inner:
95+ // the other configuration is better and we can skip this one
96+ if ( usedAsInner . has ( currentRoot ) )
97+ continue ;
98+
99+ // create a configuration with the root
100+ const currentConfiguration = new ConcatConfiguration ( currentRoot ) ;
101+
102+ // cache failures to add modules
103+ const failureCache = new Map ( ) ;
104+
105+ // try to add all imports
106+ for ( const imp of this . getImports ( currentRoot ) ) {
107+ const problem = this . tryToAdd ( currentConfiguration , imp , possibleInners , failureCache ) ;
108+ if ( problem ) {
109+ failureCache . set ( imp , problem ) ;
110+ currentConfiguration . addWarning ( imp , problem ) ;
89111 }
90-
91- possibleInners . add ( module ) ;
92112 }
93- // sort by depth
94- // modules with lower depth are more likly suited as roots
95- // this improves performance, because modules already selected as inner are skipped
96- relevantModules . sort ( ( a , b ) => {
97- return a . depth - b . depth ;
98- } ) ;
99- const concatConfigurations = [ ] ;
100- const usedAsInner = new Set ( ) ;
101- for ( const currentRoot of relevantModules ) {
102- // when used by another configuration as inner:
103- // the other configuration is better and we can skip this one
104- if ( usedAsInner . has ( currentRoot ) )
105- continue ;
106-
107- // create a configuration with the root
108- const currentConfiguration = new ConcatConfiguration ( currentRoot ) ;
109-
110- // cache failures to add modules
111- const failureCache = new Map ( ) ;
112-
113- // try to add all imports
114- for ( const imp of this . getImports ( currentRoot ) ) {
115- const problem = this . tryToAdd ( currentConfiguration , imp , possibleInners , failureCache ) ;
116- if ( problem ) {
117- failureCache . set ( imp , problem ) ;
118- currentConfiguration . addWarning ( imp , problem ) ;
119- }
120- }
121- if ( ! currentConfiguration . isEmpty ( ) ) {
122- concatConfigurations . push ( currentConfiguration ) ;
123- for ( const module of currentConfiguration . modules ) {
124- if ( module !== currentConfiguration . rootModule )
125- usedAsInner . add ( module ) ;
126- }
113+ if ( ! currentConfiguration . isEmpty ( ) ) {
114+ concatConfigurations . push ( currentConfiguration ) ;
115+ for ( const module of currentConfiguration . modules ) {
116+ if ( module !== currentConfiguration . rootModule )
117+ usedAsInner . add ( module ) ;
127118 }
128119 }
129- // HACK: Sort configurations by length and start with the longest one
130- // to get the biggers groups possible. Used modules are marked with usedModules
131- // TODO: Allow to reuse existing configuration while trying to add dependencies.
132- // This would improve performance. O(n^2) -> O(n)
133- concatConfigurations . sort ( ( a , b ) => {
134- return b . modules . size - a . modules . size ;
135- } ) ;
136- const usedModules = new Set ( ) ;
137- for ( const concatConfiguration of concatConfigurations ) {
138- if ( usedModules . has ( concatConfiguration . rootModule ) )
139- continue ;
140- const orderedModules = new Set ( ) ;
141- this . addInOrder ( concatConfiguration . rootModule , concatConfiguration . modules , orderedModules ) ;
142- const newModule = new ConcatenatedModule ( concatConfiguration . rootModule , Array . from ( orderedModules ) ) ;
143- for ( const warning of concatConfiguration . warnings ) {
144- newModule . optimizationBailout . push ( ( requestShortener ) => {
145- const reason = getBailoutReason ( warning [ 0 ] , requestShortener ) ;
146- const reasonPrefix = reason ? `: ${ reason } ` : "" ;
147- if ( warning [ 0 ] === warning [ 1 ] )
148- return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
149- else
150- return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } because of ${ warning [ 1 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
151- } ) ;
152- }
153- for ( const m of orderedModules ) {
154- usedModules . add ( m ) ;
155- chunk . removeModule ( m ) ;
156- }
120+ }
121+ // HACK: Sort configurations by length and start with the longest one
122+ // to get the biggers groups possible. Used modules are marked with usedModules
123+ // TODO: Allow to reuse existing configuration while trying to add dependencies.
124+ // This would improve performance. O(n^2) -> O(n)
125+ concatConfigurations . sort ( ( a , b ) => {
126+ return b . modules . size - a . modules . size ;
127+ } ) ;
128+ const usedModules = new Set ( ) ;
129+ for ( const concatConfiguration of concatConfigurations ) {
130+ if ( usedModules . has ( concatConfiguration . rootModule ) )
131+ continue ;
132+ const orderedModules = new Set ( ) ;
133+ this . addInOrder ( concatConfiguration . rootModule , concatConfiguration . modules , orderedModules ) ;
134+ const newModule = new ConcatenatedModule ( concatConfiguration . rootModule , Array . from ( orderedModules ) ) ;
135+ concatConfiguration . sortWarnings ( ) ;
136+ for ( const warning of concatConfiguration . warnings ) {
137+ newModule . optimizationBailout . push ( ( requestShortener ) => {
138+ const reason = getBailoutReason ( warning [ 0 ] , requestShortener ) ;
139+ const reasonPrefix = reason ? `: ${ reason } ` : "" ;
140+ if ( warning [ 0 ] === warning [ 1 ] )
141+ return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
142+ else
143+ return `ModuleConcatenation: Cannot concat with ${ warning [ 0 ] . readableIdentifier ( requestShortener ) } because of ${ warning [ 1 ] . readableIdentifier ( requestShortener ) } ${ reasonPrefix } ` ;
144+ } ) ;
145+ }
146+ const chunks = concatConfiguration . rootModule . getChunks ( ) ;
147+ for ( const m of orderedModules ) {
148+ usedModules . add ( m ) ;
149+ chunks . forEach ( chunk => chunk . removeModule ( m ) ) ;
150+ }
151+ chunks . forEach ( chunk => {
157152 chunk . addModule ( newModule ) ;
158- compilation . modules . push ( newModule ) ;
159153 if ( chunk . entryModule === concatConfiguration . rootModule )
160154 chunk . entryModule = newModule ;
161- newModule . reasons . forEach ( reason => reason . dependency . module = newModule ) ;
162- newModule . dependencies . forEach ( dep => {
163- if ( dep . module ) {
164- dep . module . reasons . forEach ( reason => {
165- if ( reason . dependency === dep )
166- reason . module = newModule ;
167- } ) ;
168- }
169- } ) ;
170- }
171- compilation . modules = compilation . modules . filter ( m => ! usedModules . has ( m ) ) ;
172- } ) ;
155+ } ) ;
156+ compilation . modules . push ( newModule ) ;
157+ newModule . reasons . forEach ( reason => reason . dependency . module = newModule ) ;
158+ newModule . dependencies . forEach ( dep => {
159+ if ( dep . module ) {
160+ dep . module . reasons . forEach ( reason => {
161+ if ( reason . dependency === dep )
162+ reason . module = newModule ;
163+ } ) ;
164+ }
165+ } ) ;
166+ }
167+ compilation . modules = compilation . modules . filter ( m => ! usedModules . has ( m ) ) ;
173168 } ) ;
174169 } ) ;
175170 }
@@ -208,6 +203,13 @@ class ModuleConcatenationPlugin {
208203
209204 // Not possible to add?
210205 if ( ! possibleModules . has ( module ) ) {
206+ failureCache . set ( module , module ) ; // cache failures for performance
207+ return module ;
208+ }
209+
210+ // module must be in the same chunks
211+ if ( ! config . rootModule . hasEqualsChunks ( module ) ) {
212+ failureCache . set ( module , module ) ; // cache failures for performance
211213 return module ;
212214 }
213215
@@ -273,6 +275,16 @@ class ConcatConfiguration {
273275 this . warnings . set ( module , problem ) ;
274276 }
275277
278+ sortWarnings ( ) {
279+ this . warnings = new Map ( Array . from ( this . warnings ) . sort ( ( a , b ) => {
280+ const ai = a [ 0 ] . identifier ( ) ;
281+ const bi = b [ 0 ] . identifier ( ) ;
282+ if ( ai < bi ) return - 1 ;
283+ if ( ai > bi ) return 1 ;
284+ return 0 ;
285+ } ) ) ;
286+ }
287+
276288 clone ( ) {
277289 const clone = new ConcatConfiguration ( this . rootModule ) ;
278290 for ( const module of this . modules )
0 commit comments