UNPKG

8.07 kBJavaScriptView Raw
1/* @flow */
2
3const { escape } = require('he')
4
5import { SSR_ATTR } from 'shared/constants'
6import { RenderContext } from './render-context'
7import { compileToFunctions } from 'web/compiler/index'
8import { createComponentInstanceForVnode } from 'core/vdom/create-component'
9
10import { isDef, isUndef, isTrue } from 'shared/util'
11
12let warned = Object.create(null)
13const warnOnce = msg => {
14 if (!warned[msg]) {
15 warned[msg] = true
16 console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
17 }
18}
19
20const compilationCache = Object.create(null)
21const normalizeRender = vm => {
22 const { render, template } = vm.$options
23 if (isUndef(render)) {
24 if (template) {
25 const renderFns = (
26 compilationCache[template] ||
27 (compilationCache[template] = compileToFunctions(template))
28 )
29 Object.assign(vm.$options, renderFns)
30 } else {
31 throw new Error(
32 `render function or template not defined in component: ${
33 vm.$options.name || vm.$options._componentTag || 'anonymous'
34 }`
35 )
36 }
37 }
38}
39
40function renderNode (node, isRoot, context) {
41 if (isDef(node.componentOptions)) {
42 renderComponent(node, isRoot, context)
43 } else {
44 if (isDef(node.tag)) {
45 renderElement(node, isRoot, context)
46 } else if (isTrue(node.isComment)) {
47 context.write(
48 `<!--${node.text}-->`,
49 context.next
50 )
51 } else {
52 context.write(
53 node.raw ? node.text : escape(String(node.text)),
54 context.next
55 )
56 }
57 }
58}
59
60function registerComponentForCache (options, write) {
61 // exposed by vue-loader, need to call this if cache hit because
62 // component lifecycle hooks will not be called.
63 const register = options._ssrRegister
64 if (write.caching && isDef(register)) {
65 write.componentBuffer[write.componentBuffer.length - 1].add(register)
66 }
67 return register
68}
69
70function renderComponent (node, isRoot, context) {
71 const { write, next, userContext } = context
72
73 // check cache hit
74 const Ctor = node.componentOptions.Ctor
75 const getKey = Ctor.options.serverCacheKey
76 const name = Ctor.options.name
77 const cache = context.cache
78 const registerComponent = registerComponentForCache(Ctor.options, write)
79
80 if (isDef(getKey) && isDef(cache) && isDef(name)) {
81 const key = name + '::' + getKey(node.componentOptions.propsData)
82 const { has, get } = context
83 if (isDef(has)) {
84 has(key, hit => {
85 if (hit === true && isDef(get)) {
86 get(key, res => {
87 if (isDef(registerComponent)) {
88 registerComponent(userContext)
89 }
90 res.components.forEach(register => register(userContext))
91 write(res.html, next)
92 })
93 } else {
94 renderComponentWithCache(node, isRoot, key, context)
95 }
96 })
97 } else if (isDef(get)) {
98 get(key, res => {
99 if (isDef(res)) {
100 if (isDef(registerComponent)) {
101 registerComponent(userContext)
102 }
103 res.components.forEach(register => register(userContext))
104 write(res.html, next)
105 } else {
106 renderComponentWithCache(node, isRoot, key, context)
107 }
108 })
109 }
110 } else {
111 if (isDef(getKey) && isUndef(cache)) {
112 warnOnce(
113 `[vue-server-renderer] Component ${
114 Ctor.options.name || '(anonymous)'
115 } implemented serverCacheKey, ` +
116 'but no cache was provided to the renderer.'
117 )
118 }
119 if (isDef(getKey) && isUndef(name)) {
120 warnOnce(
121 `[vue-server-renderer] Components that implement "serverCacheKey" ` +
122 `must also define a unique "name" option.`
123 )
124 }
125 renderComponentInner(node, isRoot, context)
126 }
127}
128
129function renderComponentWithCache (node, isRoot, key, context) {
130 const write = context.write
131 write.caching = true
132 const buffer = write.cacheBuffer
133 const bufferIndex = buffer.push('') - 1
134 const componentBuffer = write.componentBuffer
135 componentBuffer.push(new Set())
136 context.renderStates.push({
137 type: 'ComponentWithCache',
138 key,
139 buffer,
140 bufferIndex,
141 componentBuffer
142 })
143 renderComponentInner(node, isRoot, context)
144}
145
146function renderComponentInner (node, isRoot, context) {
147 const prevActive = context.activeInstance
148 // expose userContext on vnode
149 node.ssrContext = context.userContext
150 const child = context.activeInstance = createComponentInstanceForVnode(
151 node,
152 context.activeInstance
153 )
154 normalizeRender(child)
155 const childNode = child._render()
156 childNode.parent = node
157 context.renderStates.push({
158 type: 'Component',
159 prevActive
160 })
161 renderNode(childNode, isRoot, context)
162}
163
164function renderElement (el, isRoot, context) {
165 const { write, next } = context
166
167 if (isTrue(isRoot)) {
168 if (!el.data) el.data = {}
169 if (!el.data.attrs) el.data.attrs = {}
170 el.data.attrs[SSR_ATTR] = 'true'
171 }
172
173 if (el.functionalOptions) {
174 registerComponentForCache(el.functionalOptions, write)
175 }
176
177 const startTag = renderStartingTag(el, context)
178 const endTag = `</${el.tag}>`
179 if (context.isUnaryTag(el.tag)) {
180 write(startTag, next)
181 } else if (isUndef(el.children) || el.children.length === 0) {
182 write(startTag + endTag, next)
183 } else {
184 const children: Array<VNode> = el.children
185 context.renderStates.push({
186 type: 'Element',
187 rendered: 0,
188 total: children.length,
189 endTag, children
190 })
191 write(startTag, next)
192 }
193}
194
195function hasAncestorData (node: VNode) {
196 const parentNode = node.parent
197 return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
198}
199
200function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
201 let dir: VNodeDirective
202 let tmp
203
204 while (isDef(node)) {
205 if (node.data && node.data.directives) {
206 tmp = node.data.directives.find(dir => dir.name === 'show')
207 if (tmp) {
208 dir = tmp
209 }
210 }
211 node = node.parent
212 }
213 return dir
214}
215
216function renderStartingTag (node: VNode, context) {
217 let markup = `<${node.tag}`
218 const { directives, modules } = context
219
220 // construct synthetic data for module processing
221 // because modules like style also produce code by parent VNode data
222 if (isUndef(node.data) && hasAncestorData(node)) {
223 node.data = {}
224 }
225 if (isDef(node.data)) {
226 // check directives
227 const dirs = node.data.directives
228 if (dirs) {
229 for (let i = 0; i < dirs.length; i++) {
230 const name = dirs[i].name
231 const dirRenderer = directives[name]
232 if (dirRenderer && name !== 'show') {
233 // directives mutate the node's data
234 // which then gets rendered by modules
235 dirRenderer(node, dirs[i])
236 }
237 }
238 }
239
240 // v-show directive needs to be merged from parent to child
241 const vshowDirectiveInfo = getVShowDirectiveInfo(node)
242 if (vshowDirectiveInfo) {
243 directives.show(node, vshowDirectiveInfo)
244 }
245
246 // apply other modules
247 for (let i = 0; i < modules.length; i++) {
248 const res = modules[i](node)
249 if (res) {
250 markup += res
251 }
252 }
253 }
254 // attach scoped CSS ID
255 let scopeId
256 const activeInstance = context.activeInstance
257 if (isDef(activeInstance) &&
258 activeInstance !== node.context &&
259 isDef(scopeId = activeInstance.$options._scopeId)
260 ) {
261 markup += ` ${(scopeId: any)}`
262 }
263 while (isDef(node)) {
264 if (isDef(scopeId = node.context.$options._scopeId)) {
265 markup += ` ${scopeId}`
266 }
267 node = node.parent
268 }
269 return markup + '>'
270}
271
272export function createRenderFunction (
273 modules: Array<Function>,
274 directives: Object,
275 isUnaryTag: Function,
276 cache: any
277) {
278 return function render (
279 component: Component,
280 write: (text: string, next: Function) => void,
281 userContext: ?Object,
282 done: Function
283 ) {
284 warned = Object.create(null)
285 const context = new RenderContext({
286 activeInstance: component,
287 userContext,
288 write, done, renderNode,
289 isUnaryTag, modules, directives,
290 cache
291 })
292 normalizeRender(component)
293 renderNode(component._render(), true, context)
294 }
295}