Domenik Reitzner
Posted on June 17, 2020
This is the first post in a series of seven.
I will go through the steps of how I shaved off more than 100kB (~25%) from the js main bundle size and improved various other things.
Idea
Some time ago I was put in charge of one of our clients old web project. The project had already 5 years of development and a lot of different developers behind it.
One question that our client asked is, if there is any way to improve the performance of the project (decrease JS bundle size).
Setup
The project is set up in the following way:
- C# MVC in the backend
- angularjs in the frontend (with class syntax, which will be important later on)
- gulp for the build
First steps
When I took over the project, one of the first improvements that I wanted to work out, was code splitting. The hard part was, that there wasn't any documentation on how to do that with angularjs.
I figured, that since we control the routing mostly with the backend, that this would be easiest by injecting code based on the route.
The way I set it up, was to firstly separate the bundle into a vendor and main chunk (caching can persist for vendor after a new release and improves load times). After that I pinpointed all components, directives and services that are only used for certain routes (profile, checkout, basket,....) and moved them out of the main js folder into a separate folder (eg. app-per-route).
The next step was to setup the build process.
const bundleRouteScripts = () => {
const dir = 'some-directory/app-per-route';
// get all folders in directory
const folders = fs.readdirSync(dir)
.filter((file) => fs.statSync(path.join(dir, file)).isDirectory());
const streams = [];
// add stream for each bundle operation based on folder
folders.forEach((folder) => {
streams.push(bundle(folder, `${dir}/${folder}/*/*.js`, 'app-per-route/'));
});
// return merged streams
return p.mergeStream(streams);
};
With this I ended up with one file for each folder in my app-per-route folder.
Next I needed to inject the bundle in the right place (after loading the main app chunk and before bootstrap) in our C# Layout,
<head>
// ...
@Scripts.RenderDefer("~/resources/scripts/vendor")
@Scripts.RenderDefer("~/resources/scripts/main")
@RenderSection("AppPerRoute", false)
@Scripts.RenderDefer("~/resources/scripts/app-bootstrap")
// ...
</head>
and add it in our views.
@section AppPerRoute
{
@Scripts.RenderDefer("~/resources/scripts/app-some-route-bundle")
}
After that already a considerable chunk has been cut out from the main bundle, but there is still a lot of wiggle room.
Coming up next
- Extract polyfills from the vendor bundle and only load them for legacy browsers
- Extract external libaries and only load them when needed
- Fix babel code duplication
Posted on June 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.