1 |
|
2 |
|
3 | const { escape } = require('he')
|
4 |
|
5 | import { SSR_ATTR } from 'shared/constants'
|
6 | import { RenderContext } from './render-context'
|
7 | import { compileToFunctions } from 'web/compiler/index'
|
8 | import { createComponentInstanceForVnode } from 'core/vdom/create-component'
|
9 |
|
10 | import { isDef, isUndef, isTrue } from 'shared/util'
|
11 |
|
12 | let warned = Object.create(null)
|
13 | const warnOnce = msg => {
|
14 | if (!warned[msg]) {
|
15 | warned[msg] = true
|
16 | console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
|
17 | }
|
18 | }
|
19 |
|
20 | const compilationCache = Object.create(null)
|
21 | const 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 |
|
40 | function 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 |
|
60 | function registerComponentForCache (options, write) {
|
61 |
|
62 |
|
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 |
|
70 | function renderComponent (node, isRoot, context) {
|
71 | const { write, next, userContext } = context
|
72 |
|
73 |
|
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 |
|
129 | function 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 |
|
146 | function renderComponentInner (node, isRoot, context) {
|
147 | const prevActive = context.activeInstance
|
148 |
|
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 |
|
164 | function 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 |
|
195 | function hasAncestorData (node: VNode) {
|
196 | const parentNode = node.parent
|
197 | return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
|
198 | }
|
199 |
|
200 | function 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 |
|
216 | function renderStartingTag (node: VNode, context) {
|
217 | let markup = `<${node.tag}`
|
218 | const { directives, modules } = context
|
219 |
|
220 |
|
221 |
|
222 | if (isUndef(node.data) && hasAncestorData(node)) {
|
223 | node.data = {}
|
224 | }
|
225 | if (isDef(node.data)) {
|
226 |
|
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 |
|
234 |
|
235 | dirRenderer(node, dirs[i])
|
236 | }
|
237 | }
|
238 | }
|
239 |
|
240 |
|
241 | const vshowDirectiveInfo = getVShowDirectiveInfo(node)
|
242 | if (vshowDirectiveInfo) {
|
243 | directives.show(node, vshowDirectiveInfo)
|
244 | }
|
245 |
|
246 |
|
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 |
|
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 |
|
272 | export 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 | }
|