1 | import { stringify } from 'querystring'
|
2 | import Vue from 'vue'
|
3 | import omit from 'lodash/omit'
|
4 | import middleware from './middleware'
|
5 | import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
|
6 | import { createApp, NuxtError } from './index'
|
7 |
|
8 | const debug = require('debug')('nuxt:render')
|
9 | debug.color = 4
|
10 |
|
11 | const isDev = <%= isDev %>
|
12 |
|
13 | const noopApp = () => new Vue({ render: h => h('div') })
|
14 |
|
15 | const 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.
|
44 | export 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 |