@@ -87,123 +87,233 @@ class PreloadPlugin {
8787 }
8888
8989 apply ( compiler ) {
90+ if ( compiler . hooks ) {
91+ compiler . hooks [ 'compilation' ] . tap ( PLUGIN_NAME , this . hooksHandler . bind ( this ) ) ;
92+ } else {
93+ compiler . plugin ( 'compilation' , this . pluginHandler . bind ( this ) ) ;
94+ }
95+ }
96+
97+ // handler use for webpack v4
98+ hooksHandler ( compilation ) {
9099 const options = this . options ;
91- compiler . hooks [ 'compilation' ] . tap ( PLUGIN_NAME , ( compilation ) => {
92- if ( ! compilation . hooks . htmlWebpackPluginAfterHtmlProcessing ) {
93- const message = `compilation.hooks.htmlWebpackPluginAfterHtmlProcessing is lost. Please make sure you have installed html-webpack-plugin and put it before ${ PLUGIN_NAME } ` ;
94- log . error ( message ) ;
95- throw new Error ( message ) ;
100+ if ( ! compilation . hooks . htmlWebpackPluginAfterHtmlProcessing ) {
101+ const message = `compilation.hooks.htmlWebpackPluginAfterHtmlProcessing is lost. Please make sure you have installed html-webpack-plugin and put it before ${ PLUGIN_NAME } ` ;
102+ log . error ( message ) ;
103+ throw new Error ( message ) ;
104+ }
105+ compilation . hooks . htmlWebpackPluginAfterHtmlProcessing . tapAsync ( PLUGIN_NAME , ( htmlPluginData , cb ) => {
106+ if ( this . options . excludeHtmlNames . indexOf ( htmlPluginData . plugin . options . filename ) > - 1 ) {
107+ cb ( null , htmlPluginData ) ;
108+ return ;
96109 }
97- compilation . hooks . htmlWebpackPluginAfterHtmlProcessing . tapAsync ( PLUGIN_NAME , ( htmlPluginData , cb ) => {
98- if ( this . options . excludeHtmlNames . indexOf ( htmlPluginData . plugin . options . filename ) > - 1 ) {
99- cb ( null , htmlPluginData ) ;
100- return ;
110+ let filesToInclude = '' ;
111+ let extractedChunks = [ ] ;
112+ const initialChunkGroups = compilation . chunkGroups . filter ( chunkGroup => chunkGroup . isInitial ( ) ) ;
113+ const initialChunks = initialChunkGroups . reduce ( ( initialChunks , { chunks} ) => {
114+ return initialChunks . concat ( chunks ) ;
115+ } , [ ] ) ;
116+ // 'asyncChunks' are chunks intended for lazy/async loading usually generated as
117+ // part of code-splitting with import() or require.ensure(). By default, asyncChunks
118+ // get wired up using link rel=preload when using this plugin. This behaviour can be
119+ // configured to preload all types of chunks or just prefetch chunks as needed.
120+ if ( options . include === undefined || options . include === 'asyncChunks' ) {
121+ extractedChunks = compilation . chunks . filter ( chunk => {
122+ return initialChunks . indexOf ( chunk ) < 0 ;
123+ } ) ;
124+ } else if ( options . include === 'initial' ) {
125+ extractedChunks = compilation . chunks . filter ( chunk => {
126+ return initialChunks . indexOf ( chunk ) > - 1 ;
127+ } ) ;
128+ } else if ( options . include === 'allChunks' || options . include === 'all' ) {
129+ if ( options . include === 'all' ) {
130+ /* eslint-disable no-console */
131+ console . warn ( '[WARNING]: { include: "all" } is deprecated, please use "allChunks" instead.' ) ;
132+ /* eslint-enable no-console */
101133 }
102- let filesToInclude = '' ;
103- let extractedChunks = [ ] ;
104- const initialChunkGroups = compilation . chunkGroups . filter ( chunkGroup => chunkGroup . isInitial ( ) ) ;
105- const initialChunks = initialChunkGroups . reduce ( ( initialChunks , { chunks} ) => {
106- return initialChunks . concat ( chunks ) ;
107- } , [ ] ) ;
108- // 'asyncChunks' are chunks intended for lazy/async loading usually generated as
109- // part of code-splitting with import() or require.ensure(). By default, asyncChunks
110- // get wired up using link rel=preload when using this plugin. This behaviour can be
111- // configured to preload all types of chunks or just prefetch chunks as needed.
112- if ( options . include === undefined || options . include === 'asyncChunks' ) {
113- extractedChunks = compilation . chunks . filter ( chunk => {
114- return initialChunks . indexOf ( chunk ) < 0 ;
115- } ) ;
116- } else if ( options . include === 'initial' ) {
117- extractedChunks = compilation . chunks . filter ( chunk => {
118- return initialChunks . indexOf ( chunk ) > - 1 ;
134+ // Async chunks, vendor chunks, normal chunks.
135+ extractedChunks = compilation . chunks ;
136+ } else if ( options . include === 'allAssets' ) {
137+ extractedChunks = [ { files : Object . keys ( compilation . assets ) } ] ;
138+ } else if ( Array . isArray ( options . include ) ) {
139+ // Keep only user specified chunks
140+ extractedChunks = compilation . chunks
141+ . filter ( ( chunk ) => {
142+ const chunkName = chunk . name ;
143+ // Works only for named chunks
144+ if ( ! chunkName ) {
145+ return false ;
146+ }
147+ return options . include . indexOf ( chunkName ) > - 1 ;
119148 } ) ;
120- } else if ( options . include === 'allChunks' || options . include === 'all' ) {
121- if ( options . include === 'all' ) {
122- /* eslint-disable no-console */
123- console . warn ( '[WARNING]: { include: "all" } is deprecated, please use "allChunks" instead.' ) ;
124- /* eslint-enable no-console */
125- }
126- // Async chunks, vendor chunks, normal chunks.
127- extractedChunks = compilation . chunks ;
128- } else if ( options . include === 'allAssets' ) {
129- extractedChunks = [ { files : Object . keys ( compilation . assets ) } ] ;
130- } else if ( Array . isArray ( options . include ) ) {
131- // Keep only user specified chunks
132- extractedChunks = compilation . chunks
133- . filter ( ( chunk ) => {
134- const chunkName = chunk . name ;
135- // Works only for named chunks
136- if ( ! chunkName ) {
137- return false ;
138- }
139- return options . include . indexOf ( chunkName ) > - 1 ;
140- } ) ;
141- }
149+ }
142150
143- const publicPath = compilation . outputOptions . publicPath || '' ;
144-
145- // only handle the chunks associated to this htmlWebpackPlugin instance, in case of multiple html plugin outputs
146- // allow `allAssets` mode to skip, as assets are just files to be filtered by black/whitelist, not real chunks
147- if ( options . include !== 'allAssets' ) {
148- extractedChunks = extractedChunks . filter ( chunk => {
149- const rootChunksHashs = Object . values ( htmlPluginData . assets . chunks ) . map ( ( { hash} ) => hash ) ;
150- const rootChunkGroups = compilation . chunkGroups . reduce ( ( groups , chunkGroup ) => {
151- const isRootChunkGroup = chunkGroup . chunks . reduce ( ( flag , chunk ) => {
152- return flag ||
153- rootChunksHashs . indexOf ( chunk . renderedHash ) > - 1 ;
154- } , false ) ;
155- if ( isRootChunkGroup ) groups . push ( chunkGroup ) ;
156- return groups ;
157- } , [ ] ) ;
158- return Array . from ( chunk . groupsIterable ) . reduce ( ( flag , chunkGroup ) => {
151+ const publicPath = compilation . outputOptions . publicPath || '' ;
152+
153+ // only handle the chunks associated to this htmlWebpackPlugin instance, in case of multiple html plugin outputs
154+ // allow `allAssets` mode to skip, as assets are just files to be filtered by black/whitelist, not real chunks
155+ if ( options . include !== 'allAssets' ) {
156+ extractedChunks = extractedChunks . filter ( chunk => {
157+ const rootChunksHashs = Object . values ( htmlPluginData . assets . chunks ) . map ( ( { hash} ) => hash ) ;
158+ const rootChunkGroups = compilation . chunkGroups . reduce ( ( groups , chunkGroup ) => {
159+ const isRootChunkGroup = chunkGroup . chunks . reduce ( ( flag , chunk ) => {
159160 return flag ||
160- doesChunkGroupBelongToHTML ( chunkGroup , rootChunkGroups , { } ) ;
161+ rootChunksHashs . indexOf ( chunk . renderedHash ) > - 1 ;
161162 } , false ) ;
162- } ) ;
163+ if ( isRootChunkGroup ) groups . push ( chunkGroup ) ;
164+ return groups ;
165+ } , [ ] ) ;
166+ return Array . from ( chunk . groupsIterable ) . reduce ( ( flag , chunkGroup ) => {
167+ return flag ||
168+ doesChunkGroupBelongToHTML ( chunkGroup , rootChunkGroups , { } ) ;
169+ } , false ) ;
170+ } ) ;
171+ }
172+
173+ flatten ( extractedChunks . map ( chunk => chunk . files ) )
174+ . filter ( entry => {
175+ return (
176+ ! this . options . fileWhitelist ||
177+ this . options . fileWhitelist . some ( regex => regex . test ( entry ) === true )
178+ ) ;
179+ } )
180+ . filter ( entry => {
181+ return this . options . fileBlacklist . every ( regex => regex . test ( entry ) === false ) ;
182+ } ) . forEach ( entry => {
183+ entry = `${ publicPath } ${ entry } ` ;
184+ if ( options . rel === 'preload' ) {
185+ // If `as` value is not provided in option, dynamically determine the correct
186+ // value depends on suffix of filename. Otherwise use the given `as` value.
187+ let asValue ;
188+ if ( ! options . as ) {
189+ if ( entry . match ( / \. c s s $ / ) ) asValue = 'style' ;
190+ else if ( entry . match ( / \. w o f f 2 $ / ) ) asValue = 'font' ;
191+ else asValue = 'script' ;
192+ } else if ( typeof options . as === 'function' ) {
193+ asValue = options . as ( entry ) ;
194+ } else {
195+ asValue = options . as ;
196+ }
197+ const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '' ;
198+ filesToInclude += `<link rel="${ options . rel } " as="${ asValue } " ${ crossOrigin } href="${ entry } ">\n` ;
199+ } else {
200+ // If preload isn't specified, the only other valid entry is prefetch here
201+ // You could specify preconnect but as we're dealing with direct paths to resources
202+ // instead of origins that would make less sense.
203+ filesToInclude += `<link rel="${ options . rel } " href="${ entry } ">\n` ;
204+ }
205+ } ) ;
206+ if ( htmlPluginData . html . indexOf ( '</head>' ) !== - 1 ) {
207+ // If a valid closing </head> is found, update it to include preload/prefetch tags
208+ htmlPluginData . html = htmlPluginData . html . replace ( '</head>' , filesToInclude + '</head>' ) ;
209+ } else {
210+ // Otherwise assume at least a <body> is present and update it to include a new <head>
211+ htmlPluginData . html = htmlPluginData . html . replace ( '<body>' , '<head>' + filesToInclude + '</head><body>' ) ;
212+ }
213+ cb ( null , htmlPluginData ) ;
214+ } ) ;
215+ }
216+
217+ // handler use before webpack v4
218+ pluginHandler ( compilation ) {
219+ const options = this . options ;
220+ compilation . plugin ( 'html-webpack-plugin-before-html-processing' , ( htmlPluginData , cb ) => {
221+ if ( this . options . excludeHtmlNames . indexOf ( htmlPluginData . plugin . options . filename ) > - 1 ) {
222+ cb ( null , htmlPluginData ) ;
223+ return ;
224+ }
225+ let filesToInclude = '' ;
226+ let extractedChunks = [ ] ;
227+ // 'asyncChunks' are chunks intended for lazy/async loading usually generated as
228+ // part of code-splitting with import() or require.ensure(). By default, asyncChunks
229+ // get wired up using link rel=preload when using this plugin. This behaviour can be
230+ // configured to preload all types of chunks or just prefetch chunks as needed.
231+ if ( options . include === undefined || options . include === 'asyncChunks' ) {
232+ try {
233+ extractedChunks = compilation . chunks . filter ( chunk => ! chunk . isInitial ( ) ) ;
234+ } catch ( e ) {
235+ extractedChunks = compilation . chunks ;
163236 }
237+ } else if ( options . include === 'initial' ) {
238+ try {
239+ extractedChunks = compilation . chunks . filter ( chunk => chunk . isInitial ( ) ) ;
240+ } catch ( e ) {
241+ extractedChunks = compilation . chunks ;
242+ }
243+ } else if ( options . include === 'allChunks' || options . include === 'all' ) {
244+ if ( options . include === 'all' ) {
245+ /* eslint-disable no-console */
246+ console . warn ( '[WARNING]: { include: "all" } is deprecated, please use "allChunks" instead.' ) ;
247+ /* eslint-enable no-console */
248+ }
249+ // Async chunks, vendor chunks, normal chunks.
250+ extractedChunks = compilation . chunks ;
251+ } else if ( options . include === 'allAssets' ) {
252+ extractedChunks = [ { files : Object . keys ( compilation . assets ) } ] ;
253+ } else if ( Array . isArray ( options . include ) ) {
254+ // Keep only user specified chunks
255+ extractedChunks = compilation
256+ . chunks
257+ . filter ( ( chunk ) => {
258+ const chunkName = chunk . name ;
259+ // Works only for named chunks
260+ if ( ! chunkName ) {
261+ return false ;
262+ }
263+ return options . include . indexOf ( chunkName ) > - 1 ;
264+ } ) ;
265+ }
164266
165- flatten ( extractedChunks . map ( chunk => chunk . files ) )
166- . filter ( entry => {
167- return (
168- ! this . options . fileWhitelist ||
267+ const publicPath = compilation . outputOptions . publicPath || '' ;
268+
269+ // only handle the chunks associated to this htmlWebpackPlugin instance, in case of multiple html plugin outputs
270+ // allow `allAssets` mode to skip, as assets are just files to be filtered by black/whitelist, not real chunks
271+ if ( options . include !== 'allAssets' ) {
272+ extractedChunks = extractedChunks . filter ( chunk => doesChunkBelongToHTML (
273+ chunk , Object . values ( htmlPluginData . assets . chunks ) , { } ) ) ;
274+ }
275+
276+ flatten ( extractedChunks . map ( chunk => chunk . files ) )
277+ . filter ( entry => {
278+ return (
279+ ! this . options . fileWhitelist ||
169280 this . options . fileWhitelist . some ( regex => regex . test ( entry ) === true )
170- ) ;
171- } )
172- . filter ( entry => {
173- return this . options . fileBlacklist . every ( regex => regex . test ( entry ) === false ) ;
174- } ) . forEach ( entry => {
175- entry = `${ publicPath } ${ entry } ` ;
176- if ( options . rel === 'preload' ) {
281+ ) ;
282+ } )
283+ . filter ( entry => {
284+ return this . options . fileBlacklist . every ( regex => regex . test ( entry ) === false ) ;
285+ } ) . forEach ( entry => {
286+ entry = `${ publicPath } ${ entry } ` ;
287+ if ( options . rel === 'preload' ) {
177288 // If `as` value is not provided in option, dynamically determine the correct
178289 // value depends on suffix of filename. Otherwise use the given `as` value.
179- let asValue ;
180- if ( ! options . as ) {
181- if ( entry . match ( / \. c s s $ / ) ) asValue = 'style' ;
182- else if ( entry . match ( / \. w o f f 2 $ / ) ) asValue = 'font' ;
183- else asValue = 'script' ;
184- } else if ( typeof options . as === 'function' ) {
185- asValue = options . as ( entry ) ;
186- } else {
187- asValue = options . as ;
188- }
189- const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '' ;
190- filesToInclude += `<link rel="${ options . rel } " as="${ asValue } " ${ crossOrigin } href="${ entry } ">\n` ;
290+ let asValue ;
291+ if ( ! options . as ) {
292+ if ( entry . match ( / \. c s s $ / ) ) asValue = 'style' ;
293+ else if ( entry . match ( / \. w o f f 2 $ / ) ) asValue = 'font' ;
294+ else asValue = 'script' ;
295+ } else if ( typeof options . as === 'function' ) {
296+ asValue = options . as ( entry ) ;
191297 } else {
298+ asValue = options . as ;
299+ }
300+ const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '' ;
301+ filesToInclude += `<link rel="${ options . rel } " as="${ asValue } " ${ crossOrigin } href="${ entry } ">\n` ;
302+ } else {
192303 // If preload isn't specified, the only other valid entry is prefetch here
193304 // You could specify preconnect but as we're dealing with direct paths to resources
194305 // instead of origins that would make less sense.
195- filesToInclude += `<link rel="${ options . rel } " href="${ entry } ">\n` ;
196- }
197- } ) ;
198- if ( htmlPluginData . html . indexOf ( '</head>' ) !== - 1 ) {
199- // If a valid closing </head> is found, update it to include preload/prefetch tags
200- htmlPluginData . html = htmlPluginData . html . replace ( '</head>' , filesToInclude + '</head>' ) ;
201- } else {
202- // Otherwise assume at least a <body> is present and update it to include a new <head>
203- htmlPluginData . html = htmlPluginData . html . replace ( '<body>' , '<head>' + filesToInclude + '</head><body>' ) ;
204- }
205- cb ( null , htmlPluginData ) ;
206- } ) ;
306+ filesToInclude += `<link rel="${ options . rel } " href="${ entry } ">\n` ;
307+ }
308+ } ) ;
309+ if ( htmlPluginData . html . indexOf ( '</head>' ) !== - 1 ) {
310+ // If a valid closing </head> is found, update it to include preload/prefetch tags
311+ htmlPluginData . html = htmlPluginData . html . replace ( '</head>' , filesToInclude + '</head>' ) ;
312+ } else {
313+ // Otherwise assume at least a <body> is present and update it to include a new <head>
314+ htmlPluginData . html = htmlPluginData . html . replace ( '<body>' , '<head>' + filesToInclude + '</head><body>' ) ;
315+ }
316+ cb ( null , htmlPluginData ) ;
207317 } ) ;
208318 }
209319}
0 commit comments