Skip to content

Commit 11af3aa

Browse files
committed
Improve the way SetPublicPathPlugin sets the asset filename.
1 parent b5563c7 commit 11af3aa

4 files changed

Lines changed: 95 additions & 42 deletions

File tree

build-tests/localization-plugin-test/webpack.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ module.exports = function(env) {
103103
}),
104104
new SetPublicPathPlugin({
105105
scriptName: {
106-
name: '[name]_[locale]_[contenthash].js',
107-
isTokenized: true
106+
useAssetName: true
108107
}
109108
}),
110109
new HtmlWebpackPlugin()

common/reviews/api/set-webpack-public-path-plugin.api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export interface ISetWebpackPublicPathOptions {
2323
// @public
2424
export interface ISetWebpackPublicPathPluginOptions extends ISetWebpackPublicPathOptions {
2525
scriptName?: {
26-
name: string;
27-
isTokenized: boolean;
26+
useAssetName?: boolean;
27+
name?: string;
28+
isTokenized?: boolean;
2829
};
2930
}
3031

webpack/set-webpack-public-path-plugin/src/SetPublicPathPlugin.ts

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'lodash';
99
import * as Webpack from 'webpack';
1010
import * as Tapable from 'tapable';
11+
import * as lodash from 'lodash';
1112

1213
import {
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+
99111
interface 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(/\[name\]/g, escapeRegExp(options.chunk.name))
211-
.replace(/\[hash\]/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(/\[name\]/g, escapeRegExp(options.chunk.name))
259+
.replace(/\[hash\]/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

webpack/set-webpack-public-path-plugin/src/codeGenerator.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function joinLines(lines: string[], linePrefix?: string): string {
3131
}).join(EOL).replace(new RegExp(`${EOL}${EOL}+`, 'g'), `${EOL}${EOL}`);
3232
}
3333

34-
function escapeSingleQuotes(str: string): string | undefined {
34+
export function escapeSingleQuotes(str: string): string | undefined {
3535
if (str) {
3636
return str.replace('\'', '\\\'');
3737
} else {
@@ -54,14 +54,11 @@ export function getSetPublicPathCode(options: IInternalOptions, emitWarning: (wa
5454

5555
let lines: string[] = [];
5656
if (options.regexName) {
57-
// Double-escape backslashes to make them show up as single backslashes in regexps.
58-
const escapedRegex: string = options.regexName.replace(/\\/, '\\\\');
59-
6057
lines = [
6158
`var scripts = document.getElementsByTagName('script');`
6259
];
6360

64-
const regexInitializationSnippet: string = `new RegExp('${escapeSingleQuotes(escapedRegex)}', 'i')`;
61+
const regexInitializationSnippet: string = `/${options.regexName}/i`;
6562
const regexVarName: string | undefined = options.regexVariable;
6663
if (options.regexVariable) {
6764
lines.push(...[

0 commit comments

Comments
 (0)