Vue Router lazy loading and chunking

29 diciembre 2019

When you start to create SPA (Single page application) you must bear in mind that SPA doesn’t mean Single JavaScript file.

You normally use Webpack to handle your app builds, by default, Webpack create one file for all assets, even CSS.

The first step, maybe, is separate styles from app.js in their own CSS files.

To do this, we’ll use the Webpack plugin MiniCssExtractPlugin which we’ll configure like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
],
...
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '/public/path/to/',
},
},
'css-loader',
... // Other loaders like sass-loader or postcss-loader
],
}
...
]
},

This forces Webpack to extract CSS into separated files, for example app.css

If you use vue-cli, this is the default config for Webpack.

Going forward

For simple apps it is a good idea keep all your built code into a single file, because client’s browser loads app.css the first time user accesses your app and keep it in cache, next access the file will be served from local browser’s cache (until cache expire).

But when your application starts to grow the app.js will be huge, slowing down the page loading. There will even be parts of the app that are never used, for example “pages” (in this context think pages as Vue page component, not static pages) forbidden for regular users.

In this case a good solution is chunking your app.js using async components for page components. You can split every page into different files which will be loaded when user navigates to route.

This strategy uses the Webpack’s code splitting feature.

In Vue router configuration you just do

1
2
3
4
5
6
7
8
const routes = [
{
path: '/',
name: 'home',
component: () => import('./views/HomeComponent')
},
...
]

Webpack now will create a separated file for your page components. But this way the user receives no feedback about the loading process. We could improve the router using a loading component.

An async component must provide a Promise.resolve. When you write () => import('./views/HomeComponent') implicit you return a Promise that resolves the component. But if you want to use the handling loading state you need to return an explicit Promise

Like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import LoadingComponent from './LoadingComponent'

const routes = [
{
path: '/',
name: 'home',
component: lazyLoading(import('./views/HomeComponent'))
},
...
]
function lazyLoadView (AsyncPageComponent) {
const AsyncHandler = () => ({
component: AsyncPageComponent,
loading: LoadingComponent,
...
})

return Promise.resolve({
functional: true,
render: (h, { data, children }) => h(AsyncHandler, data, children)
})
}

As you can see, we use a Promise.resolve that returns component render function.

data and children are necessary to pass props, attributes and events to component More info

With these changes, when the user navigate to / firstly, the app shows the LoadingComponent and then, when the component is fully loaded, shows it.
Finally, say that you can group the components in the same chunk using the following notation.

1
2
3
import(/* webpackChunkName: "group-main" */ './HomeComponent.vue')
import(/* webpackChunkName: "group-main" */ './LoginComponent.vue')
import(/* webpackChunkName: "group-admin" */ './AdminPageComponent.vue')
Para mostrar los comentarios es necesario la aceptación del uso de cookies

Programador y desarrollador de aplicaciones web, #drupal es mi guía. Amante de la #f1, de las buenas conversaciones y de los pequeños detalles.