So far we’ve build the router in a component and a history module within the source code of our app. The problem of it is that the router is tied to the app, specially because the routes are defined within the AppRouter
component.
That could be improved by moving out the routes from the component and passing them as a parameter. But still, the developer must import the AppRouter
component and the history.js
module and use it around.
We could improve and simplify it by creating a Vue.js plugin, which will make the router 100% reusable between apps and will allow to make it easier to use and customizable for the final developer.
Creating a Vue.js plugin is not something that we need do from scratch: we can create it by using the components and modules that we already have. That means the process to create a plugin could be progressive, you can delay it until you need it. In our case, we have the AppRouter.vue
component and the history.js
module already in place.
Let's see how we can use them to create a Vue.js plugin for the Router.
From now on, consider the router to be an external package that we could install from npm that "just works". But for now, in order to separate the plugin from the rest of the application, let's better create a folder called plugin
where we move the AppRouter.vue
component.
The Vue.js plugin structure should be an object with an install(Vue, options)
function signature. This install function is called when we register the plugin, by using Vue.use(AnyPlugin)
.
The install
function takes two parameters:
**Vue**
: the global Vue object that we can manipulate according to our needs.**options**
: the options passed as the second argument to the Vue.use
function call.Let's then create a plugin/index.js
file as the entry point for the router plugin:
// plugin/index.js
export default {
install(Vue, options) {}
};
Within the install
function of a Vue.js plugin, we can do several things on the global scope:
Vue.prototype
The first thing we can do is to register the AppRouter.vue
component. In that way, we can remove it from App.vue
but still use it as <app-router></app-router>
since it’s now registered globally:
// plugin/index.js
import AppRouter from "./AppRouter";
export default {
install(Vue, options) {
Vue.component("AppRouter", AppRouter);
}
};
Remember we have a history.js
module as well? Its logic is tied to the router and it would be nice if the final user doesn't know about its existence. So start by moving the history.js
module to the plugin
folder.
But now we have a problem: the history.js
module is used in AppRouter.vue
as well as in App.vue
. Thus, is not enough just to move it, since App.vue
would still need access to it.
To solve that, we can add a $pushRoute
instance method on the install
function:
// plugin/index.js
import AppRouter from "./AppRouter";
import { push } from "./history";
export default {
install(Vue, options) {
Vue.component("AppRouter", AppRouter);
// Set $pushRoute to the push function
Vue.prototype.$pushRoute = push;
}
};
Have you noticed the dollar $
prefix? That's a convention taken in the Vue.js community to define instance methods and properties. You can find it on the Vue.js style guide as well
By adding the $pushRoute
method we removed the dependency between App.vue
and history.js
.
Because of having the AppRouter
component registered globally and the $pushRoute
method added to the Vue instance, we can go to App.vue
and remove the imports, component and push
references, cleaning up its dependencies:
<!-- App.vue -->
<template>
<div>
<button @click="goTo('/articles')">Go to Articles</button>
<button @click="goTo('/')">Go to Home</button>
<app-router></app-router>
</div>
</template>
<script>
export default {
methods: {
goTo(route) {
this.$pushRoute(route);
}
}
};
</script>
We still need to somehow pass the routes to the router plugin, and there are several options to do so.
If you're curious enough, you can check that in the official vue-router they use a beforeCreate
mixin, accessing the router
actions from the instance options:
Vue.mixin({
beforeCreate() {
this._router = this.$options.router;
// ...
}
});
If you do it in that way, you'd pass the router
as an option to the root Vue.js instance:
const router = new Router({
"/": SomeComponent
});
new Vue({
el: "#app",
router,
render: h => h(App)
});
But here we'll use a simpler approach: we can use the plugin options to pass the routes to the plugin and then we'll store them in an instance property within the install
function:
// plugin/index.js
import AppRouter from "./AppRouter";
import { push } from "./history";
export default {
install(Vue, options) {
Vue.component("AppRouter", AppRouter);
// Add the routes to the $routes instance property
Vue.prototype.$routes = options.routes;
Vue.prototype.$pushRoute = push;
}
};
Finally, update the AppRouter.vue
computed property routedComponent
so it gets the right route from the $routes
instance property:
// AppRouter.vue
export default {
computed: {
routedComponent() {
return this.$routes[this.current];
}
}
};
The router plugin is ready to use and totally decoupled from the app, now is time to use it.
A Vue.js plugin needs to be invoked using Vue.use(Plugin)
, and our router plugin needs a routes
options. Then, go to the root file of your app and update it accordingly:
// index.js
import Vue from "vue";
import App from "./src/App";
import AppRouter from "./plugin";
import HomePage from "./src/HomePage";
import ArticlesPage from "./src/ArticlesPage";
// Create routes objects
const routes = {
"/": HomePage,
"/articles": ArticlesPage
};
// Use the plugin
Vue.use(AppRouter, {
routes
});
// ...
If you run the project, it should work exactly as it was working before.
Having the router as a plugin gives us the flexibility to add more options and functionality to it. For example, it's usual to have router hooks at different levels.
Those hooks are functions that are triggered at certain points when the route changes. For example, it could be before or after the route changes, allowing the user to perform different actions at those points.
For simplicity and since we only have one listeners
variable available in history.js
module, add just a global hook after the route change. Let's use a hook
router option to define a hook that just logs from where it comes and where it goes when a route change happens:
// index.js
// ...
Vue.use(AppRouter, {
routes,
hook: (to, from) => console.log(to, from)
});
// ...
Remember the listener callbacks receive a to
and from
parameters, that's why we have access to them. Then, from the install
function of the router plugin, we can use the listen
function from the history.js
module to be notified of the route changes:
import AppRouter from "./AppRouter";
import { listen, push } from "./history";
export default {
install(Vue, options) {
Vue.component("AppRouter", AppRouter);
Vue.prototype.$routes = options.routes;
Vue.prototype.$pushRoute = push;
// Add a listener if the hook option is provided
if (options.hook) {
listen(options.hook);
}
}
};
Remember to check for the existence of the options.hook
property to prevent it from crashing.
We've made a quite simple yet complete router plugin from scratch. If you've followed along, I'm sure you understand the basics of a router and how they work under the hood.
I hope it has opened up your mind as well, so you can add more features to the router we've created, such as:
<link>
global component to declaratively navigate through routesYou can find here the code and demo from this article.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.