Skip to content
This repository was archived by the owner on Dec 5, 2019. It is now read-only.

Commit 249eef3

Browse files
feat: cacheKeys option (#320)
1 parent 2cd4ba0 commit 249eef3

File tree

6 files changed

+242
-5
lines changed

6 files changed

+242
-5
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module.exports = {
4747
|**`include`**|`{RegExp\|Array<RegExp>}`|`undefined`|Files to `include`|
4848
|**`exclude`**|`{RegExp\|Array<RegExp>}`|`undefined`|Files to `exclude`|
4949
|**`cache`**|`{Boolean\|String}`|`false`|Enable file caching|
50+
|**`cacheKeys`**|`{Function(defaultCacheKeys, file) -> {Object}}`|`defaultCacheKeys => defaultCacheKeys`|Allows you to override default cache keys|
5051
|**`parallel`**|`{Boolean\|Number}`|`false`|Use multi-process parallel running to improve the build speed|
5152
|**`sourceMap`**|`{Boolean}`|`false`|Use source maps to map error message locations to modules (This slows down the compilation) ⚠️ **`cheap-source-map` options don't work with this plugin**|
5253
|**`uglifyOptions`**|`{Object}`|[`{...defaults}`](https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/master#uglifyoptions)|`uglify` [Options](https://github.com/mishoo/UglifyJS2/tree/harmony#minify-options)|
@@ -115,6 +116,35 @@ Default path to cache directory: `node_modules/.cache/uglifyjs-webpack-plugin`.
115116

116117
Path to cache directory.
117118

119+
### `cacheKeys`
120+
121+
**webpack.config.js**
122+
```js
123+
[
124+
new UglifyJsPlugin({
125+
cache: true,
126+
cacheKeys: (defaultCacheKeys, file) => {
127+
defaultCacheKeys.myCacheKey = 'myCacheKeyValue';
128+
129+
return defaultCacheKeys;
130+
},
131+
})
132+
]
133+
```
134+
135+
Allows you to override default cache keys.
136+
137+
Default keys:
138+
```js
139+
{
140+
'uglify-es': versions.uglify, // uglify version
141+
'uglifyjs-webpack-plugin': versions.plugin, // plugin version
142+
'uglifyjs-webpack-plugin-options': this.options, // plugin options
143+
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file, // asset path
144+
hash: crypto.createHash('md4').update(input).digest('hex'), // source file hash
145+
}
146+
```
147+
118148
### `parallel`
119149

120150
#### `{Boolean}`

src/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { SourceMapSource, RawSource, ConcatSource } from 'webpack-sources';
88
import RequestShortener from 'webpack/lib/RequestShortener';
99
import ModuleFilenameHelpers from 'webpack/lib/ModuleFilenameHelpers';
1010
import validateOptions from 'schema-utils';
11-
import serialize from 'serialize-javascript';
1211
import schema from './options.json';
1312
import Uglify from './uglify';
1413
import versions from './uglify/versions';
@@ -27,6 +26,7 @@ class UglifyJsPlugin {
2726
extractComments = false,
2827
sourceMap = false,
2928
cache = false,
29+
cacheKeys = defaultCacheKeys => defaultCacheKeys,
3030
parallel = false,
3131
include,
3232
exclude,
@@ -38,6 +38,7 @@ class UglifyJsPlugin {
3838
extractComments,
3939
sourceMap,
4040
cache,
41+
cacheKeys,
4142
parallel,
4243
include,
4344
exclude,
@@ -171,13 +172,15 @@ class UglifyJsPlugin {
171172
};
172173

173174
if (this.options.cache) {
174-
task.cacheKey = serialize({
175+
const defaultCacheKeys = {
175176
'uglify-es': versions.uglify,
176177
'uglifyjs-webpack-plugin': versions.plugin,
177178
'uglifyjs-webpack-plugin-options': this.options,
178179
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file,
179180
hash: crypto.createHash('md4').update(input).digest('hex'),
180-
});
181+
};
182+
183+
task.cacheKeys = this.options.cacheKeys(defaultCacheKeys, file);
181184
}
182185

183186
tasks.push(task);

src/options.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
{ "type": "string" }
1111
]
1212
},
13+
"cacheKeys": {
14+
"instanceof": "Function"
15+
},
1316
"parallel": {
1417
"oneOf": [
1518
{ "type": "boolean" },

src/uglify/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ export default class {
5757
const done = () => step(index, result);
5858

5959
if (this.cacheDir && !result.error) {
60-
cacache.put(this.cacheDir, task.cacheKey, JSON.stringify(data)).then(done, done);
60+
cacache.put(this.cacheDir, serialize(task.cacheKeys), JSON.stringify(data)).then(done, done);
6161
} else {
6262
done();
6363
}
6464
});
6565
};
6666

6767
if (this.cacheDir) {
68-
cacache.get(this.cacheDir, task.cacheKey).then(({ data }) => step(index, JSON.parse(data)), enqueue);
68+
cacache.get(this.cacheDir, serialize(task.cacheKeys)).then(({ data }) => step(index, JSON.parse(data)), enqueue);
6969
} else {
7070
enqueue();
7171
}

test/__snapshots__/cache-options.test.js.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,39 @@ exports[`when options.cache true matches snapshot: main.0c220ec66316af2c1b24.js
7979
exports[`when options.cache true matches snapshot: manifest.d6857f782c13a99b5917.js 1`] = `"!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={1:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,\\"a\\",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p=\\"\\",t.oe=function(r){throw console.error(r),r}}([]);"`;
8080

8181
exports[`when options.cache true matches snapshot: warnings 1`] = `Array []`;
82+
83+
exports[`when options.cache with cacheKey option compilation handler when called optimize-chunk-assets handler cache files: test.js 1`] = `
84+
Array [
85+
"test.js",
86+
"28e4d31993a4f0bf5420d06d6e6013b8",
87+
]
88+
`;
89+
90+
exports[`when options.cache with cacheKey option compilation handler when called optimize-chunk-assets handler cache files: test1.js 1`] = `
91+
Array [
92+
"test1.js",
93+
"0ee8d6a0ecdd7bc2322d1e726ef41cdd",
94+
]
95+
`;
96+
97+
exports[`when options.cache with cacheKey option compilation handler when called optimize-chunk-assets handler cache files: test2.js 1`] = `
98+
Array [
99+
"test2.js",
100+
"ba96de0d701dab1af6ca647f97d42fb7",
101+
]
102+
`;
103+
104+
exports[`when options.cache with cacheKey option compilation handler when called optimize-chunk-assets handler cache files: test3.js 1`] = `
105+
Array [
106+
"test3.js",
107+
"ef8b436e106d1bbfe07b50263e91deac",
108+
]
109+
`;
110+
111+
exports[`when options.cache with cacheKey option matches snapshot: errors 1`] = `Array []`;
112+
113+
exports[`when options.cache with cacheKey option matches snapshot: main.0c220ec66316af2c1b24.js 1`] = `"webpackJsonp([0],[function(o,n){o.exports=function(){console.log(7)}}],[0]);"`;
114+
115+
exports[`when options.cache with cacheKey option matches snapshot: manifest.d6857f782c13a99b5917.js 1`] = `"!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={1:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,\\"a\\",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p=\\"\\",t.oe=function(r){throw console.error(r),r}}([]);"`;
116+
117+
exports[`when options.cache with cacheKey option matches snapshot: warnings 1`] = `Array []`;

test/cache-options.test.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,4 +436,169 @@ describe('when options.cache', () => {
436436
});
437437
});
438438
});
439+
440+
describe('with cacheKey option', () => {
441+
let eventBindings;
442+
let eventBinding;
443+
444+
beforeAll(() => cacache.rm.all(cacheDir));
445+
446+
afterAll(() => cacache.rm.all(cacheDir));
447+
448+
beforeEach(() => {
449+
const pluginEnvironment = new PluginEnvironment();
450+
const compilerEnv = pluginEnvironment.getEnvironmentStub();
451+
compilerEnv.context = '';
452+
453+
const plugin = new UglifyJsPlugin({
454+
cache: true,
455+
cacheKeys: (defaultCacheKeys, file) => {
456+
// eslint-disable-next-line no-param-reassign
457+
defaultCacheKeys.myCacheKey = 1;
458+
// eslint-disable-next-line no-param-reassign
459+
defaultCacheKeys.myCacheKeyBasedOnFile = `file-${file}`;
460+
461+
return defaultCacheKeys;
462+
},
463+
});
464+
plugin.apply(compilerEnv);
465+
eventBindings = pluginEnvironment.getEventBindings();
466+
});
467+
468+
it('binds one event handler', () => {
469+
expect(eventBindings.length).toBe(1);
470+
});
471+
472+
describe('compilation handler', () => {
473+
beforeEach(() => {
474+
[eventBinding] = eventBindings;
475+
});
476+
477+
it('binds to compilation event', () => {
478+
expect(eventBinding.name).toBe('compilation');
479+
});
480+
481+
describe('when called', () => {
482+
let chunkPluginEnvironment;
483+
let compilationEventBindings;
484+
let compilationEventBinding;
485+
let compilation;
486+
let callback;
487+
488+
beforeEach(() => {
489+
chunkPluginEnvironment = new PluginEnvironment();
490+
compilation = chunkPluginEnvironment.getEnvironmentStub();
491+
compilation.assets = Object.assign({}, assets);
492+
compilation.errors = [];
493+
494+
eventBinding.handler(compilation);
495+
compilationEventBindings = chunkPluginEnvironment.getEventBindings();
496+
});
497+
498+
it('binds one event handler', () => {
499+
expect(compilationEventBindings.length).toBe(1);
500+
});
501+
502+
describe('optimize-chunk-assets handler', () => {
503+
beforeEach(() => {
504+
[compilationEventBinding] = compilationEventBindings;
505+
});
506+
507+
it('binds to optimize-chunk-assets event', () => {
508+
expect(compilationEventBinding.name).toEqual('optimize-chunk-assets');
509+
});
510+
511+
it('only calls callback once', (done) => {
512+
callback = jest.fn();
513+
compilationEventBinding.handler([''], () => {
514+
callback();
515+
expect(callback.mock.calls.length).toBe(1);
516+
done();
517+
});
518+
});
519+
520+
it('cache files', (done) => {
521+
const files = ['test.js', 'test1.js', 'test2.js', 'test3.js'];
522+
523+
cacache.get = jest.fn(cacache.get);
524+
cacache.put = jest.fn(cacache.put);
525+
526+
compilationEventBinding.handler([{
527+
files,
528+
}], () => {
529+
// Try to found cached files, but we don't have their in cache
530+
expect(cacache.get.mock.calls.length).toBe(4);
531+
// Put files in cache
532+
expect(cacache.put.mock.calls.length).toBe(4);
533+
534+
cacache
535+
.ls(cacheDir)
536+
.then((cacheEntriesList) => {
537+
const cacheKeys = Object.keys(cacheEntriesList);
538+
539+
// Make sure that we cached files
540+
expect(cacheKeys.length).toBe(files.length);
541+
cacheKeys.forEach((cacheEntry) => {
542+
// eslint-disable-next-line no-new-func
543+
const cacheEntryOptions = new Function(`'use strict'\nreturn ${cacheEntry}`)();
544+
545+
expect(cacheEntryOptions.myCacheKey).toBe(1);
546+
expect(cacheEntryOptions.myCacheKeyBasedOnFile).toMatch(/file-test(.)?\.js/);
547+
expect([cacheEntryOptions.path, cacheEntryOptions.hash])
548+
.toMatchSnapshot(cacheEntryOptions.path);
549+
});
550+
551+
// Reset compilation assets and mocks
552+
compilation.assets = Object.assign({}, assets);
553+
compilation.errors = [];
554+
555+
cacache.get.mockClear();
556+
cacache.put.mockClear();
557+
558+
compilationEventBinding.handler([{
559+
files,
560+
}], () => {
561+
// Now we have cached files so we get their and don't put
562+
expect(cacache.get.mock.calls.length).toBe(4);
563+
expect(cacache.put.mock.calls.length).toBe(0);
564+
565+
done();
566+
});
567+
});
568+
});
569+
});
570+
});
571+
});
572+
});
573+
574+
it('matches snapshot', () => {
575+
const compiler = createCompiler();
576+
new UglifyJsPlugin({
577+
cache: true,
578+
cacheKeys: (defaultCacheKeys, file) => {
579+
// eslint-disable-next-line no-param-reassign
580+
defaultCacheKeys.myCacheKey = 1;
581+
// eslint-disable-next-line no-param-reassign
582+
defaultCacheKeys.myCacheLeyBasedOnFile = `file-${file}`;
583+
584+
return defaultCacheKeys;
585+
},
586+
}).apply(compiler);
587+
588+
return compile(compiler)
589+
.then((stats) => {
590+
const errors = stats.compilation.errors.map(cleanErrorStack);
591+
const warnings = stats.compilation.warnings.map(cleanErrorStack);
592+
593+
expect(errors).toMatchSnapshot('errors');
594+
expect(warnings).toMatchSnapshot('warnings');
595+
596+
for (const file in stats.compilation.assets) {
597+
if (Object.prototype.hasOwnProperty.call(stats.compilation.assets, file)) {
598+
expect(stats.compilation.assets[file].source()).toMatchSnapshot(file);
599+
}
600+
}
601+
});
602+
});
603+
});
439604
});

0 commit comments

Comments
 (0)