1 |
|
2 |
|
3 | import { genHandlers } from './events'
|
4 | import baseDirectives from '../directives/index'
|
5 | import { camelize, no, extend } from 'shared/util'
|
6 | import { baseWarn, pluckModuleFunction } from '../helpers'
|
7 | import { emptySlotScopeToken } from '../parser/index'
|
8 |
|
9 | type TransformFunction = (el: ASTElement, code: string) => string;
|
10 | type DataGenFunction = (el: ASTElement) => string;
|
11 | type DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;
|
12 |
|
13 | export class CodegenState {
|
14 | options: CompilerOptions;
|
15 | warn: Function;
|
16 | transforms: Array<TransformFunction>;
|
17 | dataGenFns: Array<DataGenFunction>;
|
18 | directives: { [key: string]: DirectiveFunction };
|
19 | maybeComponent: (el: ASTElement) => boolean;
|
20 | onceId: number;
|
21 | staticRenderFns: Array<string>;
|
22 | pre: boolean;
|
23 |
|
24 | constructor (options: CompilerOptions) {
|
25 | this.options = options
|
26 | this.warn = options.warn || baseWarn
|
27 | this.transforms = pluckModuleFunction(options.modules, 'transformCode')
|
28 | this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
|
29 | this.directives = extend(extend({}, baseDirectives), options.directives)
|
30 | const isReservedTag = options.isReservedTag || no
|
31 | this.maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
|
32 | this.onceId = 0
|
33 | this.staticRenderFns = []
|
34 | this.pre = false
|
35 | }
|
36 | }
|
37 |
|
38 | export type CodegenResult = {
|
39 | render: string,
|
40 | staticRenderFns: Array<string>
|
41 | };
|
42 |
|
43 | export function generate (
|
44 | ast: ASTElement | void,
|
45 | options: CompilerOptions
|
46 | ): CodegenResult {
|
47 | const state = new CodegenState(options)
|
48 | const code = ast ? genElement(ast, state) : '_c("div")'
|
49 | return {
|
50 | render: `with(this){return ${code}}`,
|
51 | staticRenderFns: state.staticRenderFns
|
52 | }
|
53 | }
|
54 |
|
55 | export function genElement (el: ASTElement, state: CodegenState): string {
|
56 | if (el.parent) {
|
57 | el.pre = el.pre || el.parent.pre
|
58 | }
|
59 |
|
60 | if (el.staticRoot && !el.staticProcessed) {
|
61 | return genStatic(el, state)
|
62 | } else if (el.once && !el.onceProcessed) {
|
63 | return genOnce(el, state)
|
64 | } else if (el.for && !el.forProcessed) {
|
65 | return genFor(el, state)
|
66 | } else if (el.if && !el.ifProcessed) {
|
67 | return genIf(el, state)
|
68 | } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
|
69 | return genChildren(el, state) || 'void 0'
|
70 | } else if (el.tag === 'slot') {
|
71 | return genSlot(el, state)
|
72 | } else {
|
73 |
|
74 | let code
|
75 | if (el.component) {
|
76 | code = genComponent(el.component, el, state)
|
77 | } else {
|
78 | let data
|
79 | if (!el.plain || (el.pre && state.maybeComponent(el))) {
|
80 | data = genData(el, state)
|
81 | }
|
82 |
|
83 | const children = el.inlineTemplate ? null : genChildren(el, state, true)
|
84 | code = `_c('${el.tag}'${
|
85 | data ? `,${data}` : '' // data
|
86 | }${
|
87 | children ? `,${children}` : '' // children
|
88 | })`
|
89 | }
|
90 |
|
91 | for (let i = 0; i < state.transforms.length; i++) {
|
92 | code = state.transforms[i](el, code)
|
93 | }
|
94 | return code
|
95 | }
|
96 | }
|
97 |
|
98 |
|
99 | function genStatic (el: ASTElement, state: CodegenState): string {
|
100 | el.staticProcessed = true
|
101 |
|
102 |
|
103 |
|
104 | const originalPreState = state.pre
|
105 | if (el.pre) {
|
106 | state.pre = el.pre
|
107 | }
|
108 | state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
|
109 | state.pre = originalPreState
|
110 | return `_m(${
|
111 | state.staticRenderFns.length - 1
|
112 | }${
|
113 | el.staticInFor ? ',true' : ''
|
114 | })`
|
115 | }
|
116 |
|
117 |
|
118 | function genOnce (el: ASTElement, state: CodegenState): string {
|
119 | el.onceProcessed = true
|
120 | if (el.if && !el.ifProcessed) {
|
121 | return genIf(el, state)
|
122 | } else if (el.staticInFor) {
|
123 | let key = ''
|
124 | let parent = el.parent
|
125 | while (parent) {
|
126 | if (parent.for) {
|
127 | key = parent.key
|
128 | break
|
129 | }
|
130 | parent = parent.parent
|
131 | }
|
132 | if (!key) {
|
133 | process.env.NODE_ENV !== 'production' && state.warn(
|
134 | `v-once can only be used inside v-for that is keyed. `,
|
135 | el.rawAttrsMap['v-once']
|
136 | )
|
137 | return genElement(el, state)
|
138 | }
|
139 | return `_o(${genElement(el, state)},${state.onceId++},${key})`
|
140 | } else {
|
141 | return genStatic(el, state)
|
142 | }
|
143 | }
|
144 |
|
145 | export function genIf (
|
146 | el: any,
|
147 | state: CodegenState,
|
148 | altGen?: Function,
|
149 | altEmpty?: string
|
150 | ): string {
|
151 | el.ifProcessed = true
|
152 | return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
|
153 | }
|
154 |
|
155 | function genIfConditions (
|
156 | conditions: ASTIfConditions,
|
157 | state: CodegenState,
|
158 | altGen?: Function,
|
159 | altEmpty?: string
|
160 | ): string {
|
161 | if (!conditions.length) {
|
162 | return altEmpty || '_e()'
|
163 | }
|
164 |
|
165 | const condition = conditions.shift()
|
166 | if (condition.exp) {
|
167 | return `(${condition.exp})?${
|
168 | genTernaryExp(condition.block)
|
169 | }:${
|
170 | genIfConditions(conditions, state, altGen, altEmpty)
|
171 | }`
|
172 | } else {
|
173 | return `${genTernaryExp(condition.block)}`
|
174 | }
|
175 |
|
176 |
|
177 | function genTernaryExp (el) {
|
178 | return altGen
|
179 | ? altGen(el, state)
|
180 | : el.once
|
181 | ? genOnce(el, state)
|
182 | : genElement(el, state)
|
183 | }
|
184 | }
|
185 |
|
186 | export function genFor (
|
187 | el: any,
|
188 | state: CodegenState,
|
189 | altGen?: Function,
|
190 | altHelper?: string
|
191 | ): string {
|
192 | const exp = el.for
|
193 | const alias = el.alias
|
194 | const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
|
195 | const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
|
196 |
|
197 | if (process.env.NODE_ENV !== 'production' &&
|
198 | state.maybeComponent(el) &&
|
199 | el.tag !== 'slot' &&
|
200 | el.tag !== 'template' &&
|
201 | !el.key
|
202 | ) {
|
203 | state.warn(
|
204 | `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
|
205 | `v-for should have explicit keys. ` +
|
206 | `See https://vuejs.org/guide/list.html#key for more info.`,
|
207 | el.rawAttrsMap['v-for'],
|
208 | true
|
209 | )
|
210 | }
|
211 |
|
212 | el.forProcessed = true
|
213 | return `${altHelper || '_l'}((${exp}),` +
|
214 | `function(${alias}${iterator1}${iterator2}){` +
|
215 | `return ${(altGen || genElement)(el, state)}` +
|
216 | '})'
|
217 | }
|
218 |
|
219 | export function genData (el: ASTElement, state: CodegenState): string {
|
220 | let data = '{'
|
221 |
|
222 |
|
223 |
|
224 | const dirs = genDirectives(el, state)
|
225 | if (dirs) data += dirs + ','
|
226 |
|
227 |
|
228 | if (el.key) {
|
229 | data += `key:${el.key},`
|
230 | }
|
231 |
|
232 | if (el.ref) {
|
233 | data += `ref:${el.ref},`
|
234 | }
|
235 | if (el.refInFor) {
|
236 | data += `refInFor:true,`
|
237 | }
|
238 |
|
239 | if (el.pre) {
|
240 | data += `pre:true,`
|
241 | }
|
242 |
|
243 | if (el.component) {
|
244 | data += `tag:"${el.tag}",`
|
245 | }
|
246 |
|
247 | for (let i = 0; i < state.dataGenFns.length; i++) {
|
248 | data += state.dataGenFns[i](el)
|
249 | }
|
250 |
|
251 | if (el.attrs) {
|
252 | data += `attrs:${genProps(el.attrs)},`
|
253 | }
|
254 |
|
255 | if (el.props) {
|
256 | data += `domProps:${genProps(el.props)},`
|
257 | }
|
258 |
|
259 | if (el.events) {
|
260 | data += `${genHandlers(el.events, false)},`
|
261 | }
|
262 | if (el.nativeEvents) {
|
263 | data += `${genHandlers(el.nativeEvents, true)},`
|
264 | }
|
265 |
|
266 |
|
267 | if (el.slotTarget && !el.slotScope) {
|
268 | data += `slot:${el.slotTarget},`
|
269 | }
|
270 |
|
271 | if (el.scopedSlots) {
|
272 | data += `${genScopedSlots(el, el.scopedSlots, state)},`
|
273 | }
|
274 |
|
275 | if (el.model) {
|
276 | data += `model:{value:${
|
277 | el.model.value
|
278 | },callback:${
|
279 | el.model.callback
|
280 | },expression:${
|
281 | el.model.expression
|
282 | }},`
|
283 | }
|
284 |
|
285 | if (el.inlineTemplate) {
|
286 | const inlineTemplate = genInlineTemplate(el, state)
|
287 | if (inlineTemplate) {
|
288 | data += `${inlineTemplate},`
|
289 | }
|
290 | }
|
291 | data = data.replace(/,$/, '') + '}'
|
292 |
|
293 |
|
294 |
|
295 | if (el.dynamicAttrs) {
|
296 | data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
|
297 | }
|
298 |
|
299 | if (el.wrapData) {
|
300 | data = el.wrapData(data)
|
301 | }
|
302 |
|
303 | if (el.wrapListeners) {
|
304 | data = el.wrapListeners(data)
|
305 | }
|
306 | return data
|
307 | }
|
308 |
|
309 | function genDirectives (el: ASTElement, state: CodegenState): string | void {
|
310 | const dirs = el.directives
|
311 | if (!dirs) return
|
312 | let res = 'directives:['
|
313 | let hasRuntime = false
|
314 | let i, l, dir, needRuntime
|
315 | for (i = 0, l = dirs.length; i < l; i++) {
|
316 | dir = dirs[i]
|
317 | needRuntime = true
|
318 | const gen: DirectiveFunction = state.directives[dir.name]
|
319 | if (gen) {
|
320 |
|
321 |
|
322 | needRuntime = !!gen(el, dir, state.warn)
|
323 | }
|
324 | if (needRuntime) {
|
325 | hasRuntime = true
|
326 | res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
|
327 | dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
|
328 | }${
|
329 | dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
|
330 | }${
|
331 | dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
|
332 | }},`
|
333 | }
|
334 | }
|
335 | if (hasRuntime) {
|
336 | return res.slice(0, -1) + ']'
|
337 | }
|
338 | }
|
339 |
|
340 | function genInlineTemplate (el: ASTElement, state: CodegenState): ?string {
|
341 | const ast = el.children[0]
|
342 | if (process.env.NODE_ENV !== 'production' && (
|
343 | el.children.length !== 1 || ast.type !== 1
|
344 | )) {
|
345 | state.warn(
|
346 | 'Inline-template components must have exactly one child element.',
|
347 | { start: el.start }
|
348 | )
|
349 | }
|
350 | if (ast && ast.type === 1) {
|
351 | const inlineRenderFns = generate(ast, state.options)
|
352 | return `inlineTemplate:{render:function(){${
|
353 | inlineRenderFns.render
|
354 | }},staticRenderFns:[${
|
355 | inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')
|
356 | }]}`
|
357 | }
|
358 | }
|
359 |
|
360 | function genScopedSlots (
|
361 | el: ASTElement,
|
362 | slots: { [key: string]: ASTElement },
|
363 | state: CodegenState
|
364 | ): string {
|
365 |
|
366 |
|
367 |
|
368 |
|
369 | let needsForceUpdate = Object.keys(slots).some(key => {
|
370 | const slot = slots[key]
|
371 | return (
|
372 | slot.slotTargetDynamic ||
|
373 | slot.if ||
|
374 | slot.for ||
|
375 | containsSlotChild(slot)
|
376 | )
|
377 | })
|
378 |
|
379 |
|
380 | if (!needsForceUpdate) {
|
381 | let parent = el.parent
|
382 | while (parent) {
|
383 | if (parent.slotScope && parent.slotScope !== emptySlotScopeToken) {
|
384 | needsForceUpdate = true
|
385 | break
|
386 | }
|
387 | parent = parent.parent
|
388 | }
|
389 | }
|
390 |
|
391 | return `scopedSlots:_u([${
|
392 | Object.keys(slots).map(key => {
|
393 | return genScopedSlot(slots[key], state)
|
394 | }).join(',')
|
395 | }]${needsForceUpdate ? `,true` : ``})`
|
396 | }
|
397 |
|
398 | function containsSlotChild (el: ASTNode): boolean {
|
399 | if (el.type === 1) {
|
400 | if (el.tag === 'slot') {
|
401 | return true
|
402 | }
|
403 | return el.children.some(containsSlotChild)
|
404 | }
|
405 | return false
|
406 | }
|
407 |
|
408 | function genScopedSlot (
|
409 | el: ASTElement,
|
410 | state: CodegenState
|
411 | ): string {
|
412 | const isLegacySyntax = el.attrsMap['slot-scope']
|
413 | if (el.if && !el.ifProcessed && !isLegacySyntax) {
|
414 | return genIf(el, state, genScopedSlot, `null`)
|
415 | }
|
416 | if (el.for && !el.forProcessed) {
|
417 | return genFor(el, state, genScopedSlot)
|
418 | }
|
419 | const slotScope = el.slotScope === emptySlotScopeToken
|
420 | ? ``
|
421 | : String(el.slotScope)
|
422 | const fn = `function(${slotScope}){` +
|
423 | `return ${el.tag === 'template'
|
424 | ? el.if && isLegacySyntax
|
425 | ? `(${el.if})?${genChildren(el, state) || 'undefined'}:undefined`
|
426 | : genChildren(el, state) || 'undefined'
|
427 | : genElement(el, state)
|
428 | }}`
|
429 |
|
430 | const reverseProxy = slotScope ? `` : `,proxy:true`
|
431 | return `{key:${el.slotTarget || `"default"`},fn:${fn}${reverseProxy}}`
|
432 | }
|
433 |
|
434 | export function genChildren (
|
435 | el: ASTElement,
|
436 | state: CodegenState,
|
437 | checkSkip?: boolean,
|
438 | altGenElement?: Function,
|
439 | altGenNode?: Function
|
440 | ): string | void {
|
441 | const children = el.children
|
442 | if (children.length) {
|
443 | const el: any = children[0]
|
444 |
|
445 | if (children.length === 1 &&
|
446 | el.for &&
|
447 | el.tag !== 'template' &&
|
448 | el.tag !== 'slot'
|
449 | ) {
|
450 | const normalizationType = checkSkip
|
451 | ? state.maybeComponent(el) ? `,1` : `,0`
|
452 | : ``
|
453 | return `${(altGenElement || genElement)(el, state)}${normalizationType}`
|
454 | }
|
455 | const normalizationType = checkSkip
|
456 | ? getNormalizationType(children, state.maybeComponent)
|
457 | : 0
|
458 | const gen = altGenNode || genNode
|
459 | return `[${children.map(c => gen(c, state)).join(',')}]${
|
460 | normalizationType ? `,${normalizationType}` : ''
|
461 | }`
|
462 | }
|
463 | }
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 | function getNormalizationType (
|
470 | children: Array<ASTNode>,
|
471 | maybeComponent: (el: ASTElement) => boolean
|
472 | ): number {
|
473 | let res = 0
|
474 | for (let i = 0; i < children.length; i++) {
|
475 | const el: ASTNode = children[i]
|
476 | if (el.type !== 1) {
|
477 | continue
|
478 | }
|
479 | if (needsNormalization(el) ||
|
480 | (el.ifConditions && el.ifConditions.some(c => needsNormalization(c.block)))) {
|
481 | res = 2
|
482 | break
|
483 | }
|
484 | if (maybeComponent(el) ||
|
485 | (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))) {
|
486 | res = 1
|
487 | }
|
488 | }
|
489 | return res
|
490 | }
|
491 |
|
492 | function needsNormalization (el: ASTElement): boolean {
|
493 | return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
|
494 | }
|
495 |
|
496 | function genNode (node: ASTNode, state: CodegenState): string {
|
497 | if (node.type === 1) {
|
498 | return genElement(node, state)
|
499 | } else if (node.type === 3 && node.isComment) {
|
500 | return genComment(node)
|
501 | } else {
|
502 | return genText(node)
|
503 | }
|
504 | }
|
505 |
|
506 | export function genText (text: ASTText | ASTExpression): string {
|
507 | return `_v(${text.type === 2
|
508 | ? text.expression // no need for () because already wrapped in _s()
|
509 | : transformSpecialNewlines(JSON.stringify(text.text))
|
510 | })`
|
511 | }
|
512 |
|
513 | export function genComment (comment: ASTText): string {
|
514 | return `_e(${JSON.stringify(comment.text)})`
|
515 | }
|
516 |
|
517 | function genSlot (el: ASTElement, state: CodegenState): string {
|
518 | const slotName = el.slotName || '"default"'
|
519 | const children = genChildren(el, state)
|
520 | let res = `_t(${slotName}${children ? `,${children}` : ''}`
|
521 | const attrs = el.attrs || el.dynamicAttrs
|
522 | ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({
|
523 |
|
524 | name: camelize(attr.name),
|
525 | value: attr.value,
|
526 | dynamic: attr.dynamic
|
527 | })))
|
528 | : null
|
529 | const bind = el.attrsMap['v-bind']
|
530 | if ((attrs || bind) && !children) {
|
531 | res += `,null`
|
532 | }
|
533 | if (attrs) {
|
534 | res += `,${attrs}`
|
535 | }
|
536 | if (bind) {
|
537 | res += `${attrs ? '' : ',null'},${bind}`
|
538 | }
|
539 | return res + ')'
|
540 | }
|
541 |
|
542 |
|
543 | function genComponent (
|
544 | componentName: string,
|
545 | el: ASTElement,
|
546 | state: CodegenState
|
547 | ): string {
|
548 | const children = el.inlineTemplate ? null : genChildren(el, state, true)
|
549 | return `_c(${componentName},${genData(el, state)}${
|
550 | children ? `,${children}` : ''
|
551 | })`
|
552 | }
|
553 |
|
554 | function genProps (props: Array<ASTAttr>): string {
|
555 | let staticProps = ``
|
556 | let dynamicProps = ``
|
557 | for (let i = 0; i < props.length; i++) {
|
558 | const prop = props[i]
|
559 | const value = __WEEX__
|
560 | ? generateValue(prop.value)
|
561 | : transformSpecialNewlines(prop.value)
|
562 | if (prop.dynamic) {
|
563 | dynamicProps += `${prop.name},${value},`
|
564 | } else {
|
565 | staticProps += `"${prop.name}":${value},`
|
566 | }
|
567 | }
|
568 | staticProps = `{${staticProps.slice(0, -1)}}`
|
569 | if (dynamicProps) {
|
570 | return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
|
571 | } else {
|
572 | return staticProps
|
573 | }
|
574 | }
|
575 |
|
576 |
|
577 | function generateValue (value) {
|
578 | if (typeof value === 'string') {
|
579 | return transformSpecialNewlines(value)
|
580 | }
|
581 | return JSON.stringify(value)
|
582 | }
|
583 |
|
584 |
|
585 | function transformSpecialNewlines (text: string): string {
|
586 | return text
|
587 | .replace(/\u2028/g, '\\u2028')
|
588 | .replace(/\u2029/g, '\\u2029')
|
589 | }
|