@@ -140,9 +140,10 @@ class Collection {
140140 * @param {AssetEntryOptions } entry
141141 * @param {Array<Object> } styles
142142 * @param {string } LF The new line feed in depends on the minification option.
143+ * @param {Set<string> } externalStyleAssets Styles that must stay as emitted files for other entries.
143144 * @return {string|undefined }
144145 */
145- #bindImportedStyles( content , entry , styles , LF ) {
146+ async #bindImportedStyles( content , entry , styles , LF , externalStyleAssets ) {
146147 const insertPos = this . findStyleInsertPos ( content ) ;
147148 if ( insertPos < 0 ) {
148149 noHeadException ( entry . resource ) ;
@@ -153,7 +154,7 @@ class Collection {
153154
154155 for ( const asset of styles ) {
155156 if ( asset . inline ) {
156- const source = this . cssExtractModule . getInlineSource ( asset . assetFile ) ;
157+ const source = this . cssExtractModule . getInlineSource ( asset . assetFile , externalStyleAssets . has ( asset . assetFile ) ) ;
157158
158159 // note: in inlined style must be no LF character after the open tag, otherwise the mapping will not work
159160 styleTags += `<style>` + source + `</style>${ LF } ` ;
@@ -174,19 +175,22 @@ class Collection {
174175 * @param {string } search The original request of a style in the content.
175176 * @param {Object } asset The object of the style.
176177 * @param {string } LF The new line feed in depends on the minification option.
178+ * @param {Set<string> } externalStyleAssets Styles that must stay as emitted files for other entries.
179+ * @param {AssetEntryOptions } entry
177180 * @return {string|boolean } Return content with inlined CSS or false if the content was not modified.
178181 */
179- #inlineStyle( content , search , asset , LF ) {
182+ async #inlineStyle( content , search , asset , LF , externalStyleAssets , entry ) {
180183 const pos = content . indexOf ( search ) ;
184+ const searchPos = pos < 0 ? content . indexOf ( asset . assetFile ) : pos ;
181185
182- if ( pos < 0 ) return false ;
186+ if ( searchPos < 0 ) return false ;
183187
184- const source = this . cssExtractModule . getInlineSource ( asset . assetFile ) ;
188+ const source = this . cssExtractModule . getInlineSource ( asset . assetFile , externalStyleAssets . has ( asset . assetFile ) ) ;
185189 const tagEnd = '>' ;
186190 const openTag = '<style>' ;
187191 let closeTag = '</style>' ;
188- let tagStartPos = pos ;
189- let tagEndPos = pos + search . length ;
192+ let tagStartPos = searchPos ;
193+ let tagEndPos = searchPos + ( pos < 0 ? asset . assetFile . length : search . length ) ;
190194
191195 while ( tagStartPos >= 0 && content . charAt ( -- tagStartPos ) !== '<' ) { }
192196 tagEndPos = content . indexOf ( tagEnd , tagEndPos ) + tagEnd . length ;
@@ -207,13 +211,18 @@ class Collection {
207211 * @param {string } resource The resource file containing in the content.
208212 * @param {Object } asset The object of the script.
209213 * @param {string } LF The new line feed in depends on the minification option.
214+ * @param {AssetEntryOptions } entry
215+ * @param {Set<string> } externalChunkFiles Chunks that must stay as emitted files for other entries.
210216 * @return {string|boolean } Return content with inlined JS or false if the content was not modified.
211217 */
212- #bindScript( content , resource , asset , LF ) {
218+ #bindScript( content , resource , asset , LF , entry , externalChunkFiles ) {
213219 let pos = content . indexOf ( resource ) ;
220+ if ( pos < 0 && asset . chunks ?. length > 0 ) {
221+ pos = content . indexOf ( asset . chunks [ 0 ] . assetFile ) ;
222+ }
214223 if ( pos < 0 ) return false ;
215224
216- const { attributeFilter } = this . pluginOption . getJs ( ) . inline ;
225+ const { attributeFilter } = this . pluginOption . getJs ( entry ) . inline ;
217226
218227 const sources = this . compilation . assets ;
219228 const { chunks } = asset ;
@@ -267,7 +276,9 @@ class Collection {
267276 }
268277
269278 replacement += openTag + code + closeTag ;
270- this . assetTrash . add ( chunkFile ) ;
279+ if ( ! externalChunkFiles . has ( chunkFile ) ) {
280+ this . assetTrash . add ( chunkFile ) ;
281+ }
271282 } else {
272283 replacement += beforeTagSrc + assetFile + afterTagSrc ;
273284 }
@@ -279,6 +290,38 @@ class Collection {
279290 return content . slice ( 0 , tagStartPos ) + replacement + content . slice ( tagEndPos ) ;
280291 }
281292
293+ /**
294+ * Collect emitted JS chunks and CSS assets that must remain on disk because
295+ * at least one entry still references them as external files.
296+ *
297+ * This prevents removing an asset while inlining it into one page when the
298+ * same emitted file is still needed by another page.
299+ *
300+ * @return {{externalChunkFiles: Set<string>, externalStyleAssets: Set<string>} }
301+ */
302+ #collectExternalAssetReferences( ) {
303+ const externalChunkFiles = new Set ( ) ;
304+ const externalStyleAssets = new Set ( ) ;
305+
306+ for ( const [ , { assets } ] of this . data ) {
307+ for ( const asset of assets ) {
308+ if ( asset . type === Collection . type . script ) {
309+ for ( const chunk of [ ...( asset . chunks || [ ] ) , ...( asset . children || [ ] ) ] ) {
310+ if ( ! chunk . inline ) {
311+ externalChunkFiles . add ( chunk . chunkFile ) ;
312+ }
313+ }
314+ }
315+
316+ if ( asset . type === Collection . type . style && asset . inline === false ) {
317+ externalStyleAssets . add ( asset . assetFile ) ;
318+ }
319+ }
320+ }
321+
322+ return { externalChunkFiles, externalStyleAssets } ;
323+ }
324+
282325 /**
283326 * Prepare data for script rendering.
284327 */
@@ -343,7 +386,8 @@ class Collection {
343386 injectedChunks . add ( chunkFile ) ;
344387 }
345388
346- const inline = this . pluginOption . isInlineJs ( resource , chunkFile ) ;
389+ const entry = this . data . get ( entryFile ) ?. entry ;
390+ const inline = this . pluginOption . isInlineJs ( resource , chunkFile , entry ) ;
347391 const assetFile = this . pluginOption . getOutputFilename ( chunkFile , entryFile ) ;
348392
349393 splitChunkFiles . add ( chunkFile ) ;
@@ -357,7 +401,8 @@ class Collection {
357401 injectedChunks . add ( chunkFile ) ;
358402 }
359403
360- const inline = this . pluginOption . isInlineJs ( resource , chunkFile ) ;
404+ const entry = this . data . get ( entryFile ) ?. entry ;
405+ const inline = this . pluginOption . isInlineJs ( resource , chunkFile , entry ) ;
361406 const assetFile = this . pluginOption . getOutputFilename ( chunkFile , entryFile ) ;
362407
363408 splitChunkFiles . add ( chunkFile ) ;
@@ -914,9 +959,6 @@ class Collection {
914959 const compilation = this . compilation ;
915960 const { RawSource } = compilation . compiler . webpack . sources ;
916961 const hasIntegrity = this . pluginOption . isIntegrityEnabled ( ) ;
917- const isHtmlMinify = this . pluginOption . isMinify ( ) ;
918- const { minifyOptions } = this . pluginOption . get ( ) ;
919- const LF = this . pluginOption . getLF ( ) ;
920962 const hooks = this . hooks ;
921963 const promises = [ ] ;
922964
@@ -930,6 +972,7 @@ class Collection {
930972
931973 this . #normalizeData( ) ;
932974 this . #prepareScriptData( ) ;
975+ const { externalChunkFiles, externalStyleAssets } = this . #collectExternalAssetReferences( ) ;
933976
934977 // TODO: update this.data.assets[].asset.resource after change the filename in a template
935978 // - e.g. src="./main.js?v=1" => ./main.js?v=123 => WRONG filename is replaced
@@ -959,6 +1002,9 @@ class Collection {
9591002 const resourcePath = entry . resource ;
9601003 const entryDirname = path . dirname ( entryFilename ) ;
9611004 const importedStyles = [ ] ;
1005+ const isHtmlMinify = this . pluginOption . isEntryMinify ( entry ) ;
1006+ const minifyOptions = this . pluginOption . getMinifyOptions ( entry ) ;
1007+ const LF = this . pluginOption . getLF ( entry ) ;
9621008 const parseOptions = new Map ( ) ;
9631009 let hasInlineSvg = false ;
9641010 let content = rawSource . source ( ) ;
@@ -1046,7 +1092,7 @@ class Collection {
10461092 }
10471093
10481094 // 4. inline JS and CSS
1049- promise = promise . then ( ( content ) => {
1095+ promise = promise . then ( async ( content ) => {
10501096 // TODO:
10511097 // - style: rename output filename `assetFile` into filename or assetFilename
10521098 // - style: add additional filed - assetFile as asset path relative to output.path, not to issuer
@@ -1064,7 +1110,7 @@ class Collection {
10641110 if ( imported ) {
10651111 importedStyles . push ( asset ) ;
10661112 } else if ( inline ) {
1067- content = this . #inlineStyle( content , resource , asset , LF ) || content ;
1113+ content = ( await this . #inlineStyle( content , resource , asset , LF , externalStyleAssets , entry ) ) || content ;
10681114 } else {
10691115 // special use case for Pug only e.g.: style(scope='some')=require('./component.css?include')
10701116 const [ , query ] = resource . split ( '?' ) ;
@@ -1134,7 +1180,7 @@ class Collection {
11341180 }
11351181 }
11361182
1137- content = this . #bindScript( content , resource , asset , LF ) || content ;
1183+ content = this . #bindScript( content , resource , asset , LF , entry , externalChunkFiles ) || content ;
11381184 break ;
11391185 }
11401186 }
@@ -1144,7 +1190,9 @@ class Collection {
11441190
11451191 // 5. inject styles imported in JS
11461192 promise = promise . then ( ( content ) =>
1147- importedStyles . length > 0 ? this . #bindImportedStyles( content , entry , importedStyles , LF ) || content : content
1193+ importedStyles . length > 0
1194+ ? this . #bindImportedStyles( content , entry , importedStyles , LF , externalStyleAssets ) || content
1195+ : content
11481196 ) ;
11491197
11501198 // 6. inline SVG
0 commit comments