Skip to content

Commit

Permalink
Add chunkLoadRetry option for reloading chunks
Browse files Browse the repository at this point in the history
Supports `true` or an object that can tweak the config a bit further. When `true` it will have 5 attempts with exponential backoff.
  • Loading branch information
tjenkinson committed Jul 9, 2024
1 parent b988273 commit bd59541
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 6 deletions.
6 changes: 5 additions & 1 deletion docs/01-configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ module.exports = {
// in you need to turn off minification in production for any reason
minify: false,

// set to `true` to enable retries on chunk loads
// defaults to trying 5 times with exponential backoff
chunkLoadRetry: false,

// command executed to run the server/api process
// this command is exucuted only if `-x` arg is passed to jetpack
// this command is executed only if `-x` arg is passed to jetpack
// even if this option is configured
exec: 'node .',

Expand Down
3 changes: 3 additions & 0 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ module.exports = function options(command = 'dev', program = {}) {
// to turn off minification in production
minify: options.minify === undefined ? true : options.minify,

// retry loading chunks
chunkLoadRetry: options.chunkLoadRetry ?? false,

target,

// command executed to run the server/api process
Expand Down
63 changes: 63 additions & 0 deletions lib/retryChunkLoadPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { RuntimeGlobals } = require('webpack')

const name = 'RetryChunkLoadPlugin'

class RetryChunkLoadPlugin {
#maxAttempts
#base
#multiplier

constructor({ maxAttempts = 5, base = 1.8, multiplier = 500 } = {}) {
if (typeof maxAttempts !== 'number' || maxAttempts < 1) {
throw new Error('Invalid `maxAttempts`')
}

if (typeof base !== 'number' || base < 1) {
throw new Error('Invalid `base`')
}

if (typeof multiplier !== 'number' || multiplier < 0) {
throw new Error('Invalid `multiplier`')
}

this.#maxAttempts = maxAttempts
this.#base = base
this.#multiplier = multiplier
}

apply(compiler) {
compiler.hooks.thisCompilation.tap(name, (compilation) => {
const { mainTemplate, runtimeTemplate } = compilation
mainTemplate.hooks.localVars.tap({ name, stage: 1 }, (source) => {
const script = runtimeTemplate.iife(
'',
`
if (typeof ${RuntimeGlobals.require} !== "undefined") {
var initialEnsureChunk = ${RuntimeGlobals.ensureChunk};
${RuntimeGlobals.ensureChunk} = function (chunkId) {
var attemptCount = 0;
return new Promise(function (resolve, reject) {
var load = function () {
initialEnsureChunk(chunkId).then((res) => resolve(res)).catch((e) => {
if (++attemptCount >= ${this.#maxAttempts}) {
reject(e);
} else {
setTimeout(() => load(), (${this.#base} ** (attemptCount - 1)) * ${this.#multiplier})
}
});
};
load();
});
};
}`
)
return source + script + ';'
})
})
}
}

module.exports = RetryChunkLoadPlugin
5 changes: 4 additions & 1 deletion lib/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const path = require('path')
const progress = require('./progress')
const RetryChunkLoadPlugin = require('./retryChunkLoadPlugin.js')

const plugins = {
js: require('./webpack.js'),
Expand Down Expand Up @@ -46,7 +47,9 @@ async function createConfig(options, log) {
optimization: {
minimizer: []
},
plugins: [],
plugins: options.chunkLoadRetry
? [new RetryChunkLoadPlugin(options.chunkLoadRetry === true ? {} : options.chunkLoadRetry)]
: [],
devServer: {
publicPath: '/assets/',
stats: 'none'
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/pkg-with-everything/jetpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const webpackConfigForTests = require('../../helpers/webpackConfigForTests')

module.exports = {
minify: false,
chunkLoadRetry: true,
css: {
features: {
'nesting-rules': true
Expand Down
1 change: 1 addition & 0 deletions test/options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const base = (pkg, extra = {}) =>
exec: false,
proxy: {},
minify: true,
chunkLoadRetry: false,
coverage: false,
publicPath: '/assets/',
css: {
Expand Down
60 changes: 56 additions & 4 deletions test/snapshots/build.test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ Generated by [AVA](https://avajs.dev).
183.7fc0968292e45d0e33ef.js␊
bundle.0ed7b869ee43c4254963.js␊
bundle.7fff8faa.css␊
runtime~bundle.29f8f00bc809ef35d6b9.js␊
runtime~bundle.d14fbbdc4589401927ce.js␊
183.7fc0968292e45d0e33ef.js (594 KiB)␊
bundle (610 KiB)␊
bundle (611 KiB)␊
183.7fc0968292e45d0e33ef.js 560 594 KiB␊
292.1b033d9e5c6bc599a658.js 2 514 bytes␊
292.f28a9d80.chunk.css - 309 bytes␊
Expand All @@ -85,7 +85,7 @@ Generated by [AVA](https://avajs.dev).
This can impact web performance.␊
bundle.0ed7b869ee43c4254963.js 2 1.17 KiB␊
bundle.7fff8faa.css - 241 bytes␊
runtime~bundle.29f8f00bc809ef35d6b9.js - 14.6 KiB␊
runtime~bundle.d14fbbdc4589401927ce.js - 15.5 KiB␊
jetpack β€Ί Building for production...␊
jetpack β€Ί Building modern bundle␊
jetpack β€Ί Compiled with 2 warnings:`
Expand Down Expand Up @@ -278,6 +278,32 @@ Generated by [AVA](https://avajs.dev).
/******/ __webpack_require__.p = "/assets/";␊
/******/ })();␊
/******/ ␊
/******/ /* webpack/runtime/compat */␊
/******/ (() => {␊
/******/ ␊
/******/ if (typeof __webpack_require__ !== "undefined") {␊
/******/ var initialEnsureChunk = __webpack_require__.e;␊
/******/ ␊
/******/ __webpack_require__.e = function (chunkId) {␊
/******/ var attemptCount = 0;␊
/******/ ␊
/******/ return new Promise(function (resolve, reject) {␊
/******/ var load = function () {␊
/******/ initialEnsureChunk(chunkId).then((res) => resolve(res)).catch((e) => {␊
/******/ if (++attemptCount >= 5) {␊
/******/ reject(e);␊
/******/ } else {␊
/******/ setTimeout(() => load(), (1.8 ** (attemptCount - 1)) * 500)␊
/******/ }␊
/******/ });␊
/******/ };␊
/******/ ␊
/******/ load();␊
/******/ });␊
/******/ };␊
/******/ }␊
/******/ })();␊
/******/ ␊
/******/ /* webpack/runtime/css loading */␊
/******/ (() => {␊
/******/ if (typeof document === "undefined") return;␊
Expand Down Expand Up @@ -20301,7 +20327,7 @@ Generated by [AVA](https://avajs.dev).
␊
`

> /test/fixtures/pkg-with-everything/dist/assets/runtime~bundle.29f8f00bc809ef35d6b9.js
> /test/fixtures/pkg-with-everything/dist/assets/runtime~bundle.d14fbbdc4589401927ce.js

`/******/ (() => { // webpackBootstrap␊
/******/ "use strict";␊
Expand Down Expand Up @@ -20477,6 +20503,32 @@ Generated by [AVA](https://avajs.dev).
/******/ __webpack_require__.p = "/assets/";␊
/******/ })();␊
/******/ ␊
/******/ /* webpack/runtime/compat */␊
/******/ (() => {␊
/******/ ␊
/******/ if (typeof __webpack_require__ !== "undefined") {␊
/******/ var initialEnsureChunk = __webpack_require__.e;␊
/******/ ␊
/******/ __webpack_require__.e = function (chunkId) {␊
/******/ var attemptCount = 0;␊
/******/ ␊
/******/ return new Promise(function (resolve, reject) {␊
/******/ var load = function () {␊
/******/ initialEnsureChunk(chunkId).then((res) => resolve(res)).catch((e) => {␊
/******/ if (++attemptCount >= 5) {␊
/******/ reject(e);␊
/******/ } else {␊
/******/ setTimeout(() => load(), (1.8 ** (attemptCount - 1)) * 500)␊
/******/ }␊
/******/ });␊
/******/ };␊
/******/ ␊
/******/ load();␊
/******/ });␊
/******/ };␊
/******/ }␊
/******/ })();␊
/******/ ␊
/******/ /* webpack/runtime/css loading */␊
/******/ (() => {␊
/******/ if (typeof document === "undefined") return;␊
Expand Down
Binary file modified test/snapshots/build.test.js.snap
Binary file not shown.

0 comments on commit bd59541

Please sign in to comment.