edify17
edify17

Reputation: 71

How to manually load webpack async chunks, when any dynamic import fails to load file?

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

Answers (1)

Ayushya
Ayushya

Reputation: 1920

I have two solutions:

1. You need to wrap the chunk import() with a retry function.

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")));

2. You can use webpack-retry-chunk-load-plugin

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

Related Questions