Enable webpack code splitting to improve frontend performance.
Resources
As our frontend codebase continues to grow, the javascript bundles we serve to the client browser have blown up in size. This has some serious performance implications. Here is an illustration of data points from Chrome DevTools performance evaluation of https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11836 on my MacBook Pro (using slowdown of 2x to simulate a less powerful computer)
Here's a breakdown of the sections I've numbered in the graph above:
- 339ms – Ignoring everything before the first byte (because that's backend and network performance related), there is a lengthy idle frame where we are waiting for blocking network requests (our JS and CSS assets in the page header).
- 555ms – Compiling and evaluating our javascript bundles (code within webpack bundles)
-
163ms – Evaluating embedded javascript within the page (code within
<script>
tags) - 629ms – Running the DOMContentLoaded event.
After all of this, the page is fully interactive. Between all of these steps, there's about 1.7 seconds of loading time that are fully within the frontend team's power to improve.
A LOT of the code we evaluate in steps 2 and 4 are unrelated to the page we're viewing, and a lot of the size of the files we wait for in step 1 is due to code that won't even execute on this page.
Improving both perceived and actual performance with webpack
- Introduce webpack code splitting and asynchronously load large components like the emoji picker, dropzone library, or calendar picker.
- Intelligently split up the main.js and dispatcher.js files into smaller, page-specific chunks.
- Move our scripts below the html
<body>
so it doesn't block the initial render.
Next steps:
- Enable webpack code splitting. Configure babel and eslint to understand
import()
. Ensure it works in all gitlab configurations (i.e.relative_url_root
plays nice). We can test this with the emoji picker first. - Start eliminating
import
side effects as technical debt. Almost every script in our codebase (with a few privileged exceptions) should do nothing other than define andexport
classes and methods. This will help us evaluate our dependency graph and eliminate global scope pollution. - Evaluate all imports within
main.js
anddispatcher.js
and identify groups of scripts that ought to be bundled together. Utilize the new Chrome DevTools code coverage feature to inspect the code that executes on a given page and easily identify common paths. - Take the code that is not necessary for initial interactivity and split it off into asynchronous chunks.
- Remove all embedded
<script>
tags from our HTML and place them within the dispatcher instead, then move our javascript bundles from the page header to just below the page<body>
so we don't block the page from rendering initially. - Create a means to automatically include async bundle browser hints with
<link rel="preload">
which is now supported in both Chrome and Safari. - Update Documentation with guidelines for code splitting and webpack bundle best practices.