1 |
|
2 |
|
3 | import { escape } from 'web/server/util'
|
4 | import { SSR_ATTR } from 'shared/constants'
|
5 | import { RenderContext } from './render-context'
|
6 | import { resolveAsset } from 'core/util/options'
|
7 | import { generateComponentTrace } from 'core/util/debug'
|
8 | import { ssrCompileToFunctions } from 'web/server/compiler'
|
9 | import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
|
10 |
|
11 | import { isDef, isUndef, isTrue } from 'shared/util'
|
12 |
|
13 | import {
|
14 | createComponent,
|
15 | createComponentInstanceForVnode
|
16 | } from 'core/vdom/create-component'
|
17 |
|
18 | let warned = Object.create(null)
|
19 | const warnOnce = msg => {
|
20 | if (!warned[msg]) {
|
21 | warned[msg] = true
|
22 |
|
23 | console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
|
24 | }
|
25 | }
|
26 |
|
27 | const onCompilationError = (err, vm) => {
|
28 | const trace = vm ? generateComponentTrace(vm) : ''
|
29 | throw new Error(`\n\u001b[31m${err}${trace}\u001b[39m\n`)
|
30 | }
|
31 |
|
32 | const normalizeRender = vm => {
|
33 | const { render, template, _scopeId } = vm.$options
|
34 | if (isUndef(render)) {
|
35 | if (template) {
|
36 | const compiled = ssrCompileToFunctions(template, {
|
37 | scopeId: _scopeId,
|
38 | warn: onCompilationError
|
39 | }, vm)
|
40 |
|
41 | vm.$options.render = compiled.render
|
42 | vm.$options.staticRenderFns = compiled.staticRenderFns
|
43 | } else {
|
44 | throw new Error(
|
45 | `render function or template not defined in component: ${
|
46 | vm.$options.name || vm.$options._componentTag || 'anonymous'
|
47 | }`
|
48 | )
|
49 | }
|
50 | }
|
51 | }
|
52 |
|
53 | function waitForServerPrefetch (vm, resolve, reject) {
|
54 | let handlers = vm.$options.serverPrefetch
|
55 | if (isDef(handlers)) {
|
56 | if (!Array.isArray(handlers)) handlers = [handlers]
|
57 | try {
|
58 | const promises = []
|
59 | for (let i = 0, j = handlers.length; i < j; i++) {
|
60 | const result = handlers[i].call(vm, vm)
|
61 | if (result && typeof result.then === 'function') {
|
62 | promises.push(result)
|
63 | }
|
64 | }
|
65 | Promise.all(promises).then(resolve).catch(reject)
|
66 | return
|
67 | } catch (e) {
|
68 | reject(e)
|
69 | }
|
70 | }
|
71 | resolve()
|
72 | }
|
73 |
|
74 | function renderNode (node, isRoot, context) {
|
75 | if (node.isString) {
|
76 | renderStringNode(node, context)
|
77 | } else if (isDef(node.componentOptions)) {
|
78 | renderComponent(node, isRoot, context)
|
79 | } else if (isDef(node.tag)) {
|
80 | renderElement(node, isRoot, context)
|
81 | } else if (isTrue(node.isComment)) {
|
82 | if (isDef(node.asyncFactory)) {
|
83 |
|
84 | renderAsyncComponent(node, isRoot, context)
|
85 | } else {
|
86 | context.write(`<!--${node.text}-->`, context.next)
|
87 | }
|
88 | } else {
|
89 | context.write(
|
90 | node.raw ? node.text : escape(String(node.text)),
|
91 | context.next
|
92 | )
|
93 | }
|
94 | }
|
95 |
|
96 | function registerComponentForCache (options, write) {
|
97 |
|
98 |
|
99 | const register = options._ssrRegister
|
100 | if (write.caching && isDef(register)) {
|
101 | write.componentBuffer[write.componentBuffer.length - 1].add(register)
|
102 | }
|
103 | return register
|
104 | }
|
105 |
|
106 | function renderComponent (node, isRoot, context) {
|
107 | const { write, next, userContext } = context
|
108 |
|
109 |
|
110 | const Ctor = node.componentOptions.Ctor
|
111 | const getKey = Ctor.options.serverCacheKey
|
112 | const name = Ctor.options.name
|
113 | const cache = context.cache
|
114 | const registerComponent = registerComponentForCache(Ctor.options, write)
|
115 |
|
116 | if (isDef(getKey) && isDef(cache) && isDef(name)) {
|
117 | const rawKey = getKey(node.componentOptions.propsData)
|
118 | if (rawKey === false) {
|
119 | renderComponentInner(node, isRoot, context)
|
120 | return
|
121 | }
|
122 | const key = name + '::' + rawKey
|
123 | const { has, get } = context
|
124 | if (isDef(has)) {
|
125 | has(key, hit => {
|
126 | if (hit === true && isDef(get)) {
|
127 | get(key, res => {
|
128 | if (isDef(registerComponent)) {
|
129 | registerComponent(userContext)
|
130 | }
|
131 | res.components.forEach(register => register(userContext))
|
132 | write(res.html, next)
|
133 | })
|
134 | } else {
|
135 | renderComponentWithCache(node, isRoot, key, context)
|
136 | }
|
137 | })
|
138 | } else if (isDef(get)) {
|
139 | get(key, res => {
|
140 | if (isDef(res)) {
|
141 | if (isDef(registerComponent)) {
|
142 | registerComponent(userContext)
|
143 | }
|
144 | res.components.forEach(register => register(userContext))
|
145 | write(res.html, next)
|
146 | } else {
|
147 | renderComponentWithCache(node, isRoot, key, context)
|
148 | }
|
149 | })
|
150 | }
|
151 | } else {
|
152 | if (isDef(getKey) && isUndef(cache)) {
|
153 | warnOnce(
|
154 | `[vue-server-renderer] Component ${
|
155 | Ctor.options.name || '(anonymous)'
|
156 | } implemented serverCacheKey, ` +
|
157 | 'but no cache was provided to the renderer.'
|
158 | )
|
159 | }
|
160 | if (isDef(getKey) && isUndef(name)) {
|
161 | warnOnce(
|
162 | `[vue-server-renderer] Components that implement "serverCacheKey" ` +
|
163 | `must also define a unique "name" option.`
|
164 | )
|
165 | }
|
166 | renderComponentInner(node, isRoot, context)
|
167 | }
|
168 | }
|
169 |
|
170 | function renderComponentWithCache (node, isRoot, key, context) {
|
171 | const write = context.write
|
172 | write.caching = true
|
173 | const buffer = write.cacheBuffer
|
174 | const bufferIndex = buffer.push('') - 1
|
175 | const componentBuffer = write.componentBuffer
|
176 | componentBuffer.push(new Set())
|
177 | context.renderStates.push({
|
178 | type: 'ComponentWithCache',
|
179 | key,
|
180 | buffer,
|
181 | bufferIndex,
|
182 | componentBuffer
|
183 | })
|
184 | renderComponentInner(node, isRoot, context)
|
185 | }
|
186 |
|
187 | function renderComponentInner (node, isRoot, context) {
|
188 | const prevActive = context.activeInstance
|
189 |
|
190 | node.ssrContext = context.userContext
|
191 | const child = context.activeInstance = createComponentInstanceForVnode(
|
192 | node,
|
193 | context.activeInstance
|
194 | )
|
195 | normalizeRender(child)
|
196 |
|
197 | const resolve = () => {
|
198 | const childNode = child._render()
|
199 | childNode.parent = node
|
200 | context.renderStates.push({
|
201 | type: 'Component',
|
202 | prevActive
|
203 | })
|
204 | renderNode(childNode, isRoot, context)
|
205 | }
|
206 |
|
207 | const reject = context.done
|
208 |
|
209 | waitForServerPrefetch(child, resolve, reject)
|
210 | }
|
211 |
|
212 | function renderAsyncComponent (node, isRoot, context) {
|
213 | const factory = node.asyncFactory
|
214 |
|
215 | const resolve = comp => {
|
216 | if (comp.__esModule && comp.default) {
|
217 | comp = comp.default
|
218 | }
|
219 | const { data, children, tag } = node.asyncMeta
|
220 | const nodeContext = node.asyncMeta.context
|
221 | const resolvedNode: any = createComponent(
|
222 | comp,
|
223 | data,
|
224 | nodeContext,
|
225 | children,
|
226 | tag
|
227 | )
|
228 | if (resolvedNode) {
|
229 | if (resolvedNode.componentOptions) {
|
230 |
|
231 | renderComponent(resolvedNode, isRoot, context)
|
232 | } else if (!Array.isArray(resolvedNode)) {
|
233 |
|
234 | renderNode(resolvedNode, isRoot, context)
|
235 | } else {
|
236 |
|
237 | context.renderStates.push({
|
238 | type: 'Fragment',
|
239 | children: resolvedNode,
|
240 | rendered: 0,
|
241 | total: resolvedNode.length
|
242 | })
|
243 | context.next()
|
244 | }
|
245 | } else {
|
246 |
|
247 |
|
248 | context.write(`<!---->`, context.next)
|
249 | }
|
250 | }
|
251 |
|
252 | if (factory.resolved) {
|
253 | resolve(factory.resolved)
|
254 | return
|
255 | }
|
256 |
|
257 | const reject = context.done
|
258 | let res
|
259 | try {
|
260 | res = factory(resolve, reject)
|
261 | } catch (e) {
|
262 | reject(e)
|
263 | }
|
264 | if (res) {
|
265 | if (typeof res.then === 'function') {
|
266 | res.then(resolve, reject).catch(reject)
|
267 | } else {
|
268 |
|
269 | const comp = res.component
|
270 | if (comp && typeof comp.then === 'function') {
|
271 | comp.then(resolve, reject).catch(reject)
|
272 | }
|
273 | }
|
274 | }
|
275 | }
|
276 |
|
277 | function renderStringNode (el, context) {
|
278 | const { write, next } = context
|
279 | if (isUndef(el.children) || el.children.length === 0) {
|
280 | write(el.open + (el.close || ''), next)
|
281 | } else {
|
282 | const children: Array<VNode> = el.children
|
283 | context.renderStates.push({
|
284 | type: 'Element',
|
285 | children,
|
286 | rendered: 0,
|
287 | total: children.length,
|
288 | endTag: el.close
|
289 | })
|
290 | write(el.open, next)
|
291 | }
|
292 | }
|
293 |
|
294 | function renderElement (el, isRoot, context) {
|
295 | const { write, next } = context
|
296 |
|
297 | if (isTrue(isRoot)) {
|
298 | if (!el.data) el.data = {}
|
299 | if (!el.data.attrs) el.data.attrs = {}
|
300 | el.data.attrs[SSR_ATTR] = 'true'
|
301 | }
|
302 |
|
303 | if (el.fnOptions) {
|
304 | registerComponentForCache(el.fnOptions, write)
|
305 | }
|
306 |
|
307 | const startTag = renderStartingTag(el, context)
|
308 | const endTag = `</${el.tag}>`
|
309 | if (context.isUnaryTag(el.tag)) {
|
310 | write(startTag, next)
|
311 | } else if (isUndef(el.children) || el.children.length === 0) {
|
312 | write(startTag + endTag, next)
|
313 | } else {
|
314 | const children: Array<VNode> = el.children
|
315 | context.renderStates.push({
|
316 | type: 'Element',
|
317 | children,
|
318 | rendered: 0,
|
319 | total: children.length,
|
320 | endTag
|
321 | })
|
322 | write(startTag, next)
|
323 | }
|
324 | }
|
325 |
|
326 | function hasAncestorData (node: VNode) {
|
327 | const parentNode = node.parent
|
328 | return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
|
329 | }
|
330 |
|
331 | function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
|
332 | let dir: VNodeDirective
|
333 | let tmp
|
334 |
|
335 | while (isDef(node)) {
|
336 | if (node.data && node.data.directives) {
|
337 | tmp = node.data.directives.find(dir => dir.name === 'show')
|
338 | if (tmp) {
|
339 | dir = tmp
|
340 | }
|
341 | }
|
342 | node = node.parent
|
343 | }
|
344 | return dir
|
345 | }
|
346 |
|
347 | function renderStartingTag (node: VNode, context) {
|
348 | let markup = `<${node.tag}`
|
349 | const { directives, modules } = context
|
350 |
|
351 |
|
352 |
|
353 | if (isUndef(node.data) && hasAncestorData(node)) {
|
354 | node.data = {}
|
355 | }
|
356 | if (isDef(node.data)) {
|
357 |
|
358 | const dirs = node.data.directives
|
359 | if (dirs) {
|
360 | for (let i = 0; i < dirs.length; i++) {
|
361 | const name = dirs[i].name
|
362 | if (name !== 'show') {
|
363 | const dirRenderer = resolveAsset(context, 'directives', name)
|
364 | if (dirRenderer) {
|
365 |
|
366 |
|
367 | dirRenderer(node, dirs[i])
|
368 | }
|
369 | }
|
370 | }
|
371 | }
|
372 |
|
373 |
|
374 | const vshowDirectiveInfo = getVShowDirectiveInfo(node)
|
375 | if (vshowDirectiveInfo) {
|
376 | directives.show(node, vshowDirectiveInfo)
|
377 | }
|
378 |
|
379 |
|
380 | for (let i = 0; i < modules.length; i++) {
|
381 | const res = modules[i](node)
|
382 | if (res) {
|
383 | markup += res
|
384 | }
|
385 | }
|
386 | }
|
387 |
|
388 | let scopeId
|
389 | const activeInstance = context.activeInstance
|
390 | if (isDef(activeInstance) &&
|
391 | activeInstance !== node.context &&
|
392 | isDef(scopeId = activeInstance.$options._scopeId)
|
393 | ) {
|
394 | markup += ` ${(scopeId: any)}`
|
395 | }
|
396 | if (isDef(node.fnScopeId)) {
|
397 | markup += ` ${node.fnScopeId}`
|
398 | } else {
|
399 | while (isDef(node)) {
|
400 | if (isDef(scopeId = node.context.$options._scopeId)) {
|
401 | markup += ` ${scopeId}`
|
402 | }
|
403 | node = node.parent
|
404 | }
|
405 | }
|
406 | return markup + '>'
|
407 | }
|
408 |
|
409 | export function createRenderFunction (
|
410 | modules: Array<(node: VNode) => ?string>,
|
411 | directives: Object,
|
412 | isUnaryTag: Function,
|
413 | cache: any
|
414 | ) {
|
415 | return function render (
|
416 | component: Component,
|
417 | write: (text: string, next: Function) => void,
|
418 | userContext: ?Object,
|
419 | done: Function
|
420 | ) {
|
421 | warned = Object.create(null)
|
422 | const context = new RenderContext({
|
423 | activeInstance: component,
|
424 | userContext,
|
425 | write, done, renderNode,
|
426 | isUnaryTag, modules, directives,
|
427 | cache
|
428 | })
|
429 | installSSRHelpers(component)
|
430 | normalizeRender(component)
|
431 |
|
432 | const resolve = () => {
|
433 | renderNode(component._render(), true, context)
|
434 | }
|
435 | waitForServerPrefetch(component, resolve, done)
|
436 | }
|
437 | }
|