Commit 6c9dd49a authored by Evan You's avatar Evan You

more elegant title handling

parent 3e603e88
...@@ -106,7 +106,11 @@ function render (req, res) { ...@@ -106,7 +106,11 @@ function render (req, res) {
} }
} }
renderer.renderToString({ url: req.url }, (err, html) => { const context = {
title: 'Vue HN 2.0', // default title
url: req.url
}
renderer.renderToString(context, (err, html) => {
if (err) { if (err) {
return handleError(err) return handleError(err)
} }
......
...@@ -3,8 +3,12 @@ import App from './App.vue' ...@@ -3,8 +3,12 @@ import App from './App.vue'
import { createStore } from './store' import { createStore } from './store'
import { createRouter } from './router' import { createRouter } from './router'
import { sync } from 'vuex-router-sync' import { sync } from 'vuex-router-sync'
import titleMixin from './util/title'
import * as filters from './util/filters' import * as filters from './util/filters'
// mixin for handling title
Vue.mixin(titleMixin)
// register global utility filters. // register global utility filters.
Object.keys(filters).forEach(key => { Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key]) Vue.filter(key, filters[key])
...@@ -12,7 +16,7 @@ Object.keys(filters).forEach(key => { ...@@ -12,7 +16,7 @@ Object.keys(filters).forEach(key => {
// Expose a factory function that creates a fresh set of store, router, // Expose a factory function that creates a fresh set of store, router,
// app instances on each call (which is called for each SSR request) // app instances on each call (which is called for each SSR request)
export function createApp () { export function createApp (ssrContext) {
// create store and router instances // create store and router instances
const store = createStore() const store = createStore()
const router = createRouter() const router = createRouter()
...@@ -22,11 +26,12 @@ export function createApp () { ...@@ -22,11 +26,12 @@ export function createApp () {
sync(store, router) sync(store, router)
// create the app instance. // create the app instance.
// here we inject the router and store to all child components, // here we inject the router, store and ssr context to all child components,
// making them available everywhere as `this.$router` and `this.$store`. // making them available everywhere as `this.$router` and `this.$store`.
const app = new Vue({ const app = new Vue({
router, router,
store, store,
ssrContext,
render: h => h(App) render: h => h(App)
}) })
......
...@@ -10,7 +10,7 @@ const isDev = process.env.NODE_ENV !== 'production' ...@@ -10,7 +10,7 @@ const isDev = process.env.NODE_ENV !== 'production'
export default context => { export default context => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const s = isDev && Date.now() const s = isDev && Date.now()
const { app, router, store } = createApp() const { app, router, store } = createApp(context)
// set router's location // set router's location
router.push(context.url) router.push(context.url)
...@@ -29,8 +29,7 @@ export default context => { ...@@ -29,8 +29,7 @@ export default context => {
Promise.all(matchedComponents.map(component => { Promise.all(matchedComponents.map(component => {
return component.asyncData && component.asyncData({ return component.asyncData && component.asyncData({
store, store,
route: router.currentRoute, route: router.currentRoute
ssrContext: context
}) })
})).then(() => { })).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`) isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
......
export const setTitle = (title, context) => { function getTitle (vm) {
title = `Vue HN 2.0 | ${title}` const { title } = vm.$options
if (context) { if (title) {
// server return typeof title === 'function'
context.title = title ? title.call(vm)
} else { : title
// client
document.title = title
} }
} }
const serverTitleMixin = {
created () {
const title = getTitle(this)
if (title) {
this.$root.$options.ssrContext.title = `Vue HN 2.0 | ${title}`
}
}
}
const clientTitleMixin = {
mounted () {
const title = getTitle(this)
if (title) {
document.title = `Vue HN 2.0 | ${title}`
}
}
}
export default process.env.VUE_ENV === 'server'
? serverTitleMixin
: clientTitleMixin
...@@ -10,12 +10,12 @@ export default function createListView (type) { ...@@ -10,12 +10,12 @@ export default function createListView (type) {
return { return {
name: `${type}-stories-view`, name: `${type}-stories-view`,
asyncData ({ store, ssrContext }) { asyncData ({ store }) {
return store.dispatch('FETCH_LIST_DATA', { type }).then(() => { return store.dispatch('FETCH_LIST_DATA', { type })
setTitle(camelize(type), ssrContext)
})
}, },
title: camelize(type),
render (h) { render (h) {
return h(ItemList, { props: { type }}) return h(ItemList, { props: { type }})
} }
......
...@@ -49,11 +49,12 @@ export default { ...@@ -49,11 +49,12 @@ export default {
// We only fetch the item itself before entering the view, because // We only fetch the item itself before entering the view, because
// it might take a long time to load threads with hundreds of comments // it might take a long time to load threads with hundreds of comments
// due to how the HN Firebase API works. // due to how the HN Firebase API works.
asyncData ({ store, route: { params: { id }}, ssrContext }) { asyncData ({ store, route: { params: { id }}}) {
return store.dispatch('FETCH_ITEMS', { ids: [id] }).then(() => { return store.dispatch('FETCH_ITEMS', { ids: [id] })
const item = store.state.items[id] },
setTitle(item.title, ssrContext)
}) title () {
return this.item.title
}, },
// Fetch comments when mounted on the client // Fetch comments when mounted on the client
......
...@@ -30,11 +30,14 @@ export default { ...@@ -30,11 +30,14 @@ export default {
} }
}, },
asyncData ({ store, route: { params: { id }}, ssrContext }) { asyncData ({ store, route: { params: { id }}}) {
return store.dispatch('FETCH_USER', { id }).then(() => { return store.dispatch('FETCH_USER', { id })
const user = store.state.users[id] },
setTitle(user ? user.id : 'User not found', ssrContext)
}) title () {
return this.user
? this.user.id
: 'User not found'
} }
} }
</script> </script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment