UNPKG

7.75 kBJavaScriptView Raw
1import { stringify } from 'querystring'
2import Vue from 'vue'
3import omit from 'lodash/omit'
4import middleware from './middleware'
5import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
6import { createApp, NuxtError } from './index'
7
8const debug = require('debug')('nuxt:render')
9debug.color = 4 // force blue color
10
11const isDev = <%= isDev %>
12
13const noopApp = () => new Vue({ render: h => h('div') })
14
15const createNext = ssrContext => (opts) => {
16 ssrContext.redirected = opts
17 // If nuxt generate
18 if (!ssrContext.res) {
19 ssrContext.nuxt.serverRendered = false
20 return
21 }
22 opts.query = stringify(opts.query)
23 opts.path = opts.path + (opts.query ? '?' + opts.query : '')
24 const routerBase = '<%= router.base %>'
25 if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) {
26 opts.path = urlJoin(routerBase, opts.path)
27 }
28 // Avoid loop redirect
29 if (opts.path === ssrContext.url) {
30 ssrContext.redirected = false
31 return
32 }
33 ssrContext.res.writeHead(opts.status, {
34 'Location': opts.path
35 })
36 ssrContext.res.end()
37}
38
39// This exported function will be called by `bundleRenderer`.
40// This is where we perform data-prefetching to determine the
41// state of our application before actually rendering it.
42// Since data fetching is async, this function is expected to
43// return a Promise that resolves to the app instance.
44export default async (ssrContext) => {
45 // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect
46 ssrContext.redirected = false
47 ssrContext.next = createNext(ssrContext)
48 // Used for beforeNuxtRender({ Components, nuxtState })
49 ssrContext.beforeRenderFns = []
50 // Nuxt object (window{{globals.context}}, defaults to window.__NUXT__)
51 ssrContext.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
52 // Create the app definition and the instance (created for each request)
53 const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
54 const _app = new Vue(app)
55
56 // Add meta infos (used in renderer.js)
57 ssrContext.meta = _app.$meta()
58 // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext)
59 ssrContext.asyncData = {}
60
61 const beforeRender = async () => {
62 // Call beforeNuxtRender() methods
63 await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
64 <% if (store) { %>
65 // Add the state from the vuex store
66 ssrContext.nuxt.state = store.state
67 <% } %>
68 }
69 const renderErrorPage = async () => {
70 // Load layout for error page
71 const errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout)
72 ssrContext.nuxt.layout = errLayout || 'default'
73 await _app.loadLayout(errLayout)
74 _app.setLayout(errLayout)
75 await beforeRender()
76 return _app
77 }
78 const render404Page = () => {
79 app.context.error({ statusCode: 404, path: ssrContext.url, message: `<%= messages.error_404 %>` })
80 return renderErrorPage()
81 }
82
83 <% if (isDev) { %>const s = isDev && Date.now()<% } %>
84
85 // Components are already resolved by setContext -> getRouteData (app/utils.js)
86 const Components = getMatchedComponents(router.match(ssrContext.url))
87
88 <% if (store) { %>
89 /*
90 ** Dispatch store nuxtServerInit
91 */
92 if (store._actions && store._actions.nuxtServerInit) {
93 try {
94 await store.dispatch('nuxtServerInit', app.context)
95 } catch (err) {
96 debug('error occurred when calling nuxtServerInit: ', err.message)
97 throw err
98 }
99 }
100 // ...If there is a redirect or an error, stop the process
101 if (ssrContext.redirected) return noopApp()
102 if (ssrContext.nuxt.error) return renderErrorPage()
103 <% } %>
104
105 /*
106 ** Call global middleware (nuxt.config.js)
107 */
108 let midd = <%= serialize(router.middleware).replace('middleware(', 'function(') %><%= isTest ? '// eslint-disable-line' : '' %>
109 midd = midd.map((name) => {
110 if (typeof name === 'function') return name
111 if (typeof middleware[name] !== 'function') {
112 app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
113 }
114 return middleware[name]
115 })
116 await middlewareSeries(midd, app.context)
117 // ...If there is a redirect or an error, stop the process
118 if (ssrContext.redirected) return noopApp()
119 if (ssrContext.nuxt.error) return renderErrorPage()
120
121 /*
122 ** Set layout
123 */
124 let layout = Components.length ? Components[0].options.layout : NuxtError.layout
125 if (typeof layout === 'function') layout = layout(app.context)
126 await _app.loadLayout(layout)
127 if (ssrContext.nuxt.error) return renderErrorPage()
128 layout = _app.setLayout(layout)
129 ssrContext.nuxt.layout = _app.layoutName
130
131 /*
132 ** Call middleware (layout + pages)
133 */
134 midd = []
135 if (layout.middleware) midd = midd.concat(layout.middleware)
136 Components.forEach((Component) => {
137 if (Component.options.middleware) {
138 midd = midd.concat(Component.options.middleware)
139 }
140 })
141 midd = midd.map((name) => {
142 if (typeof name === 'function') return name
143 if (typeof middleware[name] !== 'function') {
144 app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
145 }
146 return middleware[name]
147 })
148 await middlewareSeries(midd, app.context)
149 // ...If there is a redirect or an error, stop the process
150 if (ssrContext.redirected) return noopApp()
151 if (ssrContext.nuxt.error) return renderErrorPage()
152
153 /*
154 ** Call .validate()
155 */
156 let isValid = true
157 try {
158 for (const Component of Components) {
159 if (typeof Component.options.validate !== 'function') {
160 continue
161 }
162
163 isValid = await Component.options.validate(app.context)
164
165 if (!isValid) {
166 break
167 }
168 }
169 } catch (validationError) {
170 // ...If .validate() threw an error
171 app.context.error({
172 statusCode: validationError.statusCode || '500',
173 message: validationError.message
174 })
175 return renderErrorPage()
176 }
177
178 // ...If .validate() returned false
179 if (!isValid) {
180 // Don't server-render the page in generate mode
181 if (ssrContext._generate) ssrContext.nuxt.serverRendered = false
182 // Render a 404 error page
183 return render404Page()
184 }
185
186 // If no Components found, returns 404
187 if (!Components.length) return render404Page()
188
189 // Call asyncData & fetch hooks on components matched by the route.
190 const asyncDatas = await Promise.all(Components.map((Component) => {
191 const promises = []
192
193 // Call asyncData(context)
194 if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
195 const promise = promisify(Component.options.asyncData, app.context)
196 promise.then((asyncDataResult) => {
197 ssrContext.asyncData[Component.cid] = asyncDataResult
198 applyAsyncData(Component)
199 return asyncDataResult
200 })
201 promises.push(promise)
202 } else {
203 promises.push(null)
204 }
205
206 // Call fetch(context)
207 if (Component.options.fetch) {
208 promises.push(Component.options.fetch(app.context))
209 } else {
210 promises.push(null)
211 }
212
213 return Promise.all(promises)
214 }))
215
216 <% if (isDev) { %>if (asyncDatas.length) debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms')<% } %>
217
218 // datas are the first row of each
219 ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {})
220
221 // ...If there is a redirect or an error, stop the process
222 if (ssrContext.redirected) return noopApp()
223 if (ssrContext.nuxt.error) return renderErrorPage()
224
225 // Call beforeNuxtRender methods & add store state
226 await beforeRender()
227
228 return _app
229}
230
\No newline at end of file