Optimizing Vue Apps: Lazy Loading and Code Splitting
As smart phones and smart devices usage has exploded, more and more people (potential customers) are viewing your website on the mobile devices with smaller screens and restrictions on compute resources and network bandwidth. With the expectations of more and more feature rich websites and web-apps and uncertain network conditions are something we should always take into consideration, it is harder and harder to keep your application loading fast.
In this series, We will explore Vue performance optimization techniques that you can use in your Vue.js applications to make them loading instantly and perform smooth. This story covers the concepts of lazy loading and code splitting.
Understanding Webpack bundling
@vue/cli
— the official vue CLI, is based upon a JavaScript bundler called Webpack. Webpack is responsible for bundling the JavaScript that is shipped to the client. Thus it is important to understand how it works to effectively split and lazily load JS bundles on client machine.
While bundling our assets Webpack is creating something called
dependency graph. It is a graph that links all of our files based on imports. Assuming we have a file called main.js
specified as an entry point in our webpack config it will be a root of our dependency graph. Now every js module that we will import in this file will become its node in the graph and every module imported in these nodes will become their nodes.
Webpack is using this dependency graph to detect which files it should include in the output bundle. Output bundle is just a single (or multiple as we will see in the later parts) javascript file containing all modules from the dependency graph.
The bundle is essentially our entire application’s JavaScript. We can illustrate this process with below image:
Now that we know how bundling works, it becomes obvious that the bigger our project gets, the bigger the initial JavaScript bundle becomes.
The bigger bundle, the longer it takes to download and parse for our users. The longer a user has to wait, the more likely he is to leave our site. In fact, according to Google, 53% of mobile users leave a page that takes longer than three seconds to load.
As a summary, bigger bundle = fewer users, which can be directly translated to a loss of potential revenue.
Lazy loading
So how we can reduce bundle size while we still need to add new features? As the name suggests lazy loading is a process of loading parts (chunks) of your application lazily. In other words — loading them only when we really need them. Code splitting is just a process of splitting the app into this lazily loaded chunks.
In most cases, you don’t need all the code from your Javascript bundle immediately when a user visits your website.
For example, we don’t need to spend valuable resources on loading the “My Page” area for guests that visits our website for the first time. Or there might be modals, tooltips and other parts and components that are not needed on every page. It is wasteful to download, parse and execute the entire bundle everything on every page load when only a few parts are needed.
Lazy loading allows us to split the bundle and serve only the needed parts so users are not wasting time to download and parse code that’ll not be used. Ok, we know what lazy loading is and that it’s pretty useful. It’s time to see how we can use lazy loading in our own Vue.js applications.
Dynamic imports
We can easily load some parts of our application lazily with webpack dynamic imports. Let’s see how they work and how they differ from regular imports. If we import a JavaScript module in a standard way like this:
It will be added as a node of a main.js
in the dependency graph and bundled with it.
But we need our order
module only under certain circumstances when a user decided to place an order. Bundling this module with our initial bundle is a bad idea since it is not needed at all times. We need a way to tell our application when it should download this chunk of code.
This is where dynamic imports can help us! Now take a look at this example:
// main.js
const getOrder = () => import('./order.js')
// later when user wants to place ordergetOrder()
.then({ placeOrder } => placeOrder())
Let’s take a quick look at what happened here:
Instead of directly importing order
module we created a function that returns the import()
function. Now webpack will bundle the content of the dynamically imported module into a separate file. The function representing dynamically imported module returns a Promise that will give us access to the exported members of the module when resolved.
We can then download this optional chunk later, when needed. For instance as a response to a certain user interaction(like route change or click).
By making a dynamic import we are basically isolating the given node that will be added to the dependency graph and downloading this part when we decide it’s needed (which implies that we are also cutting off modules that are imported inside order.js
).
Let’s see another example that will better illustrate this mechanism.
Let’s assume we have a very small web shop with 4 files:
main.js
as our main bundleproduct.js
for scripts in product pageproductGallery.js
for product gallery in product pagecategory.js
for scripts in category page
Without digging too much into details let’s see how those files are distributed across the application:
// category.js
const category = {
init () { ... }
}
export default category// product.js
import gallery from ('./productGallery.js')const product = {
init () { ... }
}
export default product// main.js
const getProduct = () => import('./product.js')
const getCategory = () => import('./category.js')if (route === "/product") {
getProduct()
.then({init} => init()) // run scripts for product page
}
if (route === "/category") {
getCategory()
.then({init} => init()) // run scripts for category page
}
In above code, depending on the current route we are dynamically importing either product
or category
modules and then running init
function that is exported by both of them.
Knowing how dynamic imports are working we know that product
and category
will end up in a separate bundles but what will happen with productGallery
module that wasn’t dynamically imported? As we know by making module dynamically imported we are cutting part of the dependency graph. Everything that was imported inside this part will be bundled together so productGallery
will end up in the same bundle as product
module.
In other words we are just creating some kind of a new entry point for the dependency graph.
Lazy loading Vue components
Now that we know what lazy loading is and why we need it. It’s time to see how we can make use of it in our Vue applications.
The good news is that it’s extremely easy and we can lazily load the entire Single File Component, with it’s CSS and HTML with the same syntax.
const lazyComponent = () => import('Component.vue')
…that’s all you need! Now the component will be downloaded only when it’s requested. Here are the most common ways to invoke dynamic loading of Vue component:
- function with import is invoked
const lazyComponent = () => import('Component.vue')
lazyComponent()
- component is requested to render
Please note that the invocation of lazyComponent function will happen only when component is requested to render in a template. For example this code:
<lazy-component v-if="false" />
The component will not be loaded until it is required in the DOM, which is as soon as the v-if value changes to true.
Summary and what’s next
Code splitting and lazy loading is one of the best ways to make reduce the bundle size and thereby make your web app load faster while not cutting down on the features.
In this story, we learned how to use lazy loading with Vue components. In the next part of this series we will learn how to split your Vue code with async routes, which is by far considered the most useful and effective way to get performance boost in any Vue.js application, along with recommended best practices for this process.
Happy Coding! Happy Learning! Wish you all the best.
And if you want to master Vue JS, here are some courses developed by us and collaborators that you may be interested in