{{announcement.body}}
{{announcement.title}}

Build a Lazy-Load Router With Vue.js

DZone 's Guide to

Build a Lazy-Load Router With Vue.js

Lazy-loading is a great tool for creating performant web pages and apps. Learn how you can build a lazy-load router with Vue.js in just a few lines of JavaScript code.

· Web Dev Zone ·
Free Resource

Dynamic module importing is one of the latest JavaScript features to hit the major browsers. The main use case for this feature is lazy-loading modules to allow content to be delivered when it is needed, rather than all at once.

In this article, I'll demonstrate how you can build a lazy-load router with Vue.js in just a few lines of code. This will work natively in browsers that have implemented dynamic module imports, but I'll also include a fallback for older browsers.

Lazy loading components

Grab the finished code here on GitHub.

You may also like: Lazy Loading ES2015 Modules in the Browser.

Static JavaScript Module Imports

If you're using the latest version of any major browser, you can perform static import/export natively now. For Vue.js, this means you can export a component definition from a file like this:

BooksPage.js

JavaScript




x
14


 
1
export default {
2
  name: 'BooksPage',
3
  template: `
4
    <div>
5
     <h1>Books Page</h1>
6
     <p>{{ message }}</p>
7
    </div>
8
  `,
9
  data() {
10
    return {
11
      message: 'Oh hai from the books page'
12
    }
13
  }
14
};



And import it into your app like this:

app.js

JavaScript




xxxxxxxxxx
1


 
1
import BooksPage from './BooksPage';
2
 
          
3
new Vue({
4
  components: {
5
    BooksPage
6
  }
7
});



Making component JavaScript modules allows you to organize your app such that every "page" is in a different file. This is nothing new if you use Vue.js single-file components, but with native support now, this architecture can be achieved without Webpack or Babel.

I cover importing Vue.js components as JavaScript modules more thoroughly in a recent article Vue.js Single-File JavaScript Components In The Browser.

Dynamic JavaScript Module Imports

If your components represent pages, it'd be better to fetch the module files on demand so that a user doesn't have to download pages they don't visit. Static imports are resolved at compile time, however. This means you can't put import BooksPage from './BooksPage'; inside an if statement to conditionally load it. Instead, your static imports will start downloading as soon as the script that loads them runs.

That's where dynamic imports come in. These can be determined at runtime, meaning you can conditionally load a JavaScript module, and therefore pages, on demand. Note that dynamic imports return a Promise which resolves the module content.

JavaScript




xxxxxxxxxx
1
15


 
1
import HomePage from './pages/HomePage.js';
2
 
          
3
Vue.component('my-router', {
4
  template: '<component :is="page"/>',
5
  data() {
6
    return {
7
      page: HomePage
8
    }
9
  },
10
  methods: {
11
    navigate(name) {
12
      this.page = return () => import(`./${name}Page.js`)
13
    }
14
  }
15
});


Note: component is a built-in "meta" component that accepts a component definition through the prop is. Like all Vue components, this can either be a component definition, or a Promise that resolves the component definition.

This code will work natively in the browser if you're using the latest Safari or Chrome Canary, with other browsers soon to follow.

Creating a Tiny Lazy-Load Router

With that theory in mind, let's make our lazy-load router. In your app template, create some anchor tags where the href is the path to the component module for that page. Listen to the click event on these elements, prevent the redirect, and instead trigger a method, navigate.

index.html

HTML




xxxxxxxxxx
1
10


 
1
<div id="app">
2
  <nav>
3
    <a href="/pages/BooksPage.js" @click.prevent="navigate">Books</a>
4
    <a href="/pages/MoviesPage.js" @click.prevent="navigate">Movies</a>
5
    <a href="/pages/GamesPage.js" @click.prevent="navigate">Games</a>
6
  </nav>
7
 
          
8
  <!--Where the page displays-->
9
  <component :is="page"></component>
10
</div>



We'll define the navigate method in the Vue instance and it will accept the click event as an argument. Use the value of the link's href, i.e. event.target.pathname, to dynamically import the required page component module and assign it to the local page state property. This is dynamically bound to the component component.

app.js

JavaScript




xxxxxxxxxx
1
16


 
1
import BooksPage from './pages/BooksPage.js';
2
 
          
3
new Vue({
4
  el: '#app',
5
  data: {
6
    page: BooksPage
7
  },
8
  methods: {
9
    navigate(event) {
10
      this.page = () => import(`./${event.target.pathname}`)
11
      // Vue.js < 2.5.0
12
      // .then(m => m.default)
13
      ;
14
    }
15
  }
16
});


Note that versions of Vue.js before 2.5.0 need to include a then callback to correctly resolve the module defintion.

That's it! If you run this in a browser that supports dynamic imports you'll see this:

Example output

Fallback

What about users who don't have the latest version of Safari or Chrome? They'll need a fallback. Let's create one with Webpack.

Firstly, we'll have to modify the navigate method a bit. Webpack's implementation of import() requires it to know ahead of time which files it may need to dynamically load. You don't need to specify the exact module name, just make sure the value you provide to import() is a resolvable file or directory.

To do this, we'll change the dynamic file name so it specifies the pages directory and extracts the filename from the href i.e. ./pages/${event.target.pathname.split('/').pop()}. At compile-time, Webpack is smart enough to know this means "some file in the pages directory" and will process any JavaScript file in this directory.

Secondly, we need to put the comment /* webpackChunkName: "pages/[request]" */ in the function so that Webpack knows to extract this chunk to a separate file. Otherwise, Webpack will bundle all the page component modules in one file and the benefits of lazy-loading will be forfeit.

app.js

JavaScript




xxxxxxxxxx
1


 
1
navigate(event) {
2
  this.page = () => import(
3
    /* webpackChunkName: "pages/[request]" */
4
    `./pages/${event.target.pathname.split('/').pop()}`
5
  )
6
}



Webpack Config

You can use this simple Webpack config which has two notable features:

  1. Specifies a chunkFilename output property. This ensures the page component modules are named correctly in the Webpack output.
  2. Transpiles the JavaScript with Babel. You'll need the plugin syntax-dynamic-import for Babel to recognize the dynamic import statement.
JavaScript




xxxxxxxxxx
1
24


 
1
var path = require('path');
2
var webpack = require('webpack');
3
 
          
4
module.exports = {
5
  entry: './app.js',
6
  output: {
7
    path: path.resolve(__dirname, './dist'),
8
    publicPath: '/dist/',
9
    filename: 'build.js',
10
    chunkFilename: '[name].js'
11
  },
12
  module: {
13
    rules: [
14
      {
15
        test: /\.js$/,
16
        loader: 'babel-loader',
17
        exclude: /node_modules/,
18
        options: {
19
          plugins: [require('babel-plugin-syntax-dynamic-import')]
20
        }
21
      }
22
    ]
23
  }
24
};



Run Webpack with that config and you'll get build files including:

  • All the JavaScript module files transpiled to CommonJS modules.
  • The main bundle will include Webpack's implementation of dynamic import.

Webpack dynamic import

Checking for Dynamic Import Support

How do you tell the browser to use the fallback? As far as I know, there's no specific way to check for browser support for import(). My strategy is to use an inline script in the body of the document which creates a new script tag and will conditionally change the source (either the main script or the fallback) depending on support for import().

HTML
xxxxxxxxxx
1
12
 
1
<script type="text/javascript">
2
  var script = document.createElement('script');
3
  try {
4
    Function('import("")');
5
    script.src = "app.js";
6
    script.type = 'module';
7
  } catch(e) {
8
    script.src = "dist/build.js";
9
    script.type = 'text/javascript';
10
  }
11
  document.body.appendChild(script);
12
</script>


Note that the fallback script is treated as a normal JavaScript script, whereas the main script is treated as a module.

Conclusion

It's great if we can use the native implementations of JavaScript module imports as optimizations like lazy-loading can be done with a smaller file size and much simpler implementation. For example, Webpack's implementation of import() requires it to know ahead of time which files it may need to dynamically load, the native implementation does not.

In practice, features like this will need to be progressive enhancements and will, therefore, require Webpack as a fallback, which reintroduces the same complications. Oh well, that's web dev for you.


Further Reading

Topics:
vuejs 2 ,javascript ,router ,lazy loading ,web dev

Published at DZone with permission of Anthony Gore , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}