Home > Software engineering >  Webpack imports a very large existing chunk to only use a small BootstrapVue component
Webpack imports a very large existing chunk to only use a small BootstrapVue component

Time:08-20

I’m working with Webpack 5 and trying to optimize the splitChunks configuration in a multi page application with a PHP backend and a Vue frontend.

In order to reduce the size of the vendor file I have started excluding some libraries by customizing the test function of the vendor cacheGroup.

test(module /* , chunk */) {
  if (module.context) {
    // only node_modules are needed
    const isNodeModule = module.context.includes('node_modules');
    // but we exclude some specific node_modules
    const isToBundleSeparately = [
      'marked', // only needed in one component
      'braintree-web', // only payment page
      'bootstrap/js',
    ].some(str => module.context.includes(str));
    if (isNodeModule && !isToBundleSeparately) {
      return true;
    }
  }
  return false;
},

By doing so, some libraries that are not used in all pages are only imported in the components that need them, because those components are imported via dynamic imports and are extracted to separate chunks.

This has proven to work as expected until I encountered a strange behavior with one specific chunk and with one specific library (BootstrapVue).

If I add 'bootstrap-vue' to the list of excluded libraries, what happens is that two components of the library, BTab and BTabs, are extracted to a very large chunk which also includes the code containing the whole code for the payment page and all the libraries used in that page.

Webpack Bundle Analyzer screenshot showing the large chunk

If you look at the screenshot, the file is the one whose name starts with “init-initPaymentGateway”.

So now all the pages that need those two BootstrapVue components are loading that large file, including the product page and other pages that only need the two tiny BootstrapVue components.

Here you can see that the chunk is imported in the product page:

Google Chrome Screenshot showing the imported file in the Network Tab

I would expect that with my current configuration those two components would go to a separate chunk, or, if they are too small, they should be duplicated. Using Webpack Bundle Analyzer I see both very small files and duplicated library files, so I don’t understand why this is not happening with those specific components. The BootstrapVue BAlert component, for example, which is also small, is duplicated in different components.

I suspect that the issue comes from the fact that the components are small, but by setting minSize to 0 (or 10) I would expect to not have a minimum size for the creation of chunks.

Here are the imports in the payment page:

import { BTab, BTabs } from 'bootstrap-vue';
import dataCollector from 'braintree-web/data-collector';

(Then other inner components import other files from braintree-web).

Here is the import in one component of the product page:

import { BTabs, BTab } from 'bootstrap-vue';

And here is the complete splitChunks configuration (I have removed some of the excluded libraries from the list in the test function as they are not relevant).

splitChunks: {
  chunks: 'all',
  minSize: 10,
  minChunks: 1,
  maxAsyncRequests: 30,
  maxInitialRequests: 30,
  enforceSizeThreshold: 50000,

  name(module, chunks /* , cacheGroupKey */) {
    const allChunksNames = chunks
      .filter(item => item.name !== null)
      .map(item => item.name.replace('View', ''))
      .join('~');
    return allChunksNames.slice(0, 30);
  },
  cacheGroups: {
    default: {
      minChunks: 2,
        priority: -20,
        reuseExistingChunk: true,
      },
      defaultVendors: {
        name: 'vendor',
        priority: -10,
        chunks: 'all',
        minChunks: 1,
        reuseExistingChunk: true,
        test(module /* , chunk */) {
          if (module.context) {
            // only node_modules are needed
            const isNodeModule = module.context.includes('node_modules');
            // but we exclude some specific node_modules
            const isToBundleSeparately = [
              'marked', // only needed in one component
              'bootstrap-vue',
              'braintree-web',
              'bootstrap/js',
            ].some(str => module.context.includes(str));
            if (isNodeModule && !isToBundleSeparately) {
              return true;
            }
          }
        return false;
      },
    },
  },
},

To fix the issue I removed 'bootstrap-vue' from the exclusion in the test function, but I’d like to understand if I can improve my configuration or at least the reason for this behavior.

CodePudding user response:

By trying to fix another issue caused by a naming collision, I have modified the name function and fixed the issue. Basically the slice function was causing a naming collision, which resulted in a chunk containing the code for more than one module, because both had the same name.

An explanation is given in the Webpack docs: The chunk with only the modules that I need

  • Related