Vue-router: How can I use `vue-router` along with async components for lazy loading?

Created on 4 Nov 2015  Â·  31Comments  Â·  Source: vuejs/vue-router

Is there an example I can follow?

Most helpful comment

It's exactly like async components with Vue.js only: http://vuejs.org/guide/components.html#Async_Components

You can write:

import Vue from 'vue';
import VueRouter from 'vue-router';
import config from 'config';

Vue.use(VueRouter)

const router = new VueRouter({history: true, root: config.root});

router.map({
    '/': {
        name: 'home',
        component: view('home')
    },
    '/user/:oid/': {
        name: 'user',
        component: view('user')
    },
    '/post/new/': {
        name: 'new-post',
        component: view('post-wizard')
    },
    '/post/:oid/': {
        name: 'post',
        component: view('post')
    }
});


/**
 * Asynchronously load view (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the view to load.
 */
function view(name) {
    return function(resolve) {
        require(['./views/' + name + '.vue'], resolve);
    }
};

export default router;

In my case, routable views always have views/name.vue as filename pattern.
Just adapt to your needs.

All 31 comments

It's exactly like async components with Vue.js only: http://vuejs.org/guide/components.html#Async_Components

You can write:

import Vue from 'vue';
import VueRouter from 'vue-router';
import config from 'config';

Vue.use(VueRouter)

const router = new VueRouter({history: true, root: config.root});

router.map({
    '/': {
        name: 'home',
        component: view('home')
    },
    '/user/:oid/': {
        name: 'user',
        component: view('user')
    },
    '/post/new/': {
        name: 'new-post',
        component: view('post-wizard')
    },
    '/post/:oid/': {
        name: 'post',
        component: view('post')
    }
});


/**
 * Asynchronously load view (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the view to load.
 */
function view(name) {
    return function(resolve) {
        require(['./views/' + name + '.vue'], resolve);
    }
};

export default router;

In my case, routable views always have views/name.vue as filename pattern.
Just adapt to your needs.

Hi noirbizarre,

Thanks for your solution! I have a question, and vue.js is very new to me; using your function it creates a new .js file with the four components above combined, for example (home, user, post-wizard and post). Doesn't this defeat the purpose of lazy loading by loading everything into one external file? Shouldn't they each be in a separate .js file? Sorry if I've misunderstood something.

Thanks

No that's the point, using this pattern with webpack is compatible with code splitting (http://webpack.github.io/docs/code-splitting.html).
Each view is in its own vue file (using vue-loader) and webpack will detect the pattern './views/' + name + '.vue' as a code splitting point and will create chunks for each (depending of your webpack configuration) that will be lazy loaded on first access (when you hit the route).

I mean shouldn't each vue component be split into multiple build .js files instead of merging the chunks into one build .js file? How does the lazy loading work if it loads a .js file containing all component chunks in one file?

Thanks for your help explaining

The all-in-one file is created by the context ('./views/' + name + '.vue')

If you don't want that, rewrite it like that:

import Vue from 'vue';
import VueRouter from 'vue-router';
import config from 'config';

Vue.use(VueRouter)

const router = new VueRouter({history: true, root: config.root});

router.map({
    '/': {
        name: 'home',
        component: function(resolve) {
            require(['./views/home.vue'], resolve);
        }
    },
    '/user/:oid/': {
        name: 'user',
        component: function(resolve) {
             require(['./views/user.vue'], resolve);
        }
    }
});

export default router;

Each require([], resolve) will create a split point and a lazy loaded chunk.

Thanks @noirbizarre for the explanation :)

You're welcome ;)

hi @noirbizarre when I try your method above, each route I replace with the async function renders the view broken and the view will no longer load. In the dev console in chrome it just says 'Uncaught SyntaxError: Unexpected token <' when I try to change to a new asynchronously loaded view. What could be the problem?

Many thanks

I don't think this is related to async loading.

There might be a syntax error in one of your loaded view.
Without code sample I can't find the syntax error.

Given the fact it happen on every view you try to load and given the unexpected token is <, I'm wondering if your vue-loader is correctly configured in webpack.

Cool stuff here :)
EDIT: Right, I tried it and I got the < error too.

My webpack.config.js file is as follows:

var webpack = require('webpack');
var path = require('path');
//var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    cache: true,
    entry: {
        'init': "./src/init.js"
    },
  output: {
      path: './dist/js',
      filename: "[name].js",
      chunkFilename: "[chunkhash].js"
  },
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint-loader',
        exclude: /(node_modules|bower_components)/
      }
    ],
    loaders: [
      {
        test: /\.vue$/,
        loader: 'vue'
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel',
        query: {
            presets: ['es2015']
        }
      }
    ]
  },
  resolveLoader: {                                                                                
      root: path.join(__dirname, 'node_modules')                                                  
  }, 
    plugins: [
        new webpack.ProvidePlugin({
            jQuery: "jquery",
            $: "jquery"
        })
    ]
};

Weird because I have this pattern on my app and it's working.
The only difference is that I sticked on vue-loader 5.0.0 because 7.0.x is not working for me.

This is exactly what I need, really helpful. webpack code splitting is great.

Thank you for this. The webpack code splitting feature really is amazing!

Thank you for this.
感谢,解决了我的问题。

Exactly what I'm looking for! Thank you!

I'm having a similar issue, the difference is that my components are not inside a single directory where I can just pass in the name of the component I need to load, so what I was trying to do is passing the component path, however if I do that i get the following error:

Critical dependencies:
8:11-10:6 the request of a dependency is an expression

my method looks like this:

export const asyncLoader = path => {
  return resolve => require([path], m => resolve(m.default));
};

Notice I need to call resolve passing in m.default because my components are js files that use export default, so when they are required it gives an object {default: (your component)}
The method is being used like this:

import {asyncLoader} from "services";

export const aboutRoute = {
  path: "/about", component: asyncLoader("app/about/about.vue.js")
};

Notice that my components are not .vue files (at least not the route components)
Any suggestions how to get around the fact that Webpack needs to be able to read all require paths statically so just passing a parameter with the path doesn't work...

Thanks..

@noirbizarre Thanks for the solution.

And I notice that you have no reject callback, I find that webpack2 has the reject callback,and it works well.For webpack1,you can use this plugin: require-error-handler-webpack-plugin.It has been a long time since you gave this solution. Maybe you have found some more excellent solutions.

But I also have a question:
How to deal with the timeout of the require?
You konw that , when the network is bad, this maybe a long time to request the components.So i want to find out how can I set the timeout time,just like a ajax request.

At last , sorry for my poor English.

Hello everyone i tried this solution and it works perfectly on my code by i have a doubt
do you know if you can load a component totally from another server for example i have this gist

https://gist.githubusercontent.com/shagflak/c9ab75ca4b811c04472169c724509335/raw/aa77f928b79c73d99d8c89e75da6a974c0903e22/users.vue

And i will like to lazy load this coponent remotely for example that component living on another server and ask for it to deploy it on my app like this on my main .js file i am currently using vue-cli webpack template

import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
// import Users from '@/components/Users'

Vue.use(Router)

/**
 * Asynchronously load view (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the view to load.
 */
function view (name) {
  return function (resolve) {
    // require(['../components/' + name + '.vue'], resolve)
    require(['https://gist.githubusercontent.com/shagflak/c9ab75ca4b811c04472169c724509335/raw/aa77f928b79c73d99d8c89e75da6a974c0903e22/' + name + '.vue'], resolve)
  }
}

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    },
    {
      path: '/users',
      name: 'Users',
      // component: view('Users')
      component: view('users')
    }
  ]
})

It is posssible to do this? i had done some research and it looks like it's not possible but i am not really sure if it is because the require of webpack code splitting can't do that when running this code i get this warning on my console

This dependency was not found:

 https://gist.githubusercontent.com/shagflak/c9ab75ca4b811c04472169c724509335/raw/aa77f928b79c73d99d8c89e75da6a974c0903e22 in ./src/router/index.js

To install it, you can run: npm install --save https://gist.githubusercontent.com/shagflak/c9ab75ca4b811c04472169c724509335/raw/aa77f928b79c73d99d8c89e75da6a974c0903e22

I don't really think this will work with publicPath either because that it's only for assets called within thebrowser once everything it's compiled any response on this will be appreciatted.

Thanks for the help.

I had to replace in order to make it work with the [email protected], [email protected] and Vue [email protected]. Had to change require to System.import (ES6), which returns a Promise, then I fire the callback and it do works.

For some reason, the "code splitted" was ending up into the vendor chunk in the first comment response on this issue, so it wasn't lazy loaded ("preloaded", in that situation).

My view() function:

``javascript function view(name) { return function(resolve) { System.import(./views/${name}.vue`).then(mod => {
resolve(mod)
})
}
}

@noirbizarre I use the view method to lazyload vue component, but has error:

Error: Failed to resolve async component default: AssertionError: path must be a string
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1708:17
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1766:15
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1717:11
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1742:66
    at Array.map (native)
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1742:38
    at Array.map (native)
    at flatMapComponents (/Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1741:26)
    at /Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1677:5
    at iterator (/Users/Silence/Desktop/electron-demo/jdquant-client/node_modules/[email protected]@vue-router/dist/vue-router.js:1876:7)
  1. Above that lazy load must rely on webpack? While, I am not use it.
  2. I am trying many way, but there is no way to work properly. such as:
'use strict';
import Vue from 'vue/dist/vue.js';
import Router from 'vue-router/dist/vue-router.js';

Vue.use(Router);

let router = new Router({
    routes: [
        {
            path: '/',
            redirect: '/index'
        },
        {
            name: 'login',
            path: '/login',
            // component: require('./components/Login.vue') //working
            // component: resolve => System.import('./components/Login.vue') //not working
            // component: () => import('./components/Login.vue')  //not working
            component: view('Login')  //not working
        },
        {
            name: 'test',
            path: '/test/:userId',
            component: resolve => require(['./components/Test.vue'], resolve)  //not working
        },
        {
            path: '*',
            redirect: '/'
        }
    ]
});

/**
 * Asynchronously load view (Webpack Lazy loading compatible)
 * @param  {string}   name     the filename (basename) of the view to load.
 */
function view(name) {
    return function(resolve) {
        require(['./components/' + name + '.vue'], resolve);
    }
};

export default router;

Can your help me point out the mistake? Thanks! waiting...

another approach without playing with require/import:

main.js

import subfolder1 from './components/subfolder1/index'

const componentsMap = {
    'subfolder1': subfolder1
}
const getComponent = (subfolder, name) => {
  return (resolve) => {
    resolve(componentsMap[subfolder][name])
  }
}

const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    {
      path: '/asd', component: getComponent('subfolder1', 'Asd')
    }
  ]
})

in subfolder1/index.js

import Asd from "./Asd.vue"

export default  {
    Asd: Asd
}

Im having similar issue and trying to asyncly load a component inside the Router component and getting the following error:

[Vue warn]: Failed to resolve async component: ()=>t.e(0).then(t.bind(null,138)) Reason: Error: Loading chunk 0 failed.

Route config:

export default new Router({
  routes: [
    {
      path: "/",
      name: "HomePage",
      component: HomePage
    }
  ]
});

HomePage.vue:

import WhyChooseUs from "./WhyChooseUs";
  const BelowFold = () => import(/* webpackChunkName: "below-fold" */ './BelowFold.vue'); // eslint-disable-line

  export default {
    name: "HomePage",
    components: {
      WhyChooseUs,
      BelowFold
    }
  };

Webpack Output:

output: {
    path: `${root}/public/assets/`,
    publicPath: "/lead/assets",
    filename: `${fileName()}.js`,
    chunkFilename: "[name].[chunkhash].js"
  }

Im only trying to lazy load the components that are in the BelowFold Components. The code splitting is working as I can see the below-fold.b0787f8e6be34fdd3141.js in my assets folder and in the server. But in the error it says error loading chunk 0 instead of below-fold?

@bajras Did you end up fixing this?

@bajras Did you end up fixing this? +1

Anyone any idea how to export a router config from a node_module and lazy load components from there e.g. if you have App1, and App2 and compile app2 and export an array of router configs, can you merge those routers together and have then an externally developed and compiled application.

Anyone any idea how to export a router config from a node_module and lazy load components from there e.g. if you have App1, and App2 and compile app2 and export an array of router configs, can you merge those routers together and have then an externally developed and compiled application.

Maybe router.addRoutes(routes) can solve your problem.

@devinRex i've tried that however if I have the app freeze and hit reload on the page I lose the current context of where I am. Theres no way of persisting those routes through a page refresh, the majority of examples use a simple { template: '<span>test</span>} and save it to localstorage but when you use an actual component i've not found anyway of persisting through localstorage.

image

{
    "vue": "=2.5.22",
    "vue-router": "=3.0.2",
    "@vue/cli": "=3.3.0",
    "@vue/cli-plugin-babel": "=3.3.0",
    "@vue/cli-plugin-e2e-nightwatch": "=3.3.0",
    "@vue/cli-plugin-eslint": "=3.3.0",
    "@vue/cli-plugin-pwa": "=3.3.0",
    "@vue/cli-plugin-unit-mocha": "=3.3.0",
    "@vue/cli-service": "=3.3.0",
    "@vue/test-utils": "=1.0.0-beta.25",
    "vue-loader": "=15.5.1",
    "vue-template-compiler": "=2.5.22"
}

fixed, just use previous version in some packages.
trouble with using "@babel/plugin .. dynamic import" in "@vue/cli" builder (override throught vue.config.js not helped)

The all-in-one file is created by the context ('./views/' + name + '.vue')

If you don't want that, rewrite it like that:

import Vue from 'vue';
import VueRouter from 'vue-router';
import config from 'config';

Vue.use(VueRouter)

const router = new VueRouter({history: true, root: config.root});

router.map({
    '/': {
        name: 'home',
        component: function(resolve) {
            require(['./views/home.vue'], resolve);
        }
    },
    '/user/:oid/': {
        name: 'user',
        component: function(resolve) {
             require(['./views/user.vue'], resolve);
        }
    }
});

export default router;

Each require([], resolve) will create a split point and a lazy loaded chunk.

The address of the vue component is obtained through the backend. Why does the browser prompt that it cannot be found after the string is loaded?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thomas-alrek picture thomas-alrek  Â·  3Comments

alexstanbury picture alexstanbury  Â·  3Comments

Atinux picture Atinux  Â·  3Comments

posva picture posva  Â·  3Comments

achen224 picture achen224  Â·  3Comments