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

Commit

Permalink
feat: cacheKeys option
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jun 29, 2018
1 parent f0d0a3b commit 142497e
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 5 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|
|**`cacheKeys`**|`{Function(defaultCacheKeys, file) -> {Object}}`|`defaultCacheKeys => defaultCacheKeys`|Allows you to override default cache keys|
|**`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.

### `cacheKeys`

**webpack.config.js**
```js
[
new UglifyJsPlugin({
cache: true,
cacheKeys: (defaultCacheKeys, file, options) => {
defaultCacheKeys.myCacheKey = 'myCacheKeyValue';

return defaultCacheKeys;
},
})
]
```

Allows you to override default cache keys.

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
9 changes: 6 additions & 3 deletions src/index.js
Expand Up @@ -8,7 +8,6 @@ import { SourceMapSource, RawSource, ConcatSource } from 'webpack-sources';
import RequestShortener from 'webpack/lib/RequestShortener';
import ModuleFilenameHelpers from 'webpack/lib/ModuleFilenameHelpers';
import validateOptions from 'schema-utils';
import serialize from 'serialize-javascript';
import schema from './options.json';
import Uglify from './uglify';
import versions from './uglify/versions';
Expand All @@ -27,6 +26,7 @@ class UglifyJsPlugin {
extractComments = false,
sourceMap = false,
cache = false,
cacheKeys = defaultCacheKeys => defaultCacheKeys,
parallel = false,
include,
exclude,
Expand All @@ -38,6 +38,7 @@ class UglifyJsPlugin {
extractComments,
sourceMap,
cache,
cacheKeys,
parallel,
include,
exclude,
Expand Down Expand Up @@ -171,13 +172,15 @@ class UglifyJsPlugin {
};

if (this.options.cache) {
task.cacheKey = serialize({
const defaultCacheKeys = {
'uglify-es': versions.uglify,
'uglifyjs-webpack-plugin': versions.plugin,
'uglifyjs-webpack-plugin-options': this.options,
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file,
hash: crypto.createHash('md4').update(input).digest('hex'),
});
};

task.cacheKeys = this.options.cacheKeys(defaultCacheKeys, file);
}

tasks.push(task);
Expand Down
3 changes: 3 additions & 0 deletions src/options.json
Expand Up @@ -10,6 +10,9 @@
{ "type": "string" }
]
},
"cacheKeys": {
"instanceof": "Function"
},
"parallel": {
"oneOf": [
{ "type": "boolean" },
Expand Down
4 changes: 2 additions & 2 deletions src/uglify/index.js
Expand Up @@ -57,15 +57,15 @@ export default class {
const done = () => step(index, result);

if (this.cacheDir && !result.error) {
cacache.put(this.cacheDir, task.cacheKey, JSON.stringify(data)).then(done, done);
cacache.put(this.cacheDir, serialize(task.cacheKeys), JSON.stringify(data)).then(done, done);
} else {
done();
}
});
};

if (this.cacheDir) {
cacache.get(this.cacheDir, task.cacheKey).then(({ data }) => step(index, JSON.parse(data)), enqueue);
cacache.get(this.cacheDir, serialize(task.cacheKeys)).then(({ data }) => step(index, JSON.parse(data)), enqueue);
} else {
enqueue();
}
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 with cacheKey option compilation handler when called optimize-chunk-assets handler cache files: test.js 1`] = `
Array [
"test.js",
"28e4d31993a4f0bf5420d06d6e6013b8",
]
`;

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

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

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

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

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]);"`;

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}}([]);"`;

exports[`when options.cache with cacheKey option matches snapshot: warnings 1`] = `Array []`;
165 changes: 165 additions & 0 deletions test/cache-options.test.js
Expand Up @@ -436,4 +436,169 @@ describe('when options.cache', () => {
});
});
});

describe('with cacheKey option', () => {
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,
cacheKeys: (defaultCacheKeys, file) => {
// eslint-disable-next-line no-param-reassign
defaultCacheKeys.myCacheKey = 1;
// eslint-disable-next-line no-param-reassign
defaultCacheKeys.myCacheKeyBasedOnFile = `file-${file}`;

return defaultCacheKeys;
},
});
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.myCacheKey).toBe(1);
expect(cacheEntryOptions.myCacheKeyBasedOnFile).toMatch(/file-test(.)?\.js/);
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,
cacheKeys: (defaultCacheKeys, file) => {
// eslint-disable-next-line no-param-reassign
defaultCacheKeys.myCacheKey = 1;
// eslint-disable-next-line no-param-reassign
defaultCacheKeys.myCacheLeyBasedOnFile = `file-${file}`;

return defaultCacheKeys;
},
}).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);
}
}
});
});
});
});

0 comments on commit 142497e

Please sign in to comment.