Skip to content

Instantly share code, notes, and snippets.

@AlexandrHoroshih
Created May 18, 2024 16:16
Show Gist options
  • Save AlexandrHoroshih/f76705c379a8897010b2b2a2de2ce4df to your computer and use it in GitHub Desktop.
Save AlexandrHoroshih/f76705c379a8897010b2b2a2de2ce4df to your computer and use it in GitHub Desktop.
const webpack = require('webpack');
const pluginName = 'RetryChunkLoadPlugin';
const defaultRetryDelay = 200;
class RetryChunkLoadPlugin {
constructor(options = {}) {
this.options = {
maxRetries: 5,
chunks: ['runtime'],
retryDelay: `(function() {
let retryIndex = 1;
return function() {
const timeout = retryIndex * ${defaultRetryDelay};
retryIndex++;
return timeout;
};
})();`,
lastResortHandler: `function lastResort(error) { console.error(error); };`
...options,
};
}
apply(compiler) {
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
const { mainTemplate, runtimeTemplate } = compilation;
const { maxRetries } = this.options;
const getCacheBustString = () => '"cache-bust=true"';
mainTemplate.hooks.localVars.tap(
{ name: pluginName, stage: 1 },
(source, chunk) => {
const currentChunkName = chunk.name;
const addRetryCode = !this.options.chunks || this.options.chunks.includes(currentChunkName);
const getRetryDelay = typeof this.options.retryDelay === 'string' ? this.options.retryDelay : `function() { return ${this.options.retryDelay || 0} }`;
if (!addRetryCode) return source;
const script = runtimeTemplate.iife(
'',
`
if(typeof ${webpack.RuntimeGlobals.require} !== "undefined")
const defaultGetChunkScriptFilename = ${webpack.RuntimeGlobals.getChunkScriptFilename};
const defaultEnsureChunkLoad = ${webpack.RuntimeGlobals.ensureChunk};
const chunkID_ActiveRequests = {};
const chunkID_Retries = {};
const chunkID_Query = {};
const getRetryDelay = ${getRetryDelay};
${webpack.RuntimeGlobals.getChunkScriptFilename} = function(chunkId){
const chunkName = defaultGetChunkScriptFilename(chunkId);
return chunkName + (chunkID_Query[chunkId] ? '?' + chunkID_Query[chunkId] : '');
};
${webpack.RuntimeGlobals.ensureChunk} = function(chunkId) {
if (chunkID_ActiveRequests[chunkId]) return chunkID_ActiveRequests[chunkId];
chunkID_Retries[chunkId] = 0;
const resultWrapper = new Promise(function(resolve, reject) {
function request() {
const retryCounter = chunkID_Retries[chunkId];
defaultEnsureChunkLoad(chunkId)
.then(function(data) {
resolve(data);
return data;
})
.catch(function(error) {
if (retryCounter >= ${maxRetries}) {
const chunkName = defaultGetChunkScriptFilename(chunkId);
error.message = chunkName + ' | Loading chunk failed after ${maxRetries} retries.';
const handleError = ${this.options.lastResortHandler}
handleError(error);
throw error;
}
const cacheBust = ${getCacheBustString()} + '&retry-attempt=' + (retryCounter + 1);
chunkID_Query[chunkId] = cacheBust;
setTimeout(request, getRetryDelay(retryCounter));
});
chunkID_Retries[chunkId]++;
}
return request();
});
chunkID_ActiveRequests[chunkId] = resultWrapper;
return resultWrapper;
};
}`,
);
return source + script;
},
);
});
}
}
exports.RetryChunkLoadPlugin = RetryChunkLoadPlugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment