Skip to content

Commit b1af95b

Browse files
committed
feature #884 [Webpack 5] Adding new enableBuildCache() method for Webpack 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
2 parents b7e25d2 + a592c82 commit b1af95b

8 files changed

+179
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
added to allow the `MiniCssExtractPlugin.loader` and `MiniCssExtractPlugin`
5353
to be configured.
5454

55+
* [enableBuildCache()] Added `enableBuildCache()` to enable the new
56+
Webpack 5 build caching. https://webpack.js.org/blog/2020-10-10-webpack-5-release/
57+
This feature should be considered experimental.
58+
5559
## 0.33.0
5660

5761
* [disableCssExtraction()] Added boolean argument to `disableCssExtraction()`

index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,37 @@ class Encore {
10051005
return this;
10061006
}
10071007

1008+
/**
1009+
* Enables & configures persistent build caching.
1010+
*
1011+
* https://webpack.js.org/blog/2020-10-10-webpack-5-release/#persistent-caching
1012+
*
1013+
* ```
1014+
* Encore.enableBuildCache({
1015+
* // object of "buildDependencies"
1016+
* // https://webpack.js.org/configuration/other-options/#cachebuilddependencies
1017+
* // __filename means that changes to webpack.config.js should invalidate the cache
1018+
* config: [__filename],
1019+
* });
1020+
**
1021+
* // also configure other options the Webpack "cache" key
1022+
* Encore.enableBuildCache({ config: [__filename] }, (cache) => {
1023+
* cache.version: `${process.env.GIT_REV}`;
1024+
*
1025+
* cache.name: `${env.target}`
1026+
* });
1027+
* ```
1028+
*
1029+
* @param {object} buildDependencies
1030+
* @param {function} cacheCallback
1031+
* @returns {Encore}
1032+
*/
1033+
enableBuildCache(buildDependencies, cacheCallback = (cache) => {}) {
1034+
webpackConfig.enableBuildCache(buildDependencies, cacheCallback);
1035+
1036+
return this;
1037+
}
1038+
10081039
/**
10091040
* Configure the mini-css-extract-plugin.
10101041
*

lib/WebpackConfig.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class WebpackConfig {
9696
this.useVersioning = false;
9797
this.useSourceMaps = false;
9898
this.cleanupOutput = false;
99+
this.usePersistentCache = false;
99100
this.extractCss = true;
100101
this.imageRuleOptions = {
101102
type: 'asset/resource',
@@ -150,6 +151,7 @@ class WebpackConfig {
150151
this.eslintOptions = {
151152
lintVue: false,
152153
};
154+
this.persistentCacheBuildDependencies = {};
153155

154156
// Features/Loaders options callbacks
155157
this.imageRuleCallback = () => {};
@@ -197,6 +199,7 @@ class WebpackConfig {
197199
this.terserPluginOptionsCallback = () => {};
198200
this.cssMinimizerPluginOptionsCallback = () => {};
199201
this.notifierPluginOptionsCallback = () => {};
202+
this.persistentCacheCallback = () => {};
200203
}
201204

202205
getContext() {
@@ -685,6 +688,25 @@ class WebpackConfig {
685688
this.stimulusOptions.controllersJsonPath = controllerJsonPath;
686689
}
687690

691+
enableBuildCache(buildDependencies, callback = (cache) => {}) {
692+
if (typeof buildDependencies !== 'object') {
693+
throw new Error('Argument 1 to enableBuildCache() must be an object.');
694+
}
695+
696+
if (!buildDependencies.config) {
697+
throw new Error('Argument 1 to enableBuildCache() should contain an object with at least a "config" key. See the documentation for this method.');
698+
}
699+
700+
this.usePersistentCache = true;
701+
this.persistentCacheBuildDependencies = buildDependencies;
702+
703+
if (typeof callback !== 'function') {
704+
throw new Error('Argument 2 to enableBuildCache() must be a callback function.');
705+
}
706+
707+
this.persistentCacheCallback = callback;
708+
}
709+
688710
enableReactPreset() {
689711
this.useReact = true;
690712
}

lib/config-generator.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class ConfigGenerator {
6767
},
6868
plugins: this.buildPluginsConfig(),
6969
optimization: this.buildOptimizationConfig(),
70+
cache: this.buildCacheConfig(),
7071
watchOptions: this.buildWatchOptionsConfig(),
7172
devtool: false,
7273
};
@@ -509,6 +510,24 @@ class ConfigGenerator {
509510
return optimization;
510511
}
511512

513+
buildCacheConfig() {
514+
if (!this.webpackConfig.usePersistentCache) {
515+
return false;
516+
}
517+
518+
const cache = {};
519+
520+
cache.type = 'filesystem';
521+
cache.buildDependencies = this.webpackConfig.persistentCacheBuildDependencies;
522+
523+
applyOptionsCallback(
524+
this.webpackConfig.persistentCacheCallback,
525+
cache
526+
);
527+
528+
return cache;
529+
}
530+
512531
buildStatsConfig() {
513532
// try to silence as much as possible: the output is rarely helpful
514533
// this still doesn't remove all output

test/WebpackConfig.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,40 @@ describe('WebpackConfig object', () => {
906906
});
907907
});
908908

909+
describe('enableBuildCache', () => {
910+
it('Calling method enables it', () => {
911+
const config = createConfig();
912+
config.enableBuildCache({ config: ['foo.js'] });
913+
914+
expect(config.usePersistentCache).to.be.true;
915+
expect(config.persistentCacheBuildDependencies).to.eql({ config: ['foo.js'] });
916+
});
917+
918+
it('Calling with callback', () => {
919+
const config = createConfig();
920+
const callback = (cache) => {};
921+
config.enableBuildCache({ config: ['foo.js'] }, callback);
922+
923+
expect(config.persistentCacheCallback).to.equal(callback);
924+
});
925+
926+
it('Calling without config key throws an error', () => {
927+
const config = createConfig();
928+
929+
expect(() => {
930+
config.enableBuildCache({});
931+
}).to.throw('should contain an object with at least a "config" key');
932+
});
933+
934+
it('Calling with non-callback throws an error', () => {
935+
const config = createConfig();
936+
937+
expect(() => {
938+
config.enableBuildCache({ config: ['foo.js'] }, 'FOO');
939+
}).to.throw('must be a callback function');
940+
});
941+
});
942+
909943
describe('enablePreactPreset', () => {
910944
it('Without preact-compat', () => {
911945
const config = createConfig();

test/config-generator.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,38 @@ describe('The config-generator function', () => {
913913
});
914914
});
915915

916+
describe('Test enableBuildCache()', () => {
917+
it('with full arguments', () => {
918+
const config = createConfig();
919+
config.outputPath = '/tmp/public-path';
920+
config.publicPath = '/public-path';
921+
config.addEntry('main', './main');
922+
config.enableBuildCache({ config: ['foo.js'] }, (cache) => {
923+
cache.version = 5;
924+
});
925+
926+
const actualConfig = configGenerator(config);
927+
expect(actualConfig.cache).to.eql({
928+
type: 'filesystem',
929+
buildDependencies: { config: ['foo.js'] },
930+
version: 5
931+
});
932+
});
933+
934+
it('with sourcemaps', () => {
935+
const config = createConfig();
936+
config.outputPath = '/tmp/public-path';
937+
config.publicPath = '/public-path';
938+
config.addEntry('main', './main');
939+
config.useSourceMaps = true;
940+
941+
const actualConfig = configGenerator(config);
942+
expect(actualConfig.devtool).to.equal('inline-source-map');
943+
944+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('"sourceMap":true');
945+
});
946+
});
947+
916948
describe('Test configureBabel()', () => {
917949
it('without configureBabel()', () => {
918950
const config = createConfig();

test/functional.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,23 @@ describe('Functional tests using webpack', function() {
731731
});
732732
});
733733

734+
it('Persistent caching does not cause problems', (done) => {
735+
const config = createWebpackConfig('www/build', 'dev');
736+
config.setPublicPath('/build');
737+
config.addEntry('main', './js/code_splitting');
738+
config.enableBuildCache({ config: [__filename] });
739+
740+
testSetup.runWebpack(config, (webpackAssert) => {
741+
// sanity check
742+
webpackAssert.assertManifestPath(
743+
'build/main.js',
744+
'/build/main.js'
745+
);
746+
747+
done();
748+
});
749+
});
750+
734751
describe('addCacheGroup()', () => {
735752
it('addCacheGroup() to extract a vendor into its own chunk', (done) => {
736753
const config = createWebpackConfig('www/build', 'dev');

test/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
const expect = require('chai').expect;
1313
const api = require('../index');
14+
const path = require('path');
1415

1516
describe('Public API', () => {
1617
beforeEach(() => {
@@ -416,6 +417,25 @@ describe('Public API', () => {
416417

417418
});
418419

420+
describe('enableStimulusBridge', () => {
421+
422+
it('should return the API object', () => {
423+
const returnedValue = api.enableStimulusBridge(path.resolve(__dirname, '../', 'package.json'));
424+
expect(returnedValue).to.equal(api);
425+
});
426+
427+
});
428+
429+
describe('enableBuildCache', () => {
430+
431+
it('should return the API object', () => {
432+
const returnedValue = api.enableBuildCache({ config: [__filename] });
433+
expect(returnedValue).to.equal(api);
434+
});
435+
436+
});
437+
438+
419439
describe('configureMiniCssExtractPlugin', () => {
420440

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

0 commit comments

Comments
 (0)