Large apps degrade user experience and one way to control that is to split our code into chunks. This chapter will walk through tools afforded by Shadow to split our app code. We'll also implement the splits and analyze performance gains.
The JS ecosystem makes developing JS apps fast, but installing libraries affects the bundle size.
It's normal for a JS app to be 1-5 megabytes before gzip. This means that a client has to parse at least 1MB of JS code for it to make any sense. This might not be a problem for shiny Macbooks but is definitely a concern for low-end mobile hardware.
Shadow provides us an efficient API to analyze and split our code into smaller chunks.
Bundle size reports#
Before we get into the details of code splitting, we need a way to measure where we stand. Shadow comes with a built-in reporting tool that lists the size and ingredients of bundled modules. We can generate a report using the
yarn report command.
After running this command, the output will be printed to your terminal and also in a file called
Our app is currently 625kb gzipped without any code splitting. The report also tells us about the elephants in the room. The four heaviest modules contain 50% of the code. Our project files (ie the code we wrote) are only 1.3% of the compiled code.
We'll try and optimize this and regenerate reports as we go to check our progress.
The process of code splitting comprises two steps: 1. configuring the compiler 2. modifying the app to load split code
With just one large bundle, we don't need to worry if our code is loaded yet, but with multiple smaller bundles, we need to ensure that all required code is available. One way to split code is to bundle code by routes, ie to have one module per route.
We have three main routes that we can split for:
/graphics. In the real world, you might also want to split out heavy components. For example, in our case, we can't really do anything about Blueprint, because that's our UI layer, but Firebase and Fabric are not needed to render the
/ index route.
Shadow splits code by clubbing entry points into
modules. Module is a generic term, but you can think of splitting out modules as files.
Currently, our Shadow configuration only generates one module
:main: - just one file
main.js that we load in
index.html. Let's update our configuration to generate one module per route:
:module-loader trueinjects additional code that enables fetching of modules at runtime
We added three additional modules
:graphics. This will lead to the creation of
main.jsin the configured
Only one module needs to be dependency-free: the root module, essential for the app to work
Other modules can mark dependencies using the
:depends-onconfiguration, which accepts a set. It's possible to depend on more than one module. All modules need to depend on the root module.
:entrieskey accepts a vector of namespaces that need to be clubbed inside the module.
Shadow computes the dependency graph and tries its best to put most code outside the root module. Think of it as trying to generate a small
:main file and pushing heavy components to other modules. However, this not always accurate and requires tuning.