What they don't tell you about webpack dynamic imports

1. Hashes in the names of the dynamic chunks are a must

If your web server is set up for client-side hashing of static files, dynamic chunks will be cached as well. And the cache of all the dynamic chunks has to be invalidated together with the cache of the output bundles that use them. Because if a chunk and a bundle or another chunk belong to different builds they can become internally incompatible. This can lead to a cryptic error message from inside webpack guts like this:

TypeError: Cannot read properties of undefined (reading 'call')

in __webpack_require__(webpack/bootstrap):
  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

Or type errors when chunks expect properties to be there but they're not because objects of wrong types or undefined values get passed around:

TypeError: u.extend is not a function

in apply(jquery.some-plugin):
  $.extend( $.fn, {

I know that putting hashes in the output file names is generally a good practice that doesn't need further introduction. But I'm still stressing the problem here because in the Django world there's another way of busting static files cache: ManifestStaticFilesStorage. It appends content hashes independently from webpack and it does its best job to find internal links inside the static files to update them as well, but it doesn't update links to dynamic chunks. In other words, ManifestStaticFilesStorage doesn't bust dynamic chunk cache at the time of this post.

If you're getting errors similar to the above, you might also have a problem with cache busting, but in a different place.

So, if you're starting to use dynamic chunks, open the "Sources" tab in DevTools to see if there are hashes in the dynamic chunk URLs. If not, try setting output.chunkFilename in the webpack config.

If you're using ManifestStaticFilesStorage as well, you can put a function in that setting and only append [contenthash] to dynamic chunks (Not sure if there's an appropriate flag in pathData for that. Please comment or contact me if you find out.)

2. You can and you probably should retry loading dynamic chunks

Loading a dynamic chunk is making a network request and networks are faulty. If you track your app's errors you can curiously find out that most often chunk load failures happen on mobile devices (presumable because of unstable cellular networks), as discovered by 5imone in this StackOverflow question:

Webpack code splitting: ChunkLoadError - Loading chunk X failed, but the chunk exists
I’ve integrated Sentry with my website a few days ago and I noticed that sometimes users receive this error in their console:ChunkLoadError: Loading chunk <CHUNK_NAME> failed.(error: <

The good thing is that in case of an error you can just try again as with any network request! The user will have to wait a bit before the UI initializes, but at least it's better than letting the UI break altogether.

This is the code that I've been using so far:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

async function retryOnFail(load) {
  const MAX_ATTEMPTS = 3;
  const INITIAL_DELAY = 500; // Will double each time

  let attempts = 0;
  let delay = INITIAL_DELAY;

  while (true) {
    try {
      return await load();
    } catch (e) {
      if (e.name !== "ChunkLoadError") {
        throw e;
      }

      attempts++;
      if (attempts >= MAX_ATTEMPTS) {
        throw e;
      }

      await timeout(delay);
      delay *= 2;
    }
  }
}

async function lazyLoadSomething() {
  return await retryOnFail(async () => {
    return await import(
      /* webpackChunkName: "dynamic/something" */ "something"
    );
  });
}

Hopefully, it'll find its use for you as well.