diff options
Diffstat (limited to 'node_modules/vue/src/compiler/codegen/index.js')
| -rw-r--r-- | node_modules/vue/src/compiler/codegen/index.js | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/node_modules/vue/src/compiler/codegen/index.js b/node_modules/vue/src/compiler/codegen/index.js new file mode 100644 index 00000000..1c912c4d --- /dev/null +++ b/node_modules/vue/src/compiler/codegen/index.js @@ -0,0 +1,513 @@ +/* @flow */ + +import { genHandlers } from './events' +import baseDirectives from '../directives/index' +import { camelize, no, extend } from 'shared/util' +import { baseWarn, pluckModuleFunction } from '../helpers' + +type TransformFunction = (el: ASTElement, code: string) => string; +type DataGenFunction = (el: ASTElement) => string; +type DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean; + +export class CodegenState { + options: CompilerOptions; + warn: Function; + transforms: Array<TransformFunction>; + dataGenFns: Array<DataGenFunction>; + directives: { [key: string]: DirectiveFunction }; + maybeComponent: (el: ASTElement) => boolean; + onceId: number; + staticRenderFns: Array<string>; + + constructor (options: CompilerOptions) { + this.options = options + this.warn = options.warn || baseWarn + this.transforms = pluckModuleFunction(options.modules, 'transformCode') + this.dataGenFns = pluckModuleFunction(options.modules, 'genData') + this.directives = extend(extend({}, baseDirectives), options.directives) + const isReservedTag = options.isReservedTag || no + this.maybeComponent = (el: ASTElement) => !isReservedTag(el.tag) + this.onceId = 0 + this.staticRenderFns = [] + } +} + +export type CodegenResult = { + render: string, + staticRenderFns: Array<string> +}; + +export function generate ( + ast: ASTElement | void, + options: CompilerOptions +): CodegenResult { + const state = new CodegenState(options) + const code = ast ? genElement(ast, state) : '_c("div")' + return { + render: `with(this){return ${code}}`, + staticRenderFns: state.staticRenderFns + } +} + +export function genElement (el: ASTElement, state: CodegenState): string { + if (el.staticRoot && !el.staticProcessed) { + return genStatic(el, state) + } else if (el.once && !el.onceProcessed) { + return genOnce(el, state) + } else if (el.for && !el.forProcessed) { + return genFor(el, state) + } else if (el.if && !el.ifProcessed) { + return genIf(el, state) + } else if (el.tag === 'template' && !el.slotTarget) { + return genChildren(el, state) || 'void 0' + } else if (el.tag === 'slot') { + return genSlot(el, state) + } else { + // component or element + let code + if (el.component) { + code = genComponent(el.component, el, state) + } else { + const data = el.plain ? undefined : genData(el, state) + + const children = el.inlineTemplate ? null : genChildren(el, state, true) + code = `_c('${el.tag}'${ + data ? `,${data}` : '' // data + }${ + children ? `,${children}` : '' // children + })` + } + // module transforms + for (let i = 0; i < state.transforms.length; i++) { + code = state.transforms[i](el, code) + } + return code + } +} + +// hoist static sub-trees out +function genStatic (el: ASTElement, state: CodegenState): string { + el.staticProcessed = true + state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`) + return `_m(${ + state.staticRenderFns.length - 1 + }${ + el.staticInFor ? ',true' : '' + })` +} + +// v-once +function genOnce (el: ASTElement, state: CodegenState): string { + el.onceProcessed = true + if (el.if && !el.ifProcessed) { + return genIf(el, state) + } 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' && state.warn( + `v-once can only be used inside v-for that is keyed. ` + ) + return genElement(el, state) + } + return `_o(${genElement(el, state)},${state.onceId++},${key})` + } else { + return genStatic(el, state) + } +} + +export function genIf ( + el: any, + state: CodegenState, + altGen?: Function, + altEmpty?: string +): string { + el.ifProcessed = true // avoid recursion + return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty) +} + +function genIfConditions ( + conditions: ASTIfConditions, + state: CodegenState, + altGen?: Function, + altEmpty?: string +): string { + if (!conditions.length) { + return altEmpty || '_e()' + } + + const condition = conditions.shift() + if (condition.exp) { + return `(${condition.exp})?${ + genTernaryExp(condition.block) + }:${ + genIfConditions(conditions, state, altGen, altEmpty) + }` + } else { + return `${genTernaryExp(condition.block)}` + } + + // v-if with v-once should generate code like (a)?_m(0):_m(1) + function genTernaryExp (el) { + return altGen + ? altGen(el, state) + : el.once + ? genOnce(el, state) + : genElement(el, state) + } +} + +export function genFor ( + el: any, + state: CodegenState, + altGen?: Function, + altHelper?: string +): string { + const exp = el.for + const alias = el.alias + const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' + const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' + + if (process.env.NODE_ENV !== 'production' && + state.maybeComponent(el) && + el.tag !== 'slot' && + el.tag !== 'template' && + !el.key + ) { + state.warn( + `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` + + `v-for should have explicit keys. ` + + `See https://vuejs.org/guide/list.html#key for more info.`, + true /* tip */ + ) + } + + el.forProcessed = true // avoid recursion + return `${altHelper || '_l'}((${exp}),` + + `function(${alias}${iterator1}${iterator2}){` + + `return ${(altGen || genElement)(el, state)}` + + '})' +} + +export function genData (el: ASTElement, state: CodegenState): string { + let data = '{' + + // directives first. + // directives may mutate the el's other properties before they are generated. + const dirs = genDirectives(el, state) + 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,` + } + // pre + if (el.pre) { + data += `pre: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 < state.dataGenFns.length; i++) { + data += state.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, false, state.warn)},` + } + if (el.nativeEvents) { + data += `${genHandlers(el.nativeEvents, true, state.warn)},` + } + // slot target + // only for non-scoped slots + if (el.slotTarget && !el.slotScope) { + data += `slot:${el.slotTarget},` + } + // scoped slots + if (el.scopedSlots) { + data += `${genScopedSlots(el.scopedSlots, state)},` + } + // component v-model + if (el.model) { + data += `model:{value:${ + el.model.value + },callback:${ + el.model.callback + },expression:${ + el.model.expression + }},` + } + // inline-template + if (el.inlineTemplate) { + const inlineTemplate = genInlineTemplate(el, state) + if (inlineTemplate) { + data += `${inlineTemplate},` + } + } + data = data.replace(/,$/, '') + '}' + // v-bind data wrap + if (el.wrapData) { + data = el.wrapData(data) + } + // v-on data wrap + if (el.wrapListeners) { + data = el.wrapListeners(data) + } + return data +} + +function genDirectives (el: ASTElement, state: CodegenState): 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: DirectiveFunction = state.directives[dir.name] + if (gen) { + // compile-time directive that manipulates AST. + // returns true if it also needs a runtime counterpart. + needRuntime = !!gen(el, dir, state.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, state: CodegenState): ?string { + const ast = el.children[0] + if (process.env.NODE_ENV !== 'production' && ( + el.children.length !== 1 || ast.type !== 1 + )) { + state.warn('Inline-template components must have exactly one child element.') + } + if (ast.type === 1) { + const inlineRenderFns = generate(ast, state.options) + return `inlineTemplate:{render:function(){${ + inlineRenderFns.render + }},staticRenderFns:[${ + inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',') + }]}` + } +} + +function genScopedSlots ( + slots: { [key: string]: ASTElement }, + state: CodegenState +): string { + return `scopedSlots:_u([${ + Object.keys(slots).map(key => { + return genScopedSlot(key, slots[key], state) + }).join(',') + }])` +} + +function genScopedSlot ( + key: string, + el: ASTElement, + state: CodegenState +): string { + if (el.for && !el.forProcessed) { + return genForScopedSlot(key, el, state) + } + const fn = `function(${String(el.slotScope)}){` + + `return ${el.tag === 'template' + ? el.if + ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined` + : genChildren(el, state) || 'undefined' + : genElement(el, state) + }}` + return `{key:${key},fn:${fn}}` +} + +function genForScopedSlot ( + key: string, + el: any, + state: CodegenState +): 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 ${genScopedSlot(key, el, state)}` + + '})' +} + +export function genChildren ( + el: ASTElement, + state: CodegenState, + checkSkip?: boolean, + altGenElement?: Function, + altGenNode?: Function +): string | void { + const children = el.children + if (children.length) { + const el: any = children[0] + // optimize single v-for + if (children.length === 1 && + el.for && + el.tag !== 'template' && + el.tag !== 'slot' + ) { + return (altGenElement || genElement)(el, state) + } + const normalizationType = checkSkip + ? getNormalizationType(children, state.maybeComponent) + : 0 + const gen = altGenNode || genNode + return `[${children.map(c => gen(c, state)).join(',')}]${ + normalizationType ? `,${normalizationType}` : '' + }` + } +} + +// determine the normalization needed for the children array. +// 0: no normalization needed +// 1: simple normalization needed (possible 1-level deep nested array) +// 2: full normalization needed +function getNormalizationType ( + children: Array<ASTNode>, + maybeComponent: (el: ASTElement) => boolean +): number { + let res = 0 + for (let i = 0; i < children.length; i++) { + const el: ASTNode = children[i] + if (el.type !== 1) { + continue + } + if (needsNormalization(el) || + (el.ifConditions && el.ifConditions.some(c => needsNormalization(c.block)))) { + res = 2 + break + } + if (maybeComponent(el) || + (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))) { + res = 1 + } + } + return res +} + +function needsNormalization (el: ASTElement): boolean { + return el.for !== undefined || el.tag === 'template' || el.tag === 'slot' +} + +function genNode (node: ASTNode, state: CodegenState): string { + if (node.type === 1) { + return genElement(node, state) + } if (node.type === 3 && node.isComment) { + return genComment(node) + } else { + return genText(node) + } +} + +export function genText (text: ASTText | ASTExpression): string { + return `_v(${text.type === 2 + ? text.expression // no need for () because already wrapped in _s() + : transformSpecialNewlines(JSON.stringify(text.text)) + })` +} + +export function genComment (comment: ASTText): string { + return `_e(${JSON.stringify(comment.text)})` +} + +function genSlot (el: ASTElement, state: CodegenState): string { + const slotName = el.slotName || '"default"' + const children = genChildren(el, state) + let res = `_t(${slotName}${children ? `,${children}` : ''}` + const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}` + const bind = el.attrsMap['v-bind'] + if ((attrs || bind) && !children) { + res += `,null` + } + if (attrs) { + res += `,${attrs}` + } + if (bind) { + res += `${attrs ? '' : ',null'},${bind}` + } + return res + ')' +} + +// componentName is el.component, take it as argument to shun flow's pessimistic refinement +function genComponent ( + componentName: string, + el: ASTElement, + state: CodegenState +): string { + const children = el.inlineTemplate ? null : genChildren(el, state, true) + return `_c(${componentName},${genData(el, state)}${ + children ? `,${children}` : '' + })` +} + +function genProps (props: Array<{ name: string, value: any }>): string { + let res = '' + for (let i = 0; i < props.length; i++) { + const prop = props[i] + /* istanbul ignore if */ + if (__WEEX__) { + res += `"${prop.name}":${generateValue(prop.value)},` + } else { + res += `"${prop.name}":${transformSpecialNewlines(prop.value)},` + } + } + return res.slice(0, -1) +} + +/* istanbul ignore next */ +function generateValue (value) { + if (typeof value === 'string') { + return transformSpecialNewlines(value) + } + return JSON.stringify(value) +} + +// #3895, #4268 +function transformSpecialNewlines (text: string): string { + return text + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') +} |
