UNPKG

7.21 kBJavaScriptView Raw
1/* @flow */
2
3// The SSR codegen is essentially extending the default codegen to handle
4// SSR-optimizable nodes and turn them into string render fns. In cases where
5// a node is not optimizable it simply falls back to the default codegen.
6
7import {
8 genIf,
9 genFor,
10 genData,
11 genText,
12 genElement,
13 genChildren,
14 CodegenState
15} from 'compiler/codegen/index'
16
17import {
18 genAttrSegments,
19 genDOMPropSegments,
20 genClassSegments,
21 genStyleSegments,
22 applyModelTransform
23} from './modules'
24
25import { escape } from 'web/server/util'
26import { optimizability } from './optimizer'
27import type { CodegenResult } from 'compiler/codegen/index'
28
29export type StringSegment = {
30 type: number;
31 value: string;
32};
33
34// segment types
35export const RAW = 0
36export const INTERPOLATION = 1
37export const EXPRESSION = 2
38
39export function generate (
40 ast: ASTElement | void,
41 options: CompilerOptions
42): CodegenResult {
43 const state = new CodegenState(options)
44 const code = ast ? genSSRElement(ast, state) : '_c("div")'
45 return {
46 render: `with(this){return ${code}}`,
47 staticRenderFns: state.staticRenderFns
48 }
49}
50
51function genSSRElement (el: ASTElement, state: CodegenState): string {
52 if (el.for && !el.forProcessed) {
53 return genFor(el, state, genSSRElement)
54 } else if (el.if && !el.ifProcessed) {
55 return genIf(el, state, genSSRElement)
56 } else if (el.tag === 'template' && !el.slotTarget) {
57 return el.ssrOptimizability === optimizability.FULL
58 ? genChildrenAsStringNode(el, state)
59 : genSSRChildren(el, state) || 'void 0'
60 }
61
62 switch (el.ssrOptimizability) {
63 case optimizability.FULL:
64 // stringify whole tree
65 return genStringElement(el, state)
66 case optimizability.SELF:
67 // stringify self and check children
68 return genStringElementWithChildren(el, state)
69 case optimizability.CHILDREN:
70 // generate self as VNode and stringify children
71 return genNormalElement(el, state, true)
72 case optimizability.PARTIAL:
73 // generate self as VNode and check children
74 return genNormalElement(el, state, false)
75 default:
76 // bail whole tree
77 return genElement(el, state)
78 }
79}
80
81function genNormalElement (el, state, stringifyChildren) {
82 const data = el.plain ? undefined : genData(el, state)
83 const children = stringifyChildren
84 ? `[${genChildrenAsStringNode(el, state)}]`
85 : genSSRChildren(el, state, true)
86 return `_c('${el.tag}'${
87 data ? `,${data}` : ''
88 }${
89 children ? `,${children}` : ''
90 })`
91}
92
93function genSSRChildren (el, state, checkSkip) {
94 return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
95}
96
97function genSSRNode (el, state) {
98 return el.type === 1
99 ? genSSRElement(el, state)
100 : genText(el)
101}
102
103function genChildrenAsStringNode (el, state) {
104 return el.children.length
105 ? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
106 : ''
107}
108
109function genStringElement (el, state) {
110 return `_ssrNode(${elementToString(el, state)})`
111}
112
113function genStringElementWithChildren (el, state) {
114 const children = genSSRChildren(el, state, true)
115 return `_ssrNode(${
116 flattenSegments(elementToOpenTagSegments(el, state))
117 },"</${el.tag}>"${
118 children ? `,${children}` : ''
119 })`
120}
121
122function elementToString (el, state) {
123 return `(${flattenSegments(elementToSegments(el, state))})`
124}
125
126function elementToSegments (el, state): Array<StringSegment> {
127 // v-for / v-if
128 if (el.for && !el.forProcessed) {
129 el.forProcessed = true
130 return [{
131 type: EXPRESSION,
132 value: genFor(el, state, elementToString, '_ssrList')
133 }]
134 } else if (el.if && !el.ifProcessed) {
135 el.ifProcessed = true
136 return [{
137 type: EXPRESSION,
138 value: genIf(el, state, elementToString, '"<!---->"')
139 }]
140 } else if (el.tag === 'template') {
141 return childrenToSegments(el, state)
142 }
143
144 const openSegments = elementToOpenTagSegments(el, state)
145 const childrenSegments = childrenToSegments(el, state)
146 const { isUnaryTag } = state.options
147 const close = (isUnaryTag && isUnaryTag(el.tag))
148 ? []
149 : [{ type: RAW, value: `</${el.tag}>` }]
150 return openSegments.concat(childrenSegments, close)
151}
152
153function elementToOpenTagSegments (el, state): Array<StringSegment> {
154 applyModelTransform(el, state)
155 let binding
156 const segments = [{ type: RAW, value: `<${el.tag}` }]
157 // attrs
158 if (el.attrs) {
159 segments.push.apply(segments, genAttrSegments(el.attrs))
160 }
161 // domProps
162 if (el.props) {
163 segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
164 }
165 // v-bind="object"
166 if ((binding = el.attrsMap['v-bind'])) {
167 segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
168 }
169 // v-bind.prop="object"
170 if ((binding = el.attrsMap['v-bind.prop'])) {
171 segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
172 }
173 // class
174 if (el.staticClass || el.classBinding) {
175 segments.push.apply(
176 segments,
177 genClassSegments(el.staticClass, el.classBinding)
178 )
179 }
180 // style & v-show
181 if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {
182 segments.push.apply(
183 segments,
184 genStyleSegments(
185 el.attrsMap.style,
186 el.staticStyle,
187 el.styleBinding,
188 el.attrsMap['v-show']
189 )
190 )
191 }
192 // _scopedId
193 if (state.options.scopeId) {
194 segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
195 }
196 segments.push({ type: RAW, value: `>` })
197 return segments
198}
199
200function childrenToSegments (el, state): Array<StringSegment> {
201 let binding
202 if ((binding = el.attrsMap['v-html'])) {
203 return [{ type: EXPRESSION, value: `_s(${binding})` }]
204 }
205 if ((binding = el.attrsMap['v-text'])) {
206 return [{ type: INTERPOLATION, value: `_s(${binding})` }]
207 }
208 if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {
209 return [{ type: INTERPOLATION, value: `_s(${binding})` }]
210 }
211 return el.children
212 ? nodesToSegments(el.children, state)
213 : []
214}
215
216function nodesToSegments (
217 children: Array<ASTNode>,
218 state: CodegenState
219): Array<StringSegment> {
220 const segments = []
221 for (let i = 0; i < children.length; i++) {
222 const c = children[i]
223 if (c.type === 1) {
224 segments.push.apply(segments, elementToSegments(c, state))
225 } else if (c.type === 2) {
226 segments.push({ type: INTERPOLATION, value: c.expression })
227 } else if (c.type === 3) {
228 let text = escape(c.text)
229 if (c.isComment) {
230 text = '<!--' + text + '-->'
231 }
232 segments.push({ type: RAW, value: text })
233 }
234 }
235 return segments
236}
237
238function flattenSegments (segments: Array<StringSegment>): string {
239 const mergedSegments = []
240 let textBuffer = ''
241
242 const pushBuffer = () => {
243 if (textBuffer) {
244 mergedSegments.push(JSON.stringify(textBuffer))
245 textBuffer = ''
246 }
247 }
248
249 for (let i = 0; i < segments.length; i++) {
250 const s = segments[i]
251 if (s.type === RAW) {
252 textBuffer += s.value
253 } else if (s.type === INTERPOLATION) {
254 pushBuffer()
255 mergedSegments.push(`_ssrEscape(${s.value})`)
256 } else if (s.type === EXPRESSION) {
257 pushBuffer()
258 mergedSegments.push(`(${s.value})`)
259 }
260 }
261 pushBuffer()
262
263 return mergedSegments.join('+')
264}