Webpack Finally Lazy Load

Image you have this beautiful app and everything works fast on local. You are very proud of it until you realize it takes a long time to load on production. It is most likely due to one of the most important physical restrictions: file size. You can optimize your code however you like but if client’s browsers take long time to download those files, none of the performance tweaks you made matter. Since Webpack 2 was out, we have a mean of separating our JavaScript bundles into many smaller bundles.

Code splitting is a technique that allows you to separate your bundle and load the chunks individually at runtime. Lazy loading is utilizing this technique and dynamic imports in JavaScript to make your app loads fast by loading only what you need. In your JS file, you normally import the modules you’d like to use at the beginning of the files like:

javascript
// Import what you need here
import Vue from "vue"
import firebase from "firebase"

let component = new Vue()

Dynamic importing allows you to import different modules during runtime.

javascript
async function init() {
	await import("firebase")
}

This import will return a promise and you can start interacting with the class and functions that module exports. Back to route lazy loading, I will be using Vue as an example but React and other JS frameworks are similar:

javascript
const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
    },
    {
      path: '/login',
      name: 'auth',
      component: () => import(/* webpackChunkName: "auth" */ './views/Auth.vue')
    }
  ],
})

Example above creates a Vue router with some paths and components defined. When the client visits “/“ for example, browser will download the core bundle first, it sees it’s hitting homepage, then it starts downloading the bundle that contains Home.vue which is the component for home page. Before you ask, the webpackChunkName comment is what Webpack calls magic comments. This particular comment tells Webpack to name the bundle as ‘home’ when it starts splitting this Home.vue into its own bundle. Now the main bundle should be smaller now as you don’t need to load all of the routes at once anymore. Instead, browser will only download the core bundle and the route plus components the user needs for the current path.

You probably notice that these routes will only be loaded after browser starts parsing JavaScript files, execute them, and then starts fetching files. From the sound of it, the performance for contentful paint may suffer. To further improve the loading performance in this nature, you could utilize link preload. What preload does is that browser will prioritize and start downloading these assets beforehand without parsing them. This should save some time since client doesn’t need to wait till it discovers them to download.

html
<!-- Preload JavaScript -->
<link rel="preload" href="main.js" as="script">
<!-- Preload CSS -->
<link rel="preload" href="main.css" as="style">

When your code gets to the point that it needs to fetch the file, since your browser already downloaded it you don’t need to wait. This priority allocation shouldn’t be too much of a problem as your browser can download stuffs simultaneously and more if your server is adopting http2 protocol. This preload can also preload fonts, pictures, etc.

Recently, as in since January 2019, there have been many reports on Webpack stops being able to parse and generate a dynamic imported bundle. The problem seems to be fixed now but for some people that are curious: the problem seems to be in npm that handles the peer dependencies unexpectedly and the package in question is acorn. You can read more about it here: Parsing of import() fails in 4.29.0 (Compilation issue, related to dynamic import) · Issue #8656 · webpack/webpack · GitHub The quickest way to solve it now is to npm install acorn@latest -D to match the version and some people say using yarn to install these packages also help as yarn handles the dependencies correctly. Or, update to the latest Webpack.

I personally run into another problem: it doesn’t generate these dynamic imported bundle at all and there are no errors. I’ve searched everywhere on Webpack GitHub issues but it keeps leading me to the GitHub issue mentioned above. One day, I’ve accidentally stumbled upon the solution and it has nothing to do with Webpack itself.

So babel allows you to create configurations inside package.json, babel.rc, babel.config.js, or an object in webpack.config.js When I upgraded from babel 6 to 7, I think I’ve misconfigured something so my code starts transpiling when I have my configurations inside webpack.config.jsinstead of having babel.config.js. Later on I created a babel.config.js for Jest test so Jest would understand things like import, destructuring, and some other cool ES6 syntaxes. This is when Webpack stops generating those bundles. Babel has this concept of project-wide root configuration which is like the root of the configuration and you can have all these other configurations in the project that will “extend” from this root configuration but the root one has priority. However due to babel not recognizing the configuration in babel.config.js is for test only, test configs in root are applied to the project when building. Two solutions are to fix babel configurations or what I have done is to set configFile to false inside Webpack so babel stops taking the configs in root. You can read more about what I said here: Config Files · Babel

Now your users don’t need to download the whole app to render home page anymore. To extend from this, you could start looking into PRPL pattern that was introduced by Polymer team on Google to better support Single Page Application.

If you like what you read please share on Twitter and let me know what you think, thank you!