UNPKG

16.5 kBJavaScriptView Raw
1/* @flow */
2
3import { genHandlers } from './events'
4import baseDirectives from '../directives/index'
5import { camelize, no, extend } from 'shared/util'
6import { baseWarn, pluckModuleFunction } from '../helpers'
7import { emptySlotScopeToken } from '../parser/index'
8
9type TransformFunction = (el: ASTElement, code: string) => string;
10type DataGenFunction = (el: ASTElement) => string;
11type DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;
12
13export 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
38export type CodegenResult = {
39 render: string,
40 staticRenderFns: Array<string>
41};
42
43export 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
55export 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 // component or element
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 // module transforms
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// hoist static sub-trees out
99function genStatic (el: ASTElement, state: CodegenState): string {
100 el.staticProcessed = true
101 // Some elements (templates) need to behave differently inside of a v-pre
102 // node. All pre nodes are static roots, so we can use this as a location to
103 // wrap a state change and reset it upon exiting the pre node.
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// v-once
118function 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
145export function genIf (
146 el: any,
147 state: CodegenState,
148 altGen?: Function,
149 altEmpty?: string
150): string {
151 el.ifProcessed = true // avoid recursion
152 return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
153}
154
155function 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 // v-if with v-once should generate code like (a)?_m(0):_m(1)
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
186export 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 /* tip */
209 )
210 }
211
212 el.forProcessed = true // avoid recursion
213 return `${altHelper || '_l'}((${exp}),` +
214 `function(${alias}${iterator1}${iterator2}){` +
215 `return ${(altGen || genElement)(el, state)}` +
216 '})'
217}
218
219export function genData (el: ASTElement, state: CodegenState): string {
220 let data = '{'
221
222 // directives first.
223 // directives may mutate the el's other properties before they are generated.
224 const dirs = genDirectives(el, state)
225 if (dirs) data += dirs + ','
226
227 // key
228 if (el.key) {
229 data += `key:${el.key},`
230 }
231 // ref
232 if (el.ref) {
233 data += `ref:${el.ref},`
234 }
235 if (el.refInFor) {
236 data += `refInFor:true,`
237 }
238 // pre
239 if (el.pre) {
240 data += `pre:true,`
241 }
242 // record original tag name for components using "is" attribute
243 if (el.component) {
244 data += `tag:"${el.tag}",`
245 }
246 // module data generation functions
247 for (let i = 0; i < state.dataGenFns.length; i++) {
248 data += state.dataGenFns[i](el)
249 }
250 // attributes
251 if (el.attrs) {
252 data += `attrs:${genProps(el.attrs)},`
253 }
254 // DOM props
255 if (el.props) {
256 data += `domProps:${genProps(el.props)},`
257 }
258 // event handlers
259 if (el.events) {
260 data += `${genHandlers(el.events, false)},`
261 }
262 if (el.nativeEvents) {
263 data += `${genHandlers(el.nativeEvents, true)},`
264 }
265 // slot target
266 // only for non-scoped slots
267 if (el.slotTarget && !el.slotScope) {
268 data += `slot:${el.slotTarget},`
269 }
270 // scoped slots
271 if (el.scopedSlots) {
272 data += `${genScopedSlots(el, el.scopedSlots, state)},`
273 }
274 // component v-model
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 // inline-template
285 if (el.inlineTemplate) {
286 const inlineTemplate = genInlineTemplate(el, state)
287 if (inlineTemplate) {
288 data += `${inlineTemplate},`
289 }
290 }
291 data = data.replace(/,$/, '') + '}'
292 // v-bind dynamic argument wrap
293 // v-bind with dynamic arguments must be applied using the same v-bind object
294 // merge helper so that class/style/mustUseProp attrs are handled correctly.
295 if (el.dynamicAttrs) {
296 data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
297 }
298 // v-bind data wrap
299 if (el.wrapData) {
300 data = el.wrapData(data)
301 }
302 // v-on data wrap
303 if (el.wrapListeners) {
304 data = el.wrapListeners(data)
305 }
306 return data
307}
308
309function 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 // compile-time directive that manipulates AST.
321 // returns true if it also needs a runtime counterpart.
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
340function 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
360function genScopedSlots (
361 el: ASTElement,
362 slots: { [key: string]: ASTElement },
363 state: CodegenState
364): string {
365 // by default scoped slots are considered "stable", this allows child
366 // components with only scoped slots to skip forced updates from parent.
367 // but in some cases we have to bail-out of this optimization
368 // for example if the slot contains dynamic names, has v-if or v-for on them...
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) // is passing down slot from parent which may be dynamic
376 )
377 })
378 // OR when it is inside another scoped slot (the reactivity is disconnected)
379 // #9438
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
398function 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
408function 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 // reverse proxy v-slot without scope on this.$slots
430 const reverseProxy = slotScope ? `` : `,proxy:true`
431 return `{key:${el.slotTarget || `"default"`},fn:${fn}${reverseProxy}}`
432}
433
434export 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 // optimize single v-for
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// determine the normalization needed for the children array.
466// 0: no normalization needed
467// 1: simple normalization needed (possible 1-level deep nested array)
468// 2: full normalization needed
469function 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
492function needsNormalization (el: ASTElement): boolean {
493 return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
494}
495
496function 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
506export 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
513export function genComment (comment: ASTText): string {
514 return `_e(${JSON.stringify(comment.text)})`
515}
516
517function 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 // slot props are camelized
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// componentName is el.component, take it as argument to shun flow's pessimistic refinement
543function 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
554function 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/* istanbul ignore next */
577function generateValue (value) {
578 if (typeof value === 'string') {
579 return transformSpecialNewlines(value)
580 }
581 return JSON.stringify(value)
582}
583
584// #3895, #4268
585function transformSpecialNewlines (text: string): string {
586 return text
587 .replace(/\u2028/g, '\\u2028')
588 .replace(/\u2029/g, '\\u2029')
589}