The present and future of Vue Router
Alicante 29 May 2019
Eduardo
San Martin Morote
🌍 Vue core team
👨💻 Freelance
📍Paris
🐣 Málaga 🇪🇸
Routing in you App
the 1st week
Routing in your App
after a year
Vue Router
const router = new Router({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/users/:id', component: UserProfile },
]
})
<div>
<router-link to="/">Home</router-link>
<router-link :to="`/users/${this.user.id}`">
My Profile
</router-link>
</div>
🗂 HISTORY
🚦 ROUTER
📦 COMPONENTS
router.push('/search')
<router-view/>
🗂 HISTORy
-
Store visited URLs
-
JS ↔ URL
-
push() replace(), ...
-
listen()
-
🚦 ROUTER
-
Route matching
-
match()
-
resolve()
-
-
Navigation
-
currentRoute
-
push() replace(), ...
-
beforeEach(), ...
-
-
Creating routes
-
new Router({ routes })
-
addRoutes()
-
📦 COMPONENTES
<router-view/>
<router-link to="/">Home</router-link>
SPA
Routers
Three kind of routers
- Imperative
- Declarative
- Configuration-based
Simple & Flexible
but Verbose
Page.js
✍️ Imperative
✅ Programmatic navigation
❌ Declarative navigation
✅ Navigation Guards
❌ Dynamic routing (add/remove routes)
Dynamic routing
router.addRoute('/some-route', options)
router.removeRoute('/some-route')
⚠️ not actual API
Reach Router
<Router>
<Home path="/" />
<UserProfileEdit path="/users/:id/edit" />
</Router>
<div>
<Link to="/">Home</Link>
<Link to={`/users/${this.user.id}/edit`} />
</div>
Reach router
Idiomatic for React
✍️ Declarative
⚠️ Programmatic navigation (in-jsx)
✅ Declarative navigation
❌ Navigation Guards
✅ Dynamic routing (add/remove routes)
Vue router
Decoupled:
router instance / components
✍️ Configuration-based
✅ Programmatic navigation
✅ Declarative navigation
✅ Navigation Guards
⚠️ Dynamic routing (add/remove routes)
The
Good PARTS
📦 router-view
<router-view/>
- Dynamically render current view
- Pass params as props
$route
📦 router-link
- Resolve target location
- Render an anchor tag with link
- Handles click event
- Applies active classes
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'User', params: {id: '2'}}">...</router-link>
$route
$router
this.$route
{
path: '/users/2',
name: 'UserProfile',
query: {},
params: { id: '2' },
meta: {}
}
<p>User: {{ $route.params.id }}</p>
created () {
fetch(`/api/users/${this.$route.params.id}`)
.then(/* ... */)
}
this.$route
created () {
if (someCondition) {
this.$router.push('/other-route')
}
}
this.$router
const router = new Router({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/users/:id', component: UserProfile },
]
})
Global Guards
router.beforeEach((to, from, next) => {
// verify roles based on `to`
// ...
// we can only call `next` once
if (!isLoggedIn) next('/login') // redirect to login
else if (!isAuthorized) next(false) // abort
else next() // allow navigation
})
Per-route Guards
{
path: '/admin',
component: AdminPanel,
beforeEnter (to, from, next) {
if (isAdmin) next()
else next(false)
},
}
in-component guards
export default {
name: 'AdminPanel',
data: () => ({ adminInfo: null }),
async beforeRouteEnter (to, from, next) {
const adminInfo = await getAdminInfo()
next(vm => {
vm.adminInfo = adminInfo
})
},
}
beforeEach
beforeEnter
beforeRouteEnter
next()
next(false)
/posts ➡️ /admin
/posts
Admin.vue
beforeEnter
beforeRouteEnter
next()
/posts ➡️ /admin
await Admin()
The
Sad PARTS
No clear Division of responsibilities
History
Router
router.beforeEach
function beforeEach(guard: NavigationGuard): ListenerRemover {
this.beforeGuards.push(guard)
return () => {
const i = this.beforeGuards.indexOf(guard)
if (i > -1) this.beforeGuards.splice(i, 1)
}
}
router.push
function push (location, onComplete, onAbort) {
this.history.push(location, onComplete, onAbort)
}
history.push
function push (location, onComplete, onAbort) {
const route = this.router.match(location, this.currentLocation)
try {
// run navigation guards queue
// ...
// change the url in the browser (HTML5)
window.history.pushState({}, '', route.fullPath)
onComplete(route)
} catch (error) {
// handle the error
// ...
onAbort(error)
}
}
⚠️ Simplified version
🗂 History
- Base: +300 LoC
- HTML5: 70 LoC
- Hash: 130 LoC
🚦 Router
- Router Class: ~200 LoC
- Matcher: ~180 LoC
😨 Complex codebase
🙂 Simple codebase
No clear Division of responsibilities
- Harder to fix bugs
- Harder to add features
- Harder to contribute
- Harder to extend
Save
Vue
Router
🗂 HISTORy
-
Store visited URLs
-
JS ↔ URL
-
push() replace(), ...
-
listen()
-
🚦 ROUTER
-
Route matching
-
match()
-
resolve()
-
-
Navigation
-
currentRoute
-
push() replace(), ...
-
beforeEach(), ...
-
-
Creating routes
-
new Router({ routes })
-
addRoutes()
-
🗂 HISTORy
-
push() replace()
-
listen()
- URL parsing
API
- Modify the Location
- Parses URL
- path
- query
- hash
- Notifies when Location changes
- Handles Encoding problems
- Can be overloaded
Responsibilities / Expectations
🚦 ROUTER
-
Route matching
-
resolve()
-
- Adding Route Records
-
addRouteRecord
-
removeRouteRecord
-
-
Navigation
-
currentRoute
-
push() replace(), ...
-
-
Navigation Guards
-
beforeEach(), ...
-
- Dynamic Routing
-
addRoute / removeRoute
-
🚦 ROUTER
🛣 Matcher
🛣 Matcher
-
Route matching
-
resolve()
-
- Adding Route Records
-
addRouteRecord
-
removeRouteRecord
-
API
- Resolving a Router Location to a Route Record
- Only handles the path of URL
- Handles priority of Route Records
- Parses/handle params
Responsibilities / Expectations
-
Each segment gets 4 points and then…
-
Static segments get 3 more points
-
Dynamic segments 2 more
-
Root segments 1
-
and finally wildcard segments get a 1 point penalty
-
Navigation
-
currentRoute
-
push() replace()
-
-
Navigation Guards
-
beforeEach()
-
- Dynamic Routing
- Lazy loading Pages
- In-component Guards
- Error handlers
🚦 ROUTER
API
- Async navigation
- Trigger Navigation guards
- Expose current Route Location
- Handle redirections
Responsibilities / Expectations
Browser
Quirks
vue-router/push-state.js
reach-router/history.js
Browser Quirks
URI ENcoding Issues
Directly navigate to
/é?é=é#é
- URL Bar: /é?é=é#é
-
location.pathname: '/%C3%A9'
-
location.search: '%C3%A9=%C3%A9'
-
location.search: '#%C3%A9'
- URL Bar: /é?é=é#é
-
location.pathname: '/é'
-
location.search: 'é=é'
-
location.search: '#é'
history.pushState({}, '', '/é?é=é#é')
Cool
new
Vue
Router
The Future is in Typescript
export type RouteRecord =
| RouteRecordSingleView
| RouteRecordMultipleViews
| RouteRecordRedirect
- Easier to contribute
- Easier to use
More Tests
- Full unit test coverage
- Automated Cross Browser E2E tests
Going Slowly
but Surely
Making sure we cover all cases from previous router
Router
prototype
other routers
other routers
RFCs
- Scoped Slot API for router-link
- Better active matching
- Dynamic History
- a11y improvements
- More navigation information
- Hooks API for router link
Vue 2 & 3
Support both versions
Vue Router
Vue Router
Vue 2
Vue 3
Patreons 🙌
patreon.com/posva
Thanks! 🖖
The present and future of Vue Router
By Eduardo San Martin Morote
The present and future of Vue Router
When we develop single-page applications, we have to use a router. Every single framework has its own router, React even has multiple ones you can choose from. And even though each framework is different and every router takes a different approach, they all share the same principles. What is behind a simple and easy-to-use API? Is it really that difficult to create your own SPA router? What are the different approaches and their advantages, caveats? Let's talk about the existing different approaches we see in SPA routers, today's Vue Router implementation and what is coming for the next versions of Vue Router
- 1,940