Skip to content

Commit f2dd71b

Browse files
refactor: filename option (#195)
BREAKING CHANGE: filename` is changed, you need to return path with placholders
1 parent 9de2a88 commit f2dd71b

File tree

7 files changed

+186
-119
lines changed

7 files changed

+186
-119
lines changed

README.md

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ And run `webpack` via your preferred method.
4949
| **[`compressionOptions`](#compressionoptions)** | `{Object}` | `{ level: 9 }` | Compression options for `algorithm` |
5050
| **[`threshold`](#threshold)** | `{Number}` | `0` | Only assets bigger than this size are processed (in bytes) |
5151
| **[`minRatio`](#minratio)** | `{Number}` | `0.8` | Only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`) |
52-
| **[`filename`](#filename)** | `{String\|Function}` | `[path].gz` | The target asset filename. |
52+
| **[`filename`](#filename)** | `{String\|Function}` | `[base].gz` | The target asset filename. |
5353
| **[`cache`](#cache)** | `{Boolean}` | `true` | Enable file caching |
5454

5555
### `test`
@@ -230,18 +230,27 @@ module.exports = {
230230
### `filename`
231231

232232
Type: `String|Function`
233-
Default: `[path].gz[query]`
233+
Default: `[path][base].gz`
234234

235235
The target asset filename.
236236

237237
#### `String`
238238

239-
`[file]` is replaced with the original asset filename.
240-
`[path]` is replaced with the path of the original asset.
241-
`[dir]` is replaced with the directory of the original asset.
242-
`[name]` is replaced with the filename of the original asset.
243-
`[ext]` is replaced with the extension of the original asset.
244-
`[query]` is replaced with the query.
239+
For example we have `assets/images/image.png?foo=bar#hash`:
240+
241+
`[path]` is replaced with the directories to the original asset, included trailing `/` (`assets/images/`).
242+
243+
`[file]` is replaced with the path of original asset (`assets/images/image.png`).
244+
245+
`[base]` is replaced with the base (`[name]` + `[ext]`) of the original asset (`image.png`).
246+
247+
`[name]` is replaced with the name of the original asset (`image`).
248+
249+
`[ext]` is replaced with the extension of the original asset, included `.` (`.png`).
250+
251+
`[query]` is replaced with the query of the original asset, included `?` (`?foo=bar`).
252+
253+
`[fragment]` is replaced with the fragment (in the concept of URL it is called `hash`) of the original asset (`#hash`).
245254

246255
**webpack.config.js**
247256

@@ -263,11 +272,14 @@ module.exports = {
263272
module.exports = {
264273
plugins: [
265274
new CompressionPlugin({
266-
filename(info) {
267-
// info.file is the original asset filename
268-
// info.path is the path of the original asset
269-
// info.query is the query
270-
return `${info.path}.gz${info.query}`;
275+
filename(pathData) {
276+
// The `pathData` argument contains all placeholders - `path`/`name`/`ext`/etc
277+
// Available properties described above, for the `String` notation
278+
if (/\.svg$/.test(pathData.file)) {
279+
return 'assets/svg/[path][base].gz';
280+
}
281+
282+
return 'assets/js/[path][base].gz';
271283
},
272284
}),
273285
],

src/index.js

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import crypto from 'crypto';
7-
import url from 'url';
87
import path from 'path';
98

109
import webpack, {
@@ -33,7 +32,7 @@ class CompressionPlugin {
3332
cache = true,
3433
algorithm = 'gzip',
3534
compressionOptions = {},
36-
filename = '[path].gz',
35+
filename = '[base].gz',
3736
threshold = 0,
3837
minRatio = 0.8,
3938
deleteOriginalAssets = false,
@@ -74,27 +73,6 @@ class CompressionPlugin {
7473
}
7574
}
7675

77-
static interpolateName(originalFilename, filename) {
78-
const parse = url.parse(originalFilename);
79-
const { pathname } = parse;
80-
const { dir, name, ext } = path.parse(pathname);
81-
const info = {
82-
file: originalFilename,
83-
path: pathname,
84-
dir: dir ? `${dir}/` : '',
85-
name,
86-
ext,
87-
query: parse.query ? `?${parse.query}` : '',
88-
};
89-
90-
return typeof filename === 'function'
91-
? filename(info)
92-
: filename.replace(
93-
/\[(file|path|query|dir|name|ext)]/g,
94-
(p0, p1) => info[p1]
95-
);
96-
}
97-
9876
// eslint-disable-next-line consistent-return
9977
static getAsset(compilation, name) {
10078
// New API
@@ -254,23 +232,48 @@ class CompressionPlugin {
254232
return;
255233
}
256234

257-
const newAssetName = CompressionPlugin.interpolateName(
258-
name,
259-
this.options.filename
235+
const match = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(name);
236+
const [, replacerFile] = match;
237+
const replacerQuery = match[2] || '';
238+
const replacerFragment = match[3] || '';
239+
const replacerExt = path.extname(replacerFile);
240+
const replacerBase = path.basename(replacerFile);
241+
const replacerName = replacerBase.slice(
242+
0,
243+
replacerBase.length - replacerExt.length
244+
);
245+
const replacerPath = replacerFile.slice(
246+
0,
247+
replacerFile.length - replacerBase.length
248+
);
249+
const pathData = {
250+
file: replacerFile,
251+
query: replacerQuery,
252+
fragment: replacerFragment,
253+
path: replacerPath,
254+
base: replacerBase,
255+
name: replacerName,
256+
ext: replacerExt || '',
257+
};
258+
259+
let newFilename = this.options.filename;
260+
261+
if (typeof newFilename === 'function') {
262+
newFilename = newFilename(pathData);
263+
}
264+
265+
const newName = newFilename.replace(
266+
/\[(file|query|fragment|path|base|name|ext)]/g,
267+
(p0, p1) => pathData[p1]
260268
);
261269

262270
const newInfo = { compressed: true };
263271

264-
if (info.immutable) {
272+
if (info.immutable && /(\[name]|\[base]|\[file])/.test(newFilename)) {
265273
newInfo.immutable = true;
266274
}
267275

268-
CompressionPlugin.emitAsset(
269-
compilation,
270-
newAssetName,
271-
output,
272-
newInfo
273-
);
276+
CompressionPlugin.emitAsset(compilation, newName, output, newInfo);
274277

275278
if (this.options.deleteOriginalAssets) {
276279
// eslint-disable-next-line no-param-reassign
@@ -279,7 +282,7 @@ class CompressionPlugin {
279282
// TODO `...` required only for webpack@4
280283
const newOriginalInfo = {
281284
...info,
282-
related: { [relatedName]: newAssetName },
285+
related: { [relatedName]: newName },
283286
};
284287

285288
CompressionPlugin.updateAsset(

test/CompressionPlugin.test.js

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import zlib from 'zlib';
22
import path from 'path';
33

44
import webpack from 'webpack';
5+
import findCacheDir from 'find-cache-dir';
6+
import cacache from 'cacache';
57

8+
import Webpack4Cache from '../src/Webpack4Cache';
69
import CompressionPlugin from '../src/index';
710

811
import {
@@ -17,9 +20,18 @@ import {
1720
removeCache,
1821
} from './helpers/index';
1922

23+
const cacheDir1 = findCacheDir({ name: 'compression-webpack-plugin-cache-1' });
24+
const cacheDir2 = findCacheDir({ name: 'compression-webpack-plugin-cache-2' });
25+
const cacheDir3 = findCacheDir({ name: 'compression-webpack-plugin-cache-3' });
26+
2027
describe('CompressionPlugin', () => {
21-
beforeEach(() => {
22-
return removeCache();
28+
beforeAll(() => {
29+
return Promise.all([
30+
removeCache(),
31+
cacache.rm.all(cacheDir1),
32+
cacache.rm.all(cacheDir2),
33+
cacache.rm.all(cacheDir3),
34+
]);
2335
});
2436

2537
it('should work', async () => {
@@ -148,23 +160,23 @@ describe('CompressionPlugin', () => {
148160

149161
new CompressionPlugin({
150162
algorithm: 'gzip',
151-
filename: '[path].gz',
163+
filename: '[path][base].gz',
152164
}).apply(compiler);
153165
new CompressionPlugin({
154166
algorithm: 'brotliCompress',
155-
filename: '[path].br',
167+
filename: '[path][base].br',
156168
}).apply(compiler);
157169
new CompressionPlugin({
158170
algorithm: (input, options, callback) => {
159171
return callback(input);
160172
},
161-
filename: '[path].compress',
173+
filename: '[path][base].compress',
162174
}).apply(compiler);
163175
new CompressionPlugin({
164176
algorithm: (input, options, callback) => {
165177
return callback(input);
166178
},
167-
filename: '[path].custom?foo=bar#hash',
179+
filename: '[path][base].custom?foo=bar#hash',
168180
}).apply(compiler);
169181

170182
const stats = await compile(compiler);
@@ -230,6 +242,12 @@ describe('CompressionPlugin', () => {
230242
});
231243

232244
it('should work and use memory cache without options in the "development" mode', async () => {
245+
const getCacheDirectorySpy = jest
246+
.spyOn(Webpack4Cache, 'getCacheDirectory')
247+
.mockImplementation(() => {
248+
return cacheDir1;
249+
});
250+
233251
const compiler = getCompiler('./entry.js', {}, { mode: 'development' });
234252

235253
new CompressionPlugin().apply(compiler);
@@ -267,11 +285,19 @@ describe('CompressionPlugin', () => {
267285
expect(getWarnings(newStats)).toMatchSnapshot('errors');
268286
expect(getErrors(newStats)).toMatchSnapshot('warnings');
269287

288+
getCacheDirectorySpy.mockRestore();
289+
270290
resolve();
271291
});
272292
});
273293

274294
it('should work and use memory cache when the "cache" option is "true"', async () => {
295+
const getCacheDirectorySpy = jest
296+
.spyOn(Webpack4Cache, 'getCacheDirectory')
297+
.mockImplementation(() => {
298+
return cacheDir2;
299+
});
300+
275301
const compiler = getCompiler(
276302
'./entry.js',
277303
{},
@@ -320,11 +346,19 @@ describe('CompressionPlugin', () => {
320346
expect(getWarnings(newStats)).toMatchSnapshot('errors');
321347
expect(getErrors(newStats)).toMatchSnapshot('warnings');
322348

349+
getCacheDirectorySpy.mockRestore();
350+
323351
resolve();
324352
});
325353
});
326354

327355
it('should work and use memory cache when the "cache" option is "true" and the asset has been changed', async () => {
356+
const getCacheDirectorySpy = jest
357+
.spyOn(Webpack4Cache, 'getCacheDirectory')
358+
.mockImplementation(() => {
359+
return cacheDir3;
360+
});
361+
328362
const compiler = getCompiler(
329363
'./entry.js',
330364
{},
@@ -375,6 +409,8 @@ describe('CompressionPlugin', () => {
375409
expect(getWarnings(newStats)).toMatchSnapshot('errors');
376410
expect(getErrors(newStats)).toMatchSnapshot('warnings');
377411

412+
getCacheDirectorySpy.mockRestore();
413+
378414
resolve();
379415
});
380416
});

0 commit comments

Comments
 (0)