Code Splitting With Vue.js And Webpack
Anthony Gore | July 3rd, 2017 | 3 min read
One possible downside to bundling your single page app with Webpack is that you can end up with a really big bundle file, sometimes several megabytes in size!
Asset | Size |
---|---|
bundle.main.js | 1.36 MB 😞 |
The problem with this is that a user must download the whole file and run it before they can see anything on the screen. If the user is on a mobile device with a poor connection this process could take quite some time.
Code splitting is the idea that a bundle can be fragmented into smaller files allowing the user to only download what code they need, when they need it.
For example, looking at this simple web page, we can identify portions of the app that we don't need on the initial load:
What if we delayed loading these parts of the code until after the initial render? It would allow a user to see and interact with the page much quicker.
In this article I'll show you how Vue.js and Webpack can be used to split a single page app into more optimally sized files that can be dynamically loaded.
Async components
The key to code splitting a Vue.js app is async components. These are components where the component definition (including its template, data, methods etc) is loaded asynchronously.
Let's say you're declaring a component using the component
API i.e. Vue.component(name, definition)
. Rather than having a definition object as the second argument, async components have a function. This function has two notable features:
- It's an executor for a Promise i.e. has a
resolve
argument. - It's a factory function i.e. it returns an object (in this case, the component definition).
Vue.component('async-component', (resolve) => {
resolve({
template: '<div>Async Component</div>',
props: [ 'myprop' ]
});
});
Async components are the first step for code splitting because we now have a mechanism for abstracting sections of our app's code.
Dynamic module loading
We'll also need Webpack's help. Say we abstract our component definition into an ES6 module file:
AsyncComponent.js
export default {
template: '<div>Async Component</div>',
props: [ 'myprop' ]
}
How could we get our Vue.js app to load this? You may be tempted to try something like this:
import AsyncComponent from './AsyncComponent.js'`;
Vue.component('async-component', AsyncComponent);
However this is static and is resolved at compile-time. What we need is a way to dynamically load this in a running app if we want to get the benefits of code splitting.
import()
Currently, it's not possible to dynamically load a module file with JavaScript. There is, however, a dynamic module loading function currently under proposal for ECMAScript called import()
.
Webpack already has an implementation for import()
and treats it as a code split point, putting the requested module into a separate file when the bundle is created (a separate chunk, actually, but think of it as a separate file for now).
import()
takes the file name as an argument and returns a Promise. Here's how we'd load our above module:
main.js
import(/* webpackChunkName: "async-component" */ './AsyncComponent.js')
.then((AsyncComponent) => {
console.log(AsyncComponent.default.template);
// Output: <div>Async Component</div>
});
Note: if you're using Babel, you'll need to add the
syntax-dynamic-import
plugin so that Babel can properly parse this syntax.
Now when you build your project you'll notice the module appears in its own file:
Asset | Chunk Name |
---|---|
bundle.main.js | main |
bundle.0.js | async-component |
Another note: you can give a dynamically imported module chunk a name so its more easily identifiable; simply add a comment before the file name in the same way I've done in the above example.
Dynamic component loading
Bring the pieces together now: since import()
returns a Promise, we can use it in conjunction with Vue's async component functionality. Webpack will bundle AsyncComponent separately and will dynamically load it into the app via AJAX when the app calls it.
main.js
import Vue from 'vue';
Vue.component('async-component', (resolve) => {
import('./AsyncComponent.js')
.then((AsyncComponent) => {
resolve(AsyncComponent.default);
});
});
new Vue({
el: '#app'
});
index.html
<div id="app">
<p>This part is included in the page load</p>
<async-component></async-component>
</div>
<script src="bundle.main.js"></script>
On the initial load the page will be rendered as:
<div id="app">
<p>This part is included in the page load</p>
</div>
When main.js runs it will initiate a request for the async component module (this happens automatically because Webpack's import()
implementation includes code that will load the module with AJAX!).
If the AJAX call is successful and the module is returned, the Promise resolves and the component can be rendered, so Vue will now re-render the page:
<div id="app">
<p>This part is included in the page load</p>
<div>Async Component</div>
</div>
Here's a diagram to help you visualise it:
Single file components
The idiosyncratic way to achieve code splitting in Vue, however, is to use the beloved single file component. Here's a refactor of the above code using a SFC.
AsyncComponent.vue
<template>
<div>Async Component</div>
</template>
<script>
export default {
props: [ 'myprop' ]
}
</script>
This syntax for importing is even neater:
new Vue({
el: '#app',
components: {
AsyncComponent: () => import('./AsyncComponent.vue')
}
});
Code splitting architecture
That's the technical part out of the way. The question, now, is how can you architect an app for code splitting?
The most obvious way is by page. For example, say you have two pages in your app, a home page and an about page. These pages can be wrapped inside components Home.vue and About.vue and these can be the split points of the app.
But there are other ways, for example, you could split on any components that are conditionally shown (tabs, modals, dropdown menus etc) or that are below the page fold.
For my next article I'll explore some different code splitting architectures for a Vue.js SPA so stay tuned!
About Anthony Gore
If you enjoyed this article, show your support by buying me a coffee. You might also enjoy taking one of my online courses!
Click to load comments...