1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import {
|
8 | genIf,
|
9 | genFor,
|
10 | genData,
|
11 | genText,
|
12 | genElement,
|
13 | genChildren,
|
14 | CodegenState
|
15 | } from 'compiler/codegen/index'
|
16 |
|
17 | import {
|
18 | genAttrSegments,
|
19 | genDOMPropSegments,
|
20 | genClassSegments,
|
21 | genStyleSegments,
|
22 | applyModelTransform
|
23 | } from './modules'
|
24 |
|
25 | import { escape } from 'web/server/util'
|
26 | import { optimizability } from './optimizer'
|
27 | import type { CodegenResult } from 'compiler/codegen/index'
|
28 |
|
29 | export type StringSegment = {
|
30 | type: number;
|
31 | value: string;
|
32 | };
|
33 |
|
34 |
|
35 | export const RAW = 0
|
36 | export const INTERPOLATION = 1
|
37 | export const EXPRESSION = 2
|
38 |
|
39 | export 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 |
|
51 | function 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 |
|
65 | return genStringElement(el, state)
|
66 | case optimizability.SELF:
|
67 |
|
68 | return genStringElementWithChildren(el, state)
|
69 | case optimizability.CHILDREN:
|
70 |
|
71 | return genNormalElement(el, state, true)
|
72 | case optimizability.PARTIAL:
|
73 |
|
74 | return genNormalElement(el, state, false)
|
75 | default:
|
76 |
|
77 | return genElement(el, state)
|
78 | }
|
79 | }
|
80 |
|
81 | function 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 |
|
93 | function genSSRChildren (el, state, checkSkip) {
|
94 | return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
|
95 | }
|
96 |
|
97 | function genSSRNode (el, state) {
|
98 | return el.type === 1
|
99 | ? genSSRElement(el, state)
|
100 | : genText(el)
|
101 | }
|
102 |
|
103 | function genChildrenAsStringNode (el, state) {
|
104 | return el.children.length
|
105 | ? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
|
106 | : ''
|
107 | }
|
108 |
|
109 | function genStringElement (el, state) {
|
110 | return `_ssrNode(${elementToString(el, state)})`
|
111 | }
|
112 |
|
113 | function 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 |
|
122 | function elementToString (el, state) {
|
123 | return `(${flattenSegments(elementToSegments(el, state))})`
|
124 | }
|
125 |
|
126 | function elementToSegments (el, state): Array<StringSegment> {
|
127 |
|
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 |
|
153 | function elementToOpenTagSegments (el, state): Array<StringSegment> {
|
154 | applyModelTransform(el, state)
|
155 | let binding
|
156 | const segments = [{ type: RAW, value: `<${el.tag}` }]
|
157 |
|
158 | if (el.attrs) {
|
159 | segments.push.apply(segments, genAttrSegments(el.attrs))
|
160 | }
|
161 |
|
162 | if (el.props) {
|
163 | segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
|
164 | }
|
165 |
|
166 | if ((binding = el.attrsMap['v-bind'])) {
|
167 | segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
|
168 | }
|
169 |
|
170 | if ((binding = el.attrsMap['v-bind.prop'])) {
|
171 | segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
|
172 | }
|
173 |
|
174 | if (el.staticClass || el.classBinding) {
|
175 | segments.push.apply(
|
176 | segments,
|
177 | genClassSegments(el.staticClass, el.classBinding)
|
178 | )
|
179 | }
|
180 |
|
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 |
|
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 |
|
200 | function 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 |
|
216 | function 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 |
|
238 | function 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 | }
|