Commit 530a486c authored by Evan You's avatar Evan You

restructure

parent 6482f808
import Vue from 'vue' import Vue from 'vue'
import App from './App.vue' import App from './App.vue'
import store from './store' import { createStore } from './store'
import router from './router' import { createRouter } from './router'
import { sync } from 'vuex-router-sync' import { sync } from 'vuex-router-sync'
import * as filters from './filters' import * as filters from './filters'
// sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
// 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])
}) })
// create the app instance. // Expose a factory function that creates a fresh set of store, router,
// here we inject the router and store to all child components, // app instances on each call (which is called for each SSR request)
// making them available everywhere as `this.$router` and `this.$store`. export function createApp () {
const app = new Vue({ // create store and router instances
router, const store = createStore()
store, const router = createRouter()
render: h => h(App)
}) // sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
// create the app instance.
// here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
const app = new Vue({
router,
store,
render: h => h(App)
})
// expose the app, the router and the store. // expose the app, the router and the store.
// note we are not mounting the app here, since bootstrapping will be // note we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server. // different depending on whether we are in a browser or on the server.
export { app, router, store } return { app, router, store }
}
import 'es6-promise/auto' import 'es6-promise/auto'
import { app, store, router } from './app' import { createApp } from './app'
const { app, router, store } = createApp()
// prime the store with server-initialized state. // prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup. // the state is determined during SSR and inlined in the page markup.
......
import { app, router, store } from './app' import { createApp } from './app'
const isDev = process.env.NODE_ENV !== 'production' const isDev = process.env.NODE_ENV !== 'production'
...@@ -10,6 +10,8 @@ const isDev = process.env.NODE_ENV !== 'production' ...@@ -10,6 +10,8 @@ const isDev = process.env.NODE_ENV !== 'production'
export default context => { export default context => {
const s = isDev && Date.now() const s = isDev && Date.now()
const { app, router, store } = createApp()
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// set router's location // set router's location
router.push(context.url) router.push(context.url)
......
...@@ -3,34 +3,23 @@ import Router from 'vue-router' ...@@ -3,34 +3,23 @@ import Router from 'vue-router'
Vue.use(Router) Vue.use(Router)
// We are using Webpack code splitting here so that each route's associated import createListView from '../views/CreateListView'
// component code is loaded on-demand only when the route is visited. import ItemView from '../views/ItemView.vue'
// It's actually not really necessary for a small project of this size but import UserView from '../views/UserView.vue'
// the goal is to demonstrate how to do it.
//
// Note that the dynamic import syntax should actually be just `import()`
// but buble/acorn doesn't support parsing that syntax until it's stage 4
// so we use the old System.import here instead.
//
// If using Babel, `import()` can be supported via
// babel-plugin-syntax-dynamic-import.
const createListView = name => () => export function createRouter () {
System.import('../views/CreateListView').then(m => m.createListView(name)) return new Router({
const ItemView = () => System.import('../views/ItemView.vue') mode: 'history',
const UserView = () => System.import('../views/UserView.vue') scrollBehavior: () => ({ y: 0 }),
routes: [
export default new Router({ { path: '/top/:page(\\d+)?', component: createListView('top') },
mode: 'history', { path: '/new/:page(\\d+)?', component: createListView('new') },
scrollBehavior: () => ({ y: 0 }), { path: '/show/:page(\\d+)?', component: createListView('show') },
routes: [ { path: '/ask/:page(\\d+)?', component: createListView('ask') },
{ path: '/top/:page(\\d+)?', component: createListView('top') }, { path: '/job/:page(\\d+)?', component: createListView('job') },
{ path: '/new/:page(\\d+)?', component: createListView('new') }, { path: '/item/:id(\\d+)', component: ItemView },
{ path: '/show/:page(\\d+)?', component: createListView('show') }, { path: '/user/:id', component: UserView },
{ path: '/ask/:page(\\d+)?', component: createListView('ask') }, { path: '/', redirect: '/top' }
{ path: '/job/:page(\\d+)?', component: createListView('job') }, ]
{ path: '/item/:id(\\d+)', component: ItemView }, })
{ path: '/user/:id', component: UserView }, }
{ path: '/', redirect: '/top' }
]
})
import {
fetchUser,
fetchItems,
fetchIdsByType
} from './api'
export default {
// ensure data for rendering given list type
FETCH_LIST_DATA: ({ commit, dispatch, state }, { type }) => {
commit('SET_ACTIVE_TYPE', { type })
return fetchIdsByType(type)
.then(ids => commit('SET_LIST', { type, ids }))
.then(() => dispatch('ENSURE_ACTIVE_ITEMS'))
},
// ensure all active items are fetched
ENSURE_ACTIVE_ITEMS: ({ dispatch, getters }) => {
return dispatch('FETCH_ITEMS', {
ids: getters.activeIds
})
},
FETCH_ITEMS: ({ commit, state }, { ids }) => {
// on the client, the store itself serves as a cache.
// only fetch items that we do not already have, or has expired (3 minutes)
const now = Date.now()
ids = ids.filter(id => {
const item = state.items[id]
if (!item) {
return true
}
if (now - item.__lastUpdated > 1000 * 60 * 3) {
return true
}
return false
})
if (ids.length) {
return fetchItems(ids).then(items => commit('SET_ITEMS', { items }))
} else {
return Promise.resolve()
}
},
FETCH_USER: ({ commit, state }, { id }) => {
return state.users[id]
? Promise.resolve(state.users[id])
: fetchUser(id).then(user => commit('SET_USER', { user }))
}
}
export default {
// ids of the items that should be currently displayed based on
// current list type and current pagination
activeIds (state) {
const { activeType, itemsPerPage, lists } = state
const page = Number(state.route.params.page) || 1
if (activeType) {
const start = (page - 1) * itemsPerPage
const end = page * itemsPerPage
return lists[activeType].slice(start, end)
} else {
return []
}
},
// items that should be currently displayed.
// this Array may not be fully fetched.
activeItems (state, getters) {
return getters.activeIds.map(id => state.items[id]).filter(_ => _)
}
}
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import { fetchItems, fetchIdsByType, fetchUser } from './api' import actions from './actions'
import mutations from './mutations'
import getters from './getters'
Vue.use(Vuex) Vue.use(Vuex)
const store = new Vuex.Store({ export function createStore () {
state: { return new Vuex.Store({
activeType: null, state: {
itemsPerPage: 20, activeType: null,
items: {/* [id: number]: Item */}, itemsPerPage: 20,
users: {/* [id: string]: User */}, items: {/* [id: number]: Item */},
lists: { users: {/* [id: string]: User */},
top: [/* number */], lists: {
new: [], top: [/* number */],
show: [], new: [],
ask: [], show: [],
job: [] ask: [],
} job: []
},
actions: {
// ensure data for rendering given list type
FETCH_LIST_DATA: ({ commit, dispatch, state }, { type }) => {
commit('SET_ACTIVE_TYPE', { type })
return fetchIdsByType(type)
.then(ids => commit('SET_LIST', { type, ids }))
.then(() => dispatch('ENSURE_ACTIVE_ITEMS'))
},
// ensure all active items are fetched
ENSURE_ACTIVE_ITEMS: ({ dispatch, getters }) => {
return dispatch('FETCH_ITEMS', {
ids: getters.activeIds
})
},
FETCH_ITEMS: ({ commit, state }, { ids }) => {
// on the client, the store itself serves as a cache.
// only fetch items that we do not already have, or has expired (3 minutes)
const now = Date.now()
ids = ids.filter(id => {
const item = state.items[id]
if (!item) {
return true
}
if (now - item.__lastUpdated > 1000 * 60 * 3) {
return true
}
return false
})
if (ids.length) {
return fetchItems(ids).then(items => commit('SET_ITEMS', { items }))
} else {
return Promise.resolve()
}
},
FETCH_USER: ({ commit, state }, { id }) => {
return state.users[id]
? Promise.resolve(state.users[id])
: fetchUser(id).then(user => commit('SET_USER', { user }))
}
},
mutations: {
SET_ACTIVE_TYPE: (state, { type }) => {
state.activeType = type
},
SET_LIST: (state, { type, ids }) => {
state.lists[type] = ids
},
SET_ITEMS: (state, { items }) => {
items.forEach(item => {
if (item) {
Vue.set(state.items, item.id, item)
}
})
},
SET_USER: (state, { user }) => {
Vue.set(state.users, user.id, user)
}
},
getters: {
// ids of the items that should be currently displayed based on
// current list type and current pagination
activeIds (state) {
const { activeType, itemsPerPage, lists } = state
const page = Number(state.route.params.page) || 1
if (activeType) {
const start = (page - 1) * itemsPerPage
const end = page * itemsPerPage
return lists[activeType].slice(start, end)
} else {
return []
} }
}, },
actions,
// items that should be currently displayed. mutations,
// this Array may not be fully fetched. getters
activeItems (state, getters) { })
return getters.activeIds.map(id => state.items[id]).filter(_ => _) }
}
}
})
export default store
import Vue from 'vue'
export default {
SET_ACTIVE_TYPE: (state, { type }) => {
state.activeType = type
},
SET_LIST: (state, { type, ids }) => {
state.lists[type] = ids
},
SET_ITEMS: (state, { items }) => {
items.forEach(item => {
if (item) {
Vue.set(state.items, item.id, item)
}
})
},
SET_USER: (state, { user }) => {
Vue.set(state.users, user.id, user)
}
}
...@@ -3,7 +3,7 @@ import ItemList from '../components/ItemList.vue' ...@@ -3,7 +3,7 @@ import ItemList from '../components/ItemList.vue'
// This is a factory function for dynamically creating root-level list views, // This is a factory function for dynamically creating root-level list views,
// since they share most of the logic except for the type of items to display. // since they share most of the logic except for the type of items to display.
// They are essentially higher order components wrapping ItemList.vue. // They are essentially higher order components wrapping ItemList.vue.
export function createListView (type) { export default function createListView (type) {
return { return {
name: `${type}-stories-view`, name: `${type}-stories-view`,
// this will be called during SSR to pre-fetch data into the store! // this will be called during SSR to pre-fetch data into the store!
......
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