how to build spa with vue router 2.0

22
How to Build SPA with Vue Router 2.0 2016/08/24 Takuya Tejima

Upload: takuya-tejima

Post on 16-Apr-2017

10.993 views

Category:

Engineering


3 download

TRANSCRIPT

Page 1: How to Build SPA with Vue Router 2.0

How to Build SPA with Vue Router 2.0

2016/08/24Takuya Tejima

Page 2: How to Build SPA with Vue Router 2.0

Who?

• Takuya Tejima @tejitak

• Co-Founder & CTO at Indie Inc. (ex-IBM, ex-LINE)

• Server & Web Front-End & iOS Engineer

• Community

• Vue.js core team member

• Dev Morning community founder

• http://devmorning.connpass.com/

• Global Startup Creators co-founder

• https://www.facebook.com/globalstartupcreators/

Page 3: How to Build SPA with Vue Router 2.0

What’s SPA

• SPA (Single Page Aplication)

• SPA is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience similar to a desktop application.

• Important things to introduce

• Does you app really need to be SPA, such as partial rendering?

• It may not be easy compare to typical standard server side implementation

• There are no best frameworks for all situations

• There are a lot of frameworks

• Backbone? Ember? Riot? Angular + ui-router? Angular 2? React + React-Router (+ Redux)? Vue.js + Vue-Router (+ Vuex)?

• If you decide to build SPA on your next webapp project, Vue.js + Vue Router would be a good option

Page 4: How to Build SPA with Vue Router 2.0

• Vue Router Features• Dead simple for component mapping with routes• Nested routes and sub components• Support SSR (Server Side Rendering) and Virtual DOM• Async load• Flexible hooks• History management, Scroll behavior, transition etc.

• Vue Router 2.0 (Currently Beta) is really powerful!! Especially on…

• Better performance for SPA by reactive components mechanism

• SSR & SPA with isomorphic fetch and client hydration

• It means the components will dynamically work in client side generated by server side same modules. It’s available without server side template engine like EJS! And, it has better SEO compared to ordinary SPA

What’s Vue Router?

Page 5: How to Build SPA with Vue Router 2.0

Work with SSR

• What’s BundleRenderer?

• Generate components for each requests from code to prevent shared modules on node

• TIPS: To skip parsing entire app dependencies every requests, we can specify webpack externals options

Page 6: How to Build SPA with Vue Router 2.0

Cont. Work with SSR

import Vue from 'vue'import App from './App.vue'import store from './store'import router from './router'import { sync } from 'vuex-router-sync'

// sync the router with the vuex store.// this registers `store.state.route`sync(store, router)

const app = new Vue({ router, store, ...App})

export { app, router, store }

app.js

require('es6-promise').polyfill()import { app, store } from './app'

store.replaceState(window.__INITIAL_STATE__)

app.$mount('#app')

client-entry.js

import { app, router, store } from './app'

const isDev = process.env.NODE_ENV !== 'production'

export default context => {

router.push(context.url)

const s = isDev && Date.now()

return Promise.all(router.getMatchedComponents().map(component => { if (component.preFetch) { return component.preFetch(store) } })).then(() => { context.initialState = store.state return app })}

server-entry.js

Page 7: How to Build SPA with Vue Router 2.0

Client Side Hydration• Client side Vue instance will attempt to "hydrate" the existing DOM instead

of creating new DOM nodes when the server-rendered="true" attribute exists

• Demo: Vue hackernews 2.0

• https://github.com/vuejs/vue-hackernews-2.0

• SSR + SPA (Client side hydration) can be achieved by Vue 2.0 & Vue Router 2.0 & Vuex 2.0!

• Vue: SSR

• Vue Router: Routing mapping management

• Vuex: State management & Isomorphic data fetch

Page 8: How to Build SPA with Vue Router 2.0

Isomorphic Data Fetch with Vuex

import Vue from 'vue'import Vuex from 'vuex'import {fetchItems} from './api'

Vue.use(Vuex)

const store = new Vuex.Store({ state: { itemsPerPage: 20, items: {/* [id: number]: Item */} },

actions: { FETCH_ITEMS: ({ commit, state }, { ids }) => { ids = ids.filter(id => !state.items[id]) if (ids.length) { return fetchItems(ids).then(items => commit('SET_ITEMS', { items })) } else { return Promise.resolve() } } },

mutations: { SET_ITEMS: (state, { items }) => { items.forEach(item => { if (item) { Vue.set(state.items, item.id, item) } }) } }})

export default store

store/index.js store/api.jsimport Firebase from 'firebase'

const api = new Firebase('https://hacker-news.firebaseio.com/v0')

function fetch (child) { return new Promise((resolve, reject) => { api.child(child).once('value', snapshot => { resolve(snapshot.val()) }, reject) })}

export function fetchItem (id) { return fetch(`item/${id}`)}

export function fetchItems (ids) { return Promise.all(ids.map(id => fetchItem(id)))}

Page 9: How to Build SPA with Vue Router 2.0

Server Side Component Cache

• Server side cache makes SSR + SPA faster

• Component layer server side cache

• cache components by implementing the serverCacheKey function

• API layer server side cache

• LRU cache examples:

const cache = inBrowser ? null : (process.__API_CACHE__ || (process.__API_CACHE__ = createCache()))

function createCache () { return LRU({ max: 1000, maxAge: 1000 * 60 * 15 // 15 min cache })}

const api = inBrowser ? new Firebase('https://hacker-news.firebaseio.com/v0') : (process.__API__ || (process.__API__ = createServerSideAPI()))

export default { name: 'item', // required props: ['item'], serverCacheKey: props => props.item.id, render (h) { return h('div', this.item.id) }}

Page 10: How to Build SPA with Vue Router 2.0

• Picked breaking changes from previous version

• Install

• routes config are changed to Array

• Navigations

• history.go -> history.push

• v-link directive -> <router-link> component

• Hooks

• The hooks data, activate, deactivate, canActivate, canDeactivate, canReuse are now replaced• activate & deactivate -> Component's own lifecycle hooks• data -> Use a watcher on $route to react to route changes• canActivate -> beforeEnter guards declared in route configurations• canDeactivate -> beforeRouteLeave defined at the root level of a component's definition• canReuse -> removed because it is confusing and rarely useful

Migration from Vue Router v0.x.xconst router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/users', component: Users, children: [ { path: ':username', component: User } ] } ]})

watch: { '$route': 'fetchData' }

Page 11: How to Build SPA with Vue Router 2.0

1. Use plugin

2. Define route components

3. Create the router

4. Create and mount root instance.

Vue Router 2.0 - Install

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

Vue.use(VueRouter)

const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', component: { template: '<div>home</div>' } }, { path: '/foo', component: { template: '<div>foo</div>' } }, { path: '/bar', component: { template: '<div>bar</div>' } } ]})

new Vue({ router, template: ` <div id="app"> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> </ul> <router-view class="view"></router-view> </div> `}).$mount('#app')

Page 12: How to Build SPA with Vue Router 2.0

• Examples

Vue Router 2.0 - <router-link>

<li><router-link to="/">/</router-link></li>

<li><router-link to="/users" exact>/users (exact match)</router-link></li>

<li><router-link to="/users/evan#foo">/users/evan#foo</router-link></li>

<li> <router-link :to="{ path: '/users/evan', query: { foo: 'bar', baz: 'qux' }}"> /users/evan?foo=bar&baz=qux </router-link></li>

Page 13: How to Build SPA with Vue Router 2.0

• A single route can define multiple named components

Vue Router 2.0 - Named view

const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } }, { path: '/other', components: { default: Baz, a: Bar, b: Foo } } ]})

new Vue({ router, data () { return { name: 'b' } }, watch: { '$route' () { this.name = 'a' } }, template: ` <div id="app"> <h1>Named Views</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/other">/other</router-link></li> </ul> <router-view class="view one"></router-view> <router-view class="view two" :name="name"></router-view> <router-view class="view three" name="b"></router-view> </div> `}).$mount('#app')

router config vue instance

Page 14: How to Build SPA with Vue Router 2.0

• Alias / Redirect examples

Vue Router 2.0 - Alias / Redirect

routes: [ { path: '/home', component: Home, children: [ // absolute alias { path: 'foo', component: Foo, alias: '/foo' }, // relative alias (alias to /home/bar-alias) { path: 'bar', component: Bar, alias: 'bar-alias' }, // multiple aliases { path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] } ] }, // absolute redirect { path: '/absolute-redirect', redirect: '/bar' }, // named redirect { path: '/named-redirect', redirect: { name: 'baz' }},

// redirect with params { path: '/redirect-with-params/:id', redirect: '/with-params/:id' },

// catch all redirect { path: '*', redirect: '/' } ]

Page 15: How to Build SPA with Vue Router 2.0

• beforeEnter examples

Vue Router 2.0 - Guard Examples 1

import Dashboard from './components/Dashboard.vue'import Login from './components/Login.vue'

function requireAuth (route, redirect, next) { if (!auth.loggedIn()) { redirect({ path: '/login', query: { redirect: route.fullPath } }) } else { next() }}

const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/dashboard', component: Dashboard, beforeEnter: requireAuth }, { path: '/login', component: Login } ]})

https://github.com/vuejs/vue-router/blob/next/examples/auth-flow/app.js

Page 16: How to Build SPA with Vue Router 2.0

• beforeRouterLeave examples

Vue Router 2.0 - Guard Examples 2

const Baz = { data () { return { saved: false } }, template: ` <div> <p>baz ({{ saved ? 'saved' : 'not saved' }})<p> <button @click="saved = true">save</button> </div> `, beforeRouteLeave (route, redirect, next) { if (this.saved || window.confirm('Not saved, are you sure you want to navigate away?')) { next() } }}

const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', component: Home },

{ path: '/baz', component: Baz } ]})

https://github.com/vuejs/vue-router/blob/next/examples/navigation-guards/app.js

Page 17: How to Build SPA with Vue Router 2.0

• Webpack will automatically split and lazy-load the split modules when using AMD require syntax

Vue Router 2.0 - Async Components

const Foo = resolve => require(['./Foo.vue'], resolve)const Bar = resolve => require(['./Bar.vue'], resolve)

const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', component: Home }, // Just use them normally in the route config { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ]})

https://github.com/vuejs/vue-router/blob/next/examples/lazy-loading/app.js

Page 18: How to Build SPA with Vue Router 2.0

• scrollBehavior• only available in html5 history

mode• defaults to no scroll behavior• return false to prevent scroll

Vue Router 2.0 - scrollBehaviorconst scrollBehavior = (to, from, savedPosition) => { if (savedPosition) { return savedPosition } else { const position = {} // new navigation. // scroll to anchor by returning the selector if (to.hash) { position.selector = to.hash }

if (to.matched.some(m => m.meta.scrollToTop)) { // cords will be used if no selector is provided, // or if the selector didn't match any element. position.x = 0 position.y = 0 } // if the returned position is falsy or an empty object, // will retain current scroll position. return position }}

const router = new VueRouter({ mode: 'history', base: __dirname, scrollBehavior, routes: [ { path: '/', component: Home, meta: { scrollToTop: true }}, { path: '/foo', component: Foo }, { path: '/bar', component: Bar, meta: { scrollToTop: true }} ]})

https://github.com/vuejs/vue-router/blob/next/examples/scroll-behavior/app.js

Page 19: How to Build SPA with Vue Router 2.0

• Transition• Dynamically transition setting on route change are available

Vue Router 2.0 - Transition

const Parent = { data () { return { transitionName: 'slide-left' } }, // dynamically set transition based on route change watch: { '$route' (to, from) { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left' } }, template: ` <div class="parent"> <h2>Parent</h2> <transition :name="transitionName"> <router-view class="child-view"></router-view> </transition> </div> `}

https://github.com/vuejs/vue-router/blob/next/examples/transitions/app.js

Page 20: How to Build SPA with Vue Router 2.0

• Vue Router x Vuex

• Inject (sync) router states to Vuex states

• Components can access router data (path, params, query) through vuex getter in components router

• https://github.com/vuejs/vuex-router-sync

Vue Router 2.0 - Vuex Router Sync

Page 21: How to Build SPA with Vue Router 2.0

SPA with Awesome Vue Family

• Building SPA with Vue.js 2.0 Family

• Better performance for SPA by reactive components mechanism

• SSR & SPA with isomorphic fetch and client hydration

• No worried about SEO & server side template like EJS

• Seamless integration with Vue-Router & Vuex modules compare to React & React-Router & Redux because the author is same Evan :) It allows us more consistent & intuitive coding manner

Page 22: How to Build SPA with Vue Router 2.0

Thanks!

Join Slack (Japanese)

https://vuejs-jp-slackin.herokuapp.com/