/* @flow */ import { genHandlers } from './events' import { baseWarn, pluckModuleFunction } from '../helpers' import baseDirectives from '../directives/index' import { camelize } from 'shared/util' // configurable state let warn let transforms let dataGenFns let platformDirectives let staticRenderFns let onceCount let currentOptions export function generate ( ast: ASTElement | void, options: CompilerOptions ): { render: string, staticRenderFns: Array<string> } { // save previous staticRenderFns so generate calls can be nested const prevStaticRenderFns: Array<string> = staticRenderFns const currentStaticRenderFns: Array<string> = staticRenderFns = [] const prevOnceCount = onceCount onceCount = 0 currentOptions = options warn = options.warn || baseWarn transforms = pluckModuleFunction(options.modules, 'transformCode') dataGenFns = pluckModuleFunction(options.modules, 'genData') platformDirectives = options.directives || {} const code = ast ? genElement(ast) : '_h("div")' staticRenderFns = prevStaticRenderFns onceCount = prevOnceCount return { render: `with(this){return ${code}}`, staticRenderFns: currentStaticRenderFns } } function genElement (el: ASTElement): string { if (el.staticRoot && !el.staticProcessed) { return genStatic(el) } else if (el.once && !el.onceProcessed) { return genOnce(el) } else if (el.for && !el.forProcessed) { return genFor(el) } else if (el.if && !el.ifProcessed) { return genIf(el) } else if (el.tag === 'template' && !el.slotTarget) { return genChildren(el) || 'void 0' } else if (el.tag === 'slot') { return genSlot(el) } else { // component or element let code if (el.component) { code = genComponent(el.component, el) } else { const data = el.plain ? undefined : genData(el) const children = el.inlineTemplate ? null : genChildren(el) code = `_h('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })` } // module transforms for (let i = 0; i < transforms.length; i++) { code = transforms[i](el, code) } return code } } // hoist static sub-trees out function genStatic (el: ASTElement): string { el.staticProcessed = true staticRenderFns.push(`with(this){return ${genElement(el)}}`) return `_m(${staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})` } // v-once function genOnce (el: ASTElement): string { el.onceProcessed = true if (el.if && !el.ifProcessed) { return genIf(el) } else if (el.staticInFor) { let key = '' let parent = el.parent while (parent) { if (parent.for) { key = parent.key break } parent = parent.parent } if (!key) { process.env.NODE_ENV !== 'production' && warn( `v-once can only be used inside v-for that is keyed. ` ) return genElement(el) } return `_o(${genElement(el)},${onceCount++}${key ? `,${key}` : ``})` } else { return genStatic(el) } } function genIf (el: any): string { el.ifProcessed = true // avoid recursion return genIfConditions(el.conditions) } function genIfConditions (conditions: ASTIfConditions): string { if (!conditions.length) { return '_e()' } var condition = conditions.shift() if (condition.exp) { return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}` } else { return `${genTernaryExp(condition.block)}` } // v-if with v-once shuold generate code like (a)?_m(0):_m(1) function genTernaryExp (el) { return el.once ? genOnce(el) : genElement(el) } } function genFor (el: any): string { const exp = el.for const alias = el.alias const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' el.forProcessed = true // avoid recursion return `_l((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${genElement(el)}` + '})' } function genData (el: ASTElement): string { let data = '{' // directives first. // directives may mutate the el's other properties before they are generated. const dirs = genDirectives(el) if (dirs) data += dirs + ',' // key if (el.key) { data += `key:${el.key},` } // ref if (el.ref) { data += `ref:${el.ref},` } if (el.refInFor) { data += `refInFor:true,` } // record original tag name for components using "is" attribute if (el.component) { data += `tag:"${el.tag}",` } // module data generation functions for (let i = 0; i < dataGenFns.length; i++) { data += dataGenFns[i](el) } // attributes if (el.attrs) { data += `attrs:{${genProps(el.attrs)}},` } // DOM props if (el.props) { data += `domProps:{${genProps(el.props)}},` } // event handlers if (el.events) { data += `${genHandlers(el.events)},` } if (el.nativeEvents) { data += `${genHandlers(el.nativeEvents, true)},` } // slot target if (el.slotTarget) { data += `slot:${el.slotTarget},` } // scoped slots if (el.scopedSlots) { data += `${genScopedSlots(el.scopedSlots)},` } // inline-template if (el.inlineTemplate) { const inlineTemplate = genInlineTemplate(el) if (inlineTemplate) { data += `${inlineTemplate},` } } data = data.replace(/,$/, '') + '}' // v-bind data wrap if (el.wrapData) { data = el.wrapData(data) } return data } function genDirectives (el: ASTElement): string | void { const dirs = el.directives if (!dirs) return let res = 'directives:[' let hasRuntime = false let i, l, dir, needRuntime for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i] needRuntime = true const gen = platformDirectives[dir.name] || baseDirectives[dir.name] if (gen) { // compile-time directive that manipulates AST. // returns true if it also needs a runtime counterpart. needRuntime = !!gen(el, dir, warn) } if (needRuntime) { hasRuntime = true res += `{name:"${dir.name}",rawName:"${dir.rawName}"${ dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : '' }${ dir.arg ? `,arg:"${dir.arg}"` : '' }${ dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : '' }},` } } if (hasRuntime) { return res.slice(0, -1) + ']' } } function genInlineTemplate (el: ASTElement): ?string { const ast = el.children[0] if (process.env.NODE_ENV !== 'production' && ( el.children.length > 1 || ast.type !== 1 )) { warn('Inline-template components must have exactly one child element.') } if (ast.type === 1) { const inlineRenderFns = generate(ast, currentOptions) return `inlineTemplate:{render:function(){${ inlineRenderFns.render }},staticRenderFns:[${ inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',') }]}` } } function genScopedSlots (slots) { return `scopedSlots:{${ Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',') }}` } function genScopedSlot (key: string, el: ASTElement) { return `${key}:function(${String(el.attrsMap.scope)}){` + `return ${el.tag === 'template' ? genChildren(el) || 'void 0' : genElement(el) }}` } function genChildren (el: ASTElement): string | void { if (el.children.length) { return '[' + el.children.map(genNode).join(',') + ']' } } function genNode (node: ASTNode) { if (node.type === 1) { return genElement(node) } else { return genText(node) } } function genText (text: ASTText | ASTExpression): string { return text.type === 2 ? text.expression // no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text)) } function genSlot (el: ASTElement): string { const slotName = el.slotName || '"default"' const children = genChildren(el) return `_t(${slotName}${ children ? `,${children}` : '' }${ el.attrs ? `${children ? '' : ',null'},{${ el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',') }}` : '' })` } // componentName is el.component, take it as argument to shun flow's pessimistic refinement function genComponent (componentName, el): string { const children = el.inlineTemplate ? null : genChildren(el) return `_h(${componentName},${genData(el)}${ children ? `,${children}` : '' })` } function genProps (props: Array<{ name: string, value: string }>): string { let res = '' for (let i = 0; i < props.length; i++) { const prop = props[i] res += `"${prop.name}":${transformSpecialNewlines(prop.value)},` } return res.slice(0, -1) } // #3895, #4268 function transformSpecialNewlines (text: string): string { return text .replace(/u2028/g, '\u2028') .replace(/u2029/g, '\u2029') }