88} from 'lodash' ;
99import * as Webpack from 'webpack' ;
1010import * as Tapable from 'tapable' ;
11+ import * as lodash from 'lodash' ;
1112
1213import {
1314 IV3Compilation ,
@@ -82,20 +83,31 @@ export interface ISetWebpackPublicPathPluginOptions extends ISetWebpackPublicPat
8283 * An object that describes how the public path should be discovered.
8384 */
8485 scriptName ?: {
86+ /**
87+ * If set to true, use the webpack generated asset's name. This option is not compatible with
88+ * andy other scriptName options.
89+ */
90+ useAssetName ?: boolean ;
91+
8592 /**
8693 * A regular expression expressed as a string to be applied to all script paths on the page.
8794 */
88- name : string ;
95+ name ? : string ;
8996
9097 /**
9198 * If true, the name property is tokenized.
9299 *
93100 * See the README for more information.
94101 */
95- isTokenized : boolean ;
102+ isTokenized ? : boolean ;
96103 } ;
97104}
98105
106+ interface IAsset {
107+ size ( ) : number ;
108+ source ( ) : string ;
109+ }
110+
99111interface IV4MainTemplate extends Webpack . compilation . MainTemplate {
100112 hooks : {
101113 jsonpScript ?: Tapable . SyncWaterfallHook < string , Webpack . compilation . Chunk , string > ;
@@ -116,6 +128,14 @@ interface IStartupCodeOptions {
116128 requireFn : string ;
117129}
118130
131+ const PLUGIN_NAME : string = 'set-webpack-public-path' ;
132+
133+ const SHOULD_REPLACE_ASSET_NAME_TOKEN : unique symbol = Symbol ( 'set-public-path-plugin-should-replace-asset-name' ) ;
134+
135+ const ASSET_NAME_TOKEN : string = '-ASSET-NAME-c0ef4f86-b570-44d3-b210-4428c5b7825c' ;
136+
137+ const ASSET_NAME_TOKEN_REGEX : RegExp = new RegExp ( ASSET_NAME_TOKEN ) ;
138+
119139/**
120140 * This simple plugin sets the __webpack_public_path__ variable to a value specified in the arguments,
121141 * optionally appended to the SystemJs baseURL property.
@@ -127,46 +147,73 @@ export class SetPublicPathPlugin implements Webpack.Plugin {
127147
128148 public constructor ( options : ISetWebpackPublicPathPluginOptions ) {
129149 this . options = options ;
150+
151+ if ( options . scriptName ) {
152+ if ( options . scriptName . useAssetName && options . scriptName . name ) {
153+ throw new Error ( 'scriptName.userAssetName and scriptName.name must not be used together' ) ;
154+ } else if ( options . scriptName . isTokenized && ! options . scriptName . name ) {
155+ throw new Error ( 'scriptName.isTokenized is only valid if scriptName.name is set' ) ;
156+ }
157+ }
130158 }
131159
132160 public apply ( compiler : Webpack . Compiler ) : void {
133161 const isWebpack4 : boolean = ! ! compiler . hooks ;
134162
135163 if ( isWebpack4 ) {
136- compiler . hooks . compilation . tap ( 'set-webpack-public-path' , ( compilation : Webpack . compilation . Compilation ) => {
164+ compiler . hooks . compilation . tap ( PLUGIN_NAME , ( compilation : Webpack . compilation . Compilation ) => {
137165 const v4MainTemplate : IV4MainTemplate = compilation . mainTemplate as IV4MainTemplate ;
138- v4MainTemplate . hooks . startup . tap (
139- 'set-webpack-public-path' ,
140- ( source : string , chunk : IV4Chunk , hash : string ) => {
141- let assetOrChunkFound : boolean = ! ! this . options . skipDetection ;
142-
143- if ( ! assetOrChunkFound ) {
144- for ( const chunkGroup of chunk . groupsIterable ) {
145- const children : Webpack . compilation . Chunk [ ] = chunkGroup . getChildren ( ) ;
146- assetOrChunkFound = assetOrChunkFound || ( children . length > 0 ) ;
147- }
166+ v4MainTemplate . hooks . startup . tap ( PLUGIN_NAME , ( source : string , chunk : IV4Chunk , hash : string ) => {
167+ let assetOrChunkFound : boolean = ! ! this . options . skipDetection ;
168+
169+ if ( ! assetOrChunkFound ) {
170+ for ( const chunkGroup of chunk . groupsIterable ) {
171+ const children : Webpack . compilation . Chunk [ ] = chunkGroup . getChildren ( ) ;
172+ assetOrChunkFound = assetOrChunkFound || ( children . length > 0 ) ;
148173 }
174+ }
149175
150- if ( ! assetOrChunkFound ) {
151- for ( const innerModule of chunk . modulesIterable ) {
152- if ( innerModule . buildInfo . assets && Object . keys ( innerModule . buildInfo . assets ) . length > 0 ) {
153- assetOrChunkFound = true ;
154- }
176+ if ( ! assetOrChunkFound ) {
177+ for ( const innerModule of chunk . modulesIterable ) {
178+ if ( innerModule . buildInfo . assets && Object . keys ( innerModule . buildInfo . assets ) . length > 0 ) {
179+ assetOrChunkFound = true ;
155180 }
156181 }
182+ }
183+
184+ if ( assetOrChunkFound ) {
185+ return this . _getStartupCode ( {
186+ source,
187+ chunk,
188+ hash,
189+ requireFn : v4MainTemplate . requireFn
190+ } ) ;
191+ } else {
192+ return source ;
193+ }
194+ } ) ;
195+ } ) ;
196+
197+ compiler . hooks . emit . tap ( PLUGIN_NAME , ( compilation : Webpack . compilation . Compilation ) => {
198+ for ( const chunkGroup of compilation . chunkGroups ) {
199+ for ( const chunk of chunkGroup . chunks ) {
200+ if ( chunk [ SHOULD_REPLACE_ASSET_NAME_TOKEN ] ) {
201+ for ( const assetFilename of chunk . files ) {
202+ const asset : IAsset = compilation . assets [ assetFilename ] ;
203+ const originalAssetSource : string = asset . source ( ) ;
204+ const originalAssetSize : number = asset . size ( ) ;
157205
158- if ( assetOrChunkFound ) {
159- return this . _getStartupCode ( {
160- source,
161- chunk,
162- hash,
163- requireFn : v4MainTemplate . requireFn
164- } ) ;
165- } else {
166- return source ;
206+ const newAssetSource : string = originalAssetSource . replace (
207+ ASSET_NAME_TOKEN_REGEX ,
208+ lodash . escapeRegExp ( assetFilename )
209+ ) ;
210+ const sizeDifference : number = assetFilename . length - ASSET_NAME_TOKEN . length ;
211+ asset . source = ( ) => newAssetSource ;
212+ asset . size = ( ) => originalAssetSize + sizeDifference ;
213+ }
167214 }
168215 }
169- ) ;
216+ }
170217 } ) ;
171218 } else {
172219 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -204,11 +251,20 @@ export class SetPublicPathPlugin implements Webpack.Plugin {
204251 moduleOptions . linePrefix = ' ' ;
205252
206253 if ( this . options . scriptName ) {
207- moduleOptions . regexName = this . options . scriptName . name ;
208- if ( this . options . scriptName . isTokenized ) {
209- moduleOptions . regexName = moduleOptions . regexName
210- . replace ( / \[ n a m e \] / g, escapeRegExp ( options . chunk . name ) )
211- . replace ( / \[ h a s h \] / g, options . chunk . renderedHash ) ;
254+ if ( this . options . scriptName . name ) {
255+ moduleOptions . regexName = this . options . scriptName . name ;
256+ if ( this . options . scriptName . isTokenized ) {
257+ moduleOptions . regexName = moduleOptions . regexName
258+ . replace ( / \[ n a m e \] / g, escapeRegExp ( options . chunk . name ) )
259+ . replace ( / \[ h a s h \] / g, options . chunk . renderedHash ) ;
260+ }
261+ } else if ( this . options . scriptName . useAssetName ) {
262+ options . chunk [ SHOULD_REPLACE_ASSET_NAME_TOKEN ] = (
263+ this . options . scriptName &&
264+ ! ! this . options . scriptName . useAssetName
265+ ) ;
266+
267+ moduleOptions . regexName = ASSET_NAME_TOKEN ;
212268 }
213269 }
214270
0 commit comments