Skip to content

Commit

Permalink
feature #884 [Webpack 5] Adding new enableBuildCache() method for Web…
Browse files Browse the repository at this point in the history
…pack 5 persistent caching (weaverryan)

This PR was squashed before being merged into the main branch.

Discussion
----------

[Webpack 5] Adding new enableBuildCache() method for Webpack 5 persistent caching

Hi!

This is a very simple feature, but it can also easily be improperly used. It is part of #880. See some comments about it from @Lyrkan from #645:

> Things can easily go wrong with persistent caching... even only enabling it for the config file could lead to some issues if not done properly (for instance if the config use values coming from environment variables, which is far from an edge case imo: https://github.com/webpack/changelog-v5/blob/master/guides/persistent-caching.md#version).

The trick, for us, is to:

A) Make sure we are doing everything as correctly as we can. For example, I assume that WE don't need to include `.babelrc` or `postcss.config.js`, but I actually don't know that for sure. Should we also potentially be adding Encore itself to the build dependencies?

B) Good communication in the documentation above the method and in the (eventual) recipe where we include this in the user's webpack.config.js file

## TODO

* [ ] 1) Test in a real project to see if we're missing anything (also play with what the valid keys are under `buildDependencies` - other than `config`, this is not documented anywhere).
* [x] ~~2) Check into Stimulus: I'm curious if `controllers.json` will need to be added to the build dependencies. I'm also curious if the UX vendor libraries will need to be in the build dependencies, as these do not follow the normal versioning (i.e. their directories in `node_modules/` can change without a change to `yarn.lock` or `package.json`)~~ should be solved by linked changes in #888

Cheers!

Commits
-------

a592c82 lint
9f117a4 fixing test
7a39654 Adding new enableBuildCache() method for Webpack 5 persistent caching
  • Loading branch information
weaverryan committed Jan 23, 2021
2 parents b7e25d2 + a592c82 commit b1af95b
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
added to allow the `MiniCssExtractPlugin.loader` and `MiniCssExtractPlugin`
to be configured.

* [enableBuildCache()] Added `enableBuildCache()` to enable the new
Webpack 5 build caching. https://webpack.js.org/blog/2020-10-10-webpack-5-release/
This feature should be considered experimental.

## 0.33.0

* [disableCssExtraction()] Added boolean argument to `disableCssExtraction()`
Expand Down
31 changes: 31 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,37 @@ class Encore {
return this;
}

/**
* Enables & configures persistent build caching.
*
* https://webpack.js.org/blog/2020-10-10-webpack-5-release/#persistent-caching
*
* ```
* Encore.enableBuildCache({
* // object of "buildDependencies"
* // https://webpack.js.org/configuration/other-options/#cachebuilddependencies
* // __filename means that changes to webpack.config.js should invalidate the cache
* config: [__filename],
* });
**
* // also configure other options the Webpack "cache" key
* Encore.enableBuildCache({ config: [__filename] }, (cache) => {
* cache.version: `${process.env.GIT_REV}`;
*
* cache.name: `${env.target}`
* });
* ```
*
* @param {object} buildDependencies
* @param {function} cacheCallback
* @returns {Encore}
*/
enableBuildCache(buildDependencies, cacheCallback = (cache) => {}) {
webpackConfig.enableBuildCache(buildDependencies, cacheCallback);

return this;
}

/**
* Configure the mini-css-extract-plugin.
*
Expand Down
22 changes: 22 additions & 0 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class WebpackConfig {
this.useVersioning = false;
this.useSourceMaps = false;
this.cleanupOutput = false;
this.usePersistentCache = false;
this.extractCss = true;
this.imageRuleOptions = {
type: 'asset/resource',
Expand Down Expand Up @@ -150,6 +151,7 @@ class WebpackConfig {
this.eslintOptions = {
lintVue: false,
};
this.persistentCacheBuildDependencies = {};

// Features/Loaders options callbacks
this.imageRuleCallback = () => {};
Expand Down Expand Up @@ -197,6 +199,7 @@ class WebpackConfig {
this.terserPluginOptionsCallback = () => {};
this.cssMinimizerPluginOptionsCallback = () => {};
this.notifierPluginOptionsCallback = () => {};
this.persistentCacheCallback = () => {};
}

getContext() {
Expand Down Expand Up @@ -685,6 +688,25 @@ class WebpackConfig {
this.stimulusOptions.controllersJsonPath = controllerJsonPath;
}

enableBuildCache(buildDependencies, callback = (cache) => {}) {
if (typeof buildDependencies !== 'object') {
throw new Error('Argument 1 to enableBuildCache() must be an object.');
}

if (!buildDependencies.config) {
throw new Error('Argument 1 to enableBuildCache() should contain an object with at least a "config" key. See the documentation for this method.');
}

this.usePersistentCache = true;
this.persistentCacheBuildDependencies = buildDependencies;

if (typeof callback !== 'function') {
throw new Error('Argument 2 to enableBuildCache() must be a callback function.');
}

this.persistentCacheCallback = callback;
}

enableReactPreset() {
this.useReact = true;
}
Expand Down
19 changes: 19 additions & 0 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class ConfigGenerator {
},
plugins: this.buildPluginsConfig(),
optimization: this.buildOptimizationConfig(),
cache: this.buildCacheConfig(),
watchOptions: this.buildWatchOptionsConfig(),
devtool: false,
};
Expand Down Expand Up @@ -509,6 +510,24 @@ class ConfigGenerator {
return optimization;
}

buildCacheConfig() {
if (!this.webpackConfig.usePersistentCache) {
return false;
}

const cache = {};

cache.type = 'filesystem';
cache.buildDependencies = this.webpackConfig.persistentCacheBuildDependencies;

applyOptionsCallback(
this.webpackConfig.persistentCacheCallback,
cache
);

return cache;
}

buildStatsConfig() {
// try to silence as much as possible: the output is rarely helpful
// this still doesn't remove all output
Expand Down
34 changes: 34 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,40 @@ describe('WebpackConfig object', () => {
});
});

describe('enableBuildCache', () => {
it('Calling method enables it', () => {
const config = createConfig();
config.enableBuildCache({ config: ['foo.js'] });

expect(config.usePersistentCache).to.be.true;
expect(config.persistentCacheBuildDependencies).to.eql({ config: ['foo.js'] });
});

it('Calling with callback', () => {
const config = createConfig();
const callback = (cache) => {};
config.enableBuildCache({ config: ['foo.js'] }, callback);

expect(config.persistentCacheCallback).to.equal(callback);
});

it('Calling without config key throws an error', () => {
const config = createConfig();

expect(() => {
config.enableBuildCache({});
}).to.throw('should contain an object with at least a "config" key');
});

it('Calling with non-callback throws an error', () => {
const config = createConfig();

expect(() => {
config.enableBuildCache({ config: ['foo.js'] }, 'FOO');
}).to.throw('must be a callback function');
});
});

describe('enablePreactPreset', () => {
it('Without preact-compat', () => {
const config = createConfig();
Expand Down
32 changes: 32 additions & 0 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,38 @@ describe('The config-generator function', () => {
});
});

describe('Test enableBuildCache()', () => {
it('with full arguments', () => {
const config = createConfig();
config.outputPath = '/tmp/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');
config.enableBuildCache({ config: ['foo.js'] }, (cache) => {
cache.version = 5;
});

const actualConfig = configGenerator(config);
expect(actualConfig.cache).to.eql({
type: 'filesystem',
buildDependencies: { config: ['foo.js'] },
version: 5
});
});

it('with sourcemaps', () => {
const config = createConfig();
config.outputPath = '/tmp/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');
config.useSourceMaps = true;

const actualConfig = configGenerator(config);
expect(actualConfig.devtool).to.equal('inline-source-map');

expect(JSON.stringify(actualConfig.module.rules)).to.contain('"sourceMap":true');
});
});

describe('Test configureBabel()', () => {
it('without configureBabel()', () => {
const config = createConfig();
Expand Down
17 changes: 17 additions & 0 deletions test/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,23 @@ describe('Functional tests using webpack', function() {
});
});

it('Persistent caching does not cause problems', (done) => {
const config = createWebpackConfig('www/build', 'dev');
config.setPublicPath('/build');
config.addEntry('main', './js/code_splitting');
config.enableBuildCache({ config: [__filename] });

testSetup.runWebpack(config, (webpackAssert) => {
// sanity check
webpackAssert.assertManifestPath(
'build/main.js',
'/build/main.js'
);

done();
});
});

describe('addCacheGroup()', () => {
it('addCacheGroup() to extract a vendor into its own chunk', (done) => {
const config = createWebpackConfig('www/build', 'dev');
Expand Down
20 changes: 20 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

const expect = require('chai').expect;
const api = require('../index');
const path = require('path');

describe('Public API', () => {
beforeEach(() => {
Expand Down Expand Up @@ -416,6 +417,25 @@ describe('Public API', () => {

});

describe('enableStimulusBridge', () => {

it('should return the API object', () => {
const returnedValue = api.enableStimulusBridge(path.resolve(__dirname, '../', 'package.json'));
expect(returnedValue).to.equal(api);
});

});

describe('enableBuildCache', () => {

it('should return the API object', () => {
const returnedValue = api.enableBuildCache({ config: [__filename] });
expect(returnedValue).to.equal(api);
});

});


describe('configureMiniCssExtractPlugin', () => {

it('should return the API object', () => {
Expand Down

0 comments on commit b1af95b

Please sign in to comment.