UNPKG

3.06 kBJavaScriptView Raw
1/* globals window */
2import Vue from 'vue'
3// eslint-disable-next-line import/no-unassigned-import
4import './polyfills'
5// eslint-disable-next-line import/no-unresolved
6import createApp from '#out/create-app'
7import { routerReady, pageNotFound, runMiddlewares } from './utils'
8import serverHelpers from './server-helpers'
9import ReamError from './ReamError'
10
11const {
12 app,
13 router,
14 getInitialDataContextFns,
15 event,
16 dataStore,
17 middlewares
18} = createApp()
19
20const getContext = context => {
21 for (const fn of getInitialDataContextFns) {
22 fn(context)
23 }
24 return context
25}
26
27const updateDataStore = (id, data) => {
28 dataStore.setData(id, data)
29}
30
31const handleError = err => {
32 if (err instanceof ReamError) {
33 if (err.code === 'REDIRECT') {
34 router.push(err.redirectURL)
35 } else {
36 app.setError(err)
37 }
38 } else {
39 console.error(err)
40 }
41}
42
43router.beforeResolve(async (to, from, next) => {
44 // Skip initial load on client-side
45 if (!app.$clientRendered) return next()
46
47 const matched = router.getMatchedComponents(to)
48 if (matched.length === 0) {
49 app.setError(pageNotFound(to.path))
50 return next()
51 }
52
53 const prevMatched = router.getMatchedComponents(from)
54 let diffed = false
55 const activated = matched.filter((c, i) => {
56 if (diffed) return diffed
57 diffed = prevMatched[i] !== c
58 return diffed
59 })
60
61 const components = activated.filter(c => c.getInitialData)
62
63 try {
64 const ctx = getContext({ route: to, router, ...serverHelpers })
65 await runMiddlewares(middlewares, ctx)
66 await Promise.all(
67 components.map(async c => {
68 const data = await c.getInitialData(ctx)
69 updateDataStore(c.initialDataKey, data)
70 })
71 )
72 next()
73 } catch (err) {
74 err.errorPath = to.path
75 handleError(err)
76 next()
77 }
78})
79
80// A global mixin that calls `getInitialData` when a route component's params change
81Vue.mixin({
82 async beforeRouteUpdate(to, from, next) {
83 try {
84 const context = getContext({
85 router,
86 route: to
87 })
88
89 await runMiddlewares(middlewares, context)
90
91 const { getInitialData } = this.$options
92 if (getInitialData) {
93 const data = await getInitialData(context)
94 updateDataStore(this.$initialDataKey, data)
95 Object.assign(this, data)
96 }
97 next()
98 } catch (err) {
99 err.url = to.path
100 handleError(err)
101 next()
102 }
103 }
104})
105
106// Wait until router has resolved all async before hooks
107// and async components...
108async function main() {
109 event.$emit('before-client-render')
110
111 if (window.__REAM__.initialData) {
112 dataStore.replaceState(window.__REAM__.initialData)
113 }
114
115 await routerReady(router)
116
117 if (router.getMatchedComponents().length === 0) {
118 throw new ReamError(pageNotFound(router.currentRoute.path))
119 }
120}
121
122const mountApp = app => {
123 app.$mount('#_ream')
124 app.$clientRendered = true
125}
126
127main()
128 // eslint-disable-next-line promise/prefer-await-to-then
129 .then(() => {
130 mountApp(app)
131 })
132 .catch(err => {
133 handleError(err)
134 mountApp(app)
135 })