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

Commit

Permalink
feature: add support for additional cache keys option
Browse files Browse the repository at this point in the history
  • Loading branch information
Rohit-L committed May 28, 2018
1 parent 3f0767b commit 0e42f2e
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 9 deletions.
30 changes: 30 additions & 0 deletions README.md
Expand Up @@ -44,6 +44,7 @@ module.exports = {
|**`include`**|`{RegExp\|Array<RegExp>}`|`undefined`|Files to `include`|
|**`exclude`**|`{RegExp\|Array<RegExp>}`|`undefined`|Files to `exclude`|
|**`cache`**|`{Boolean\|String}`|`false`|Enable file caching|
|**`additionalCacheKeys`**|`{Object}`|`{}`|Add additional keys to the default cache key generator|
|**`parallel`**|`{Boolean\|Number}`|`false`|Use multi-process parallel running to improve the build speed|
|**`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**|
|**`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)|
Expand Down Expand Up @@ -112,6 +113,35 @@ Default path to cache directory: `node_modules/.cache/uglifyjs-webpack-plugin`.

Path to cache directory.

### `additionalCacheKeys`

#### `{Object}`

**webpack.config.js**
```js
[
new UglifyJsPlugin({
cache: true,
additionalCacheKeys: {
myCustomKey: 'myCustomValue',
}
})
]
```

Add additional keys to the default cache key generator.

Default keys:
```js
{
'uglify-es': versions.uglify, // uglify version
'uglifyjs-webpack-plugin': versions.plugin, // plugin version
'uglifyjs-webpack-plugin-options': this.options, // plugin options
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file, // asset path
hash: crypto.createHash('md4').update(input).digest('hex'), // source file hash
}
```

### `parallel`

#### `{Boolean}`
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Expand Up @@ -27,6 +27,7 @@ class UglifyJsPlugin {
extractComments = false,
sourceMap = false,
cache = false,
additionalCacheKeys = {},
parallel = false,
include,
exclude,
Expand All @@ -38,6 +39,7 @@ class UglifyJsPlugin {
extractComments,
sourceMap,
cache,
additionalCacheKeys,
parallel,
include,
exclude,
Expand Down Expand Up @@ -182,6 +184,7 @@ class UglifyJsPlugin {
'uglifyjs-webpack-plugin-options': this.options,
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file,
hash: crypto.createHash('md4').update(input).digest('hex'),
...this.options.additionalCacheKeys,
});
}

Expand Down
4 changes: 4 additions & 0 deletions src/options.json
Expand Up @@ -10,6 +10,10 @@
{ "type": "string" }
]
},
"additionalCacheKeys": {
"type": "object",
"additionalProperties": true
},
"parallel": {
"oneOf": [
{ "type": "boolean" },
Expand Down
36 changes: 36 additions & 0 deletions test/__snapshots__/cache-options.test.js.snap
Expand Up @@ -79,3 +79,39 @@ exports[`when options.cache true matches snapshot: main.0c220ec66316af2c1b24.js
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}}([]);"`;

exports[`when options.cache true matches snapshot: warnings 1`] = `Array []`;

exports[`when options.cache when options.additionalCacheKeys compilation handler when called optimize-chunk-assets handler cache files: test.js 1`] = `
Array [
"test.js",
"28e4d31993a4f0bf5420d06d6e6013b8",
]
`;

exports[`when options.cache when options.additionalCacheKeys compilation handler when called optimize-chunk-assets handler cache files: test1.js 1`] = `
Array [
"test1.js",
"0ee8d6a0ecdd7bc2322d1e726ef41cdd",
]
`;

exports[`when options.cache when options.additionalCacheKeys compilation handler when called optimize-chunk-assets handler cache files: test2.js 1`] = `
Array [
"test2.js",
"ba96de0d701dab1af6ca647f97d42fb7",
]
`;

exports[`when options.cache when options.additionalCacheKeys compilation handler when called optimize-chunk-assets handler cache files: test3.js 1`] = `
Array [
"test3.js",
"ef8b436e106d1bbfe07b50263e91deac",
]
`;

exports[`when options.cache when options.additionalCacheKeys matches snapshot: errors 1`] = `Array []`;

exports[`when options.cache when options.additionalCacheKeys matches snapshot: main.0c220ec66316af2c1b24.js 1`] = `"webpackJsonp([0],[function(o,n){o.exports=function(){console.log(7)}}],[0]);"`;

exports[`when options.cache when options.additionalCacheKeys 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}}([]);"`;

exports[`when options.cache when options.additionalCacheKeys matches snapshot: warnings 1`] = `Array []`;
32 changes: 23 additions & 9 deletions test/__snapshots__/invalid-options.test.js.snap
Expand Up @@ -154,13 +154,27 @@ options.cache should match exactly one schema in oneOf
exports[`when applied with invalid options throws validation errors 3`] = `
"UglifyJs Plugin Invalid Options
options.additionalCacheKeys should be object
"
`;

exports[`when applied with invalid options throws validation errors 4`] = `
"UglifyJs Plugin Invalid Options
options.additionalCacheKeys should be object
"
`;

exports[`when applied with invalid options throws validation errors 5`] = `
"UglifyJs Plugin Invalid Options
options.parallel should be boolean
options.parallel should be integer
options.parallel should match exactly one schema in oneOf
"
`;

exports[`when applied with invalid options throws validation errors 4`] = `
exports[`when applied with invalid options throws validation errors 6`] = `
"UglifyJs Plugin Invalid Options
options.parallel should be boolean
Expand All @@ -169,56 +183,56 @@ options.parallel should match exactly one schema in oneOf
"
`;

exports[`when applied with invalid options throws validation errors 5`] = `
exports[`when applied with invalid options throws validation errors 7`] = `
"UglifyJs Plugin Invalid Options
options.sourceMap should be boolean
"
`;

exports[`when applied with invalid options throws validation errors 6`] = `
exports[`when applied with invalid options throws validation errors 8`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions should be object
"
`;

exports[`when applied with invalid options throws validation errors 7`] = `
exports[`when applied with invalid options throws validation errors 9`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ie8 should be boolean
"
`;

exports[`when applied with invalid options throws validation errors 8`] = `
exports[`when applied with invalid options throws validation errors 10`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ecma should be integer
"
`;

exports[`when applied with invalid options throws validation errors 9`] = `
exports[`when applied with invalid options throws validation errors 11`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ecma should be integer
"
`;

exports[`when applied with invalid options throws validation errors 10`] = `
exports[`when applied with invalid options throws validation errors 12`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ecma should be integer
"
`;

exports[`when applied with invalid options throws validation errors 11`] = `
exports[`when applied with invalid options throws validation errors 13`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ecma should be >= 5
"
`;

exports[`when applied with invalid options throws validation errors 12`] = `
exports[`when applied with invalid options throws validation errors 14`] = `
"UglifyJs Plugin Invalid Options
options.uglifyOptions.ecma should be <= 8
Expand Down
148 changes: 148 additions & 0 deletions test/cache-options.test.js
Expand Up @@ -436,4 +436,152 @@ describe('when options.cache', () => {
});
});
});

describe('when options.additionalCacheKeys', () => {
let eventBindings;
let eventBinding;

beforeAll(() => cacache.rm.all(cacheDir));

afterAll(() => cacache.rm.all(cacheDir));

beforeEach(() => {
const pluginEnvironment = new PluginEnvironment();
const compilerEnv = pluginEnvironment.getEnvironmentStub();
compilerEnv.context = '';

const plugin = new UglifyJsPlugin({
cache: true,
additionalCacheKeys: {
myCustomKey: 'myCustomValue',
},
});
plugin.apply(compilerEnv);
eventBindings = pluginEnvironment.getEventBindings();
});

it('binds one event handler', () => {
expect(eventBindings.length).toBe(1);
});

describe('compilation handler', () => {
beforeEach(() => {
[eventBinding] = eventBindings;
});

it('binds to compilation event', () => {
expect(eventBinding.name).toBe('compilation');
});

describe('when called', () => {
let chunkPluginEnvironment;
let compilationEventBindings;
let compilationEventBinding;
let compilation;
let callback;

beforeEach(() => {
chunkPluginEnvironment = new PluginEnvironment();
compilation = chunkPluginEnvironment.getEnvironmentStub();
compilation.assets = Object.assign({}, assets);
compilation.errors = [];

eventBinding.handler(compilation);
compilationEventBindings = chunkPluginEnvironment.getEventBindings();
});

it('binds one event handler', () => {
expect(compilationEventBindings.length).toBe(1);
});

describe('optimize-chunk-assets handler', () => {
beforeEach(() => {
[compilationEventBinding] = compilationEventBindings;
});

it('binds to optimize-chunk-assets event', () => {
expect(compilationEventBinding.name).toEqual('optimize-chunk-assets');
});

it('only calls callback once', (done) => {
callback = jest.fn();
compilationEventBinding.handler([''], () => {
callback();
expect(callback.mock.calls.length).toBe(1);
done();
});
});

it('cache files', (done) => {
const files = ['test.js', 'test1.js', 'test2.js', 'test3.js'];

cacache.get = jest.fn(cacache.get);
cacache.put = jest.fn(cacache.put);

compilationEventBinding.handler([{
files,
}], () => {
// Try to found cached files, but we don't have their in cache
expect(cacache.get.mock.calls.length).toBe(4);
// Put files in cache
expect(cacache.put.mock.calls.length).toBe(4);

cacache
.ls(cacheDir)
.then((cacheEntriesList) => {
const cacheKeys = Object.keys(cacheEntriesList);

// Make sure that we cached files
expect(cacheKeys.length).toBe(files.length);
cacheKeys.forEach((cacheEntry) => {
// eslint-disable-next-line no-new-func
const cacheEntryOptions = new Function(`'use strict'\nreturn ${cacheEntry}`)();

expect([cacheEntryOptions.path, cacheEntryOptions.hash])
.toMatchSnapshot(cacheEntryOptions.path);
});

// Reset compilation assets and mocks
compilation.assets = Object.assign({}, assets);
compilation.errors = [];

cacache.get.mockClear();
cacache.put.mockClear();

compilationEventBinding.handler([{
files,
}], () => {
// Now we have cached files so we get their and don't put
expect(cacache.get.mock.calls.length).toBe(4);
expect(cacache.put.mock.calls.length).toBe(0);

done();
});
});
});
});
});
});
});

it('matches snapshot', () => {
const compiler = createCompiler();
new UglifyJsPlugin({ cache: true }).apply(compiler);

return compile(compiler)
.then((stats) => {
const errors = stats.compilation.errors.map(cleanErrorStack);
const warnings = stats.compilation.warnings.map(cleanErrorStack);

expect(errors).toMatchSnapshot('errors');
expect(warnings).toMatchSnapshot('warnings');

for (const file in stats.compilation.assets) {
if (Object.prototype.hasOwnProperty.call(stats.compilation.assets, file)) {
expect(stats.compilation.assets[file].source()).toMatchSnapshot(file);
}
}
});
});
});
});
12 changes: 12 additions & 0 deletions test/invalid-options.test.js
Expand Up @@ -54,6 +54,18 @@ describe('when applied with invalid options', () => {
new UglifyJsPlugin({ cache: {} });
}).toThrowErrorMatchingSnapshot();

expect(() => {
new UglifyJsPlugin({ additionalCacheKeys: false });
}).toThrowErrorMatchingSnapshot();

expect(() => {
new UglifyJsPlugin({ additionalCacheKeys: 'additional-cache-key' });
}).toThrowErrorMatchingSnapshot();

expect(() => {
new UglifyJsPlugin({ additionalCacheKeys: { path: 'path/to/output' } });
}).not.toThrow('Validation Error');

expect(() => {
new UglifyJsPlugin({ parallel: true });
}).not.toThrow('Validation Error');
Expand Down

0 comments on commit 0e42f2e

Please sign in to comment.