DEV Community

Cover image for Splitting vendor chunk with Vite and loading them async
Tássio
Tássio

Posted on • Updated on

Splitting vendor chunk with Vite and loading them async

How have your website chunks been? Does it need a diet?

Let's take a look at how to split the vendor chunk and reduce its size. Allowing the browsers to loading smaller chunks and loading them async. I'll use a personal project to show it. The stack is as follows:

The same applies to any other js frameworks with Vite. It might applicable with Webpack too, consult the doc to know how.

See my other articles

What are chunks at development word?

Usually, front-end developers have contact with .ts, .vue, and .tsx files, but browsers don't understand them. To do so, it needs to convert those files into .js files - and they can even be optimized, typically by using a build command.

Even though all development files can be converted into a single .js file, it's not recommended because of the performance impact. Ideally, those files a converted into multiple .js files (chunks) to take the ability to loading the files at the moment they are required - thinking on user navigation.

So, chunks are .js files that, together, define a whole front-end system, in a nutshell.

Current project bundle

To start, let's see how the project looks like:

A huge index.js file with all third party

As you can see, there are some js files in the project. let's focus on the index.hash.js.

Usually, tools such as Vite and Wepack create two main chunk files: index.hash.js and vendor.hash.js:

  • index: there are crucial application codes, such as App.tsx code;
  • vendor: All production dependencies (package.json > dependencies);

Wait. Why isn't the vendor chunk at the gif? Vite has removed this behavior since the 2.9 version. Now, by default, the index also has the production dependencies code.

By using Rollup Plugin Visualizer, which gives us an interactive graph of the project bundle, it's easier to see:

Bundle screenshot with no vendor js file

As you can see, there is a huge index chunk.

The first thing we can do is to split core content from third-party content, which allows the browser to download both files async by adding the modulepreload attribute.

But before move on, see the current index size (255.35 kb):

current index size

Split index chunk into two chunks and load them async

Even though Vite doesn't split them by default, it provides a plugin to do it for you:

import { splitVendorChunkPlugin } from 'vite';

export default defineConfig({
  plugins: [
    react(),
    splitVendorChunkPlugin()
]})
Enter fullscreen mode Exit fullscreen mode

Vite does the rest for you.

With index and vendor chunks

Vendor size: 239.95kb
Index size: 15.90kb

Well, it's possible to see how third-party packages impact the bundle of this project.

Enjoying it? If so, don't forget to give a ❤️. Then, I'll continue creating more content like this

Split vendor chunk into more chunks

As we figured out who is the villain, let's try to split the vendor chunk into smaller chunks.

To do so, we gonna use the manualChunk function, provided by Rollup. But before it, we must decide who we'll remove from the main vendor chunk(?) and that's the trick question... looking into our bundle again, we find some huge (comparing with other) packages, like react-dom, but to this article, I will create two other chunks from vendor chunk:

  • @open-ish
  • @react-router

Why? Well, when we have a vendor chunk, it might have dependencies that must be loaded in the right order, as some of them might use others, otherwise, your application might not work. This is the hardest part of creating chunks manually: knowing who must be in the same chunk and it depends on the application you have been working on. For my project, I know that those dependencies must be in the same chunk: 'react-router-dom', '@remix-run', and 'react-router'.

So let's see the code to split them:

import { splitVendorChunkPlugin } from 'vite';

export default defineConfig({
  plugins: [
    react(),
    splitVendorChunkPlugin()
],
 build: {
    rollupOptions: {
      output: {
        manualChunks(id: string) {
          // creating a chunk to @open-ish deps. Reducing the vendor chunk size
          if (id.includes('@open-ish') || id.includes('tslib')) {
            return '@open-ish';
          }
          // creating a chunk to react routes deps. Reducing the vendor chunk size
          if (
            id.includes('react-router-dom') ||
            id.includes('@remix-run') ||
            id.includes('react-router')
          ) {
            return '@react-router';
          }
        },
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

With this code, Rollup will create two new chunks:

Bundle with two new chunks

And now, comparing the vendor chunk size:

build after use manual chunk funcion

Vendor size: 211.94kb

As we can see, we reduce the biggest chunk from 255.35kb to 211.94kb (~18%). That might not sound too much, but it is just the beginning of splitting out the chunks and creating smaller ones and even given the possibility of loading them async to the browser.

See now how our application loads (focus on the waterfall):

final bundle

They start to load at the same time, did you see?

Conclusion

In this article, you saw a bit more about chunks, and how to split them and load them async using Vite/Rollup.

Even though the code itself is simple (thanks to Vite/Rollup), the hardest is to identify which dependencies must be in the same chunk.

I might create a second part of this content splitting the vendor chunk more, later.

See my other articles

Top comments (2)

Collapse
 
gsaran profile image
gsaran

Thank you for this article.

I upgraded to vite v5 and see that splitVenderChunkPlugin is deprecated. Seems like manualChunk is enough.

Collapse
 
tassiofront profile image
Tássio

I'm happy for helping, @gsaran 🚀