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:
- React (SPA);
- Vite;
- Rollup Plugin Visualizer;
The same applies to any other js frameworks with Vite. It might applicable with Webpack too, consult the doc to know how.
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:
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:
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):
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()
]})
Vite does the rest for you.
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 ❤️_ and follow me to keep updated. 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';
}
},
},
},
},
})
With this code, Rollup will create two new chunks:
And now, comparing the vendor chunk size:
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):
They start to load at the same time, did you see?
Buy me a coffee ☕. Hope I have helped you somehow. 🤗
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.