Reputation: 71
Async chunks in webpack can be created by using Dynamic Imports (Eg. import('./ModuleA.js');
), Now if the dynamic chunks fail to load then I want to retry to load them from some other location. After thinking a lot about the problem and exploring babel and webpack, I wrote a babel plugin that attaches catch clause to every dynamic import and inside catches clause, I try to load chunk from some other location (Eg. if the first chunk fails to load from CDN, then I'll try to load it from server in catch clause).
To load chunk from server, I change __webpack_public_path__
to server domain and then call
__webpack_chunk_load__(chunkId);
,chunkId is available in error object whenever dynamic imports reject.
Now the problem arises with dynamic importing routes if I am using
React.lazy(() => import(/* webpackChunkName: "ModuleA" */ './ModuleA'));
React.lazy()
expects default export of React Component to be returned, __webpack_chunk_load__(chunkId)
, loads the chunk by dynamically injecting script tag but it doesn't load the module and returns module.exports
which is needed by React.lazy()
.
Internally,
React.lazy(() => import(/* webpackChunkName: "ModuleA" */ './ModuleA'));
will be converted to following code by webpack,
react__WEBPACK_IMPORTED_MODULE_5___default.a.lazy(function () {
return Promise.resolve(__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./ModuleA */ "./src/ModuleA.js")))
});
Now, as you can see, __webpack_require__.bind(null, /*! ./ModuleA */ "./src/ModuleA.js")
, returns the module.exports.
I am able to achieve, loading of webpack async chunk using __webpack_chunk_load__(chunkId);
, but not able to call __webpack_require__.bind(null, /*! ./ModuleA */ "./src/ModuleA.js")
, as __webpack_require__
requires moduleId which is not available here.
Is there any way to manually load a dynamic chunk in webpack? or how can I get moduleId
to call __webpack_require__.bind(null, /*! ./ModuleA */ "./src/ModuleA.js")
Also, is it the correct way to achieve it, will be happy to see any other approach.
I am using following code inside catch clause,
filePath = error.request;
var chunkId = error.message.substring(error.message.indexOf('chunk') + 6 , error.message.indexOf('failed.') - 1);
return Promise.resolve(window.chunkLoad(chunkId)).then(window.webpackRequire.bind(null, window.dynamicModule));
Upvotes: 2
Views: 7174
Reputation: 1920
You can edit the default of 5 retries before it finally fails.
function retry(fn, retriesLeft = 5, interval = 1000) {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
setTimeout(() => {
if (retriesLeft === 1) {
// reject('maximum retries exceeded');
reject(error);
return;
}
// Passing on "reject" is the important part
retry(fn, retriesLeft - 1, interval).then(resolve, reject);
}, interval);
});
});
}
Now use this function as below:
// Code split without retry
React.lazy(() => import("./ModuleA"));
// Code split with retry
React.lazy(() => retry(() => import("./ModuleA")));
The only caveat with this plugin is that it limits the retries to 1. But it saves the developer to make changes at every dynamic import.
Hope this helps.
Upvotes: 13