Idea, setup and first steps

dreitzner

Domenik Reitzner

Posted on June 17, 2020

Idea, setup and first steps

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);
};
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

and add it in our views.

@section AppPerRoute
{
    @Scripts.RenderDefer("~/resources/scripts/app-some-route-bundle")
}
Enter fullscreen mode Exit fullscreen mode

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
💖 💪 🙅 🚩
dreitzner
Domenik Reitzner

Posted on June 17, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Idea, setup and first steps
angular1 Idea, setup and first steps

June 17, 2020