diff options
Diffstat (limited to 'node_modules/vue/src/compiler/codegen')
| -rw-r--r-- | node_modules/vue/src/compiler/codegen/events.js | 167 | ||||
| -rw-r--r-- | node_modules/vue/src/compiler/codegen/index.js | 513 |
2 files changed, 680 insertions, 0 deletions
diff --git a/node_modules/vue/src/compiler/codegen/events.js b/node_modules/vue/src/compiler/codegen/events.js new file mode 100644 index 00000000..4f922246 --- /dev/null +++ b/node_modules/vue/src/compiler/codegen/events.js @@ -0,0 +1,167 @@ +/* @flow */ + +const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/ +const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/ + +// KeyboardEvent.keyCode aliases +const keyCodes: { [key: string]: number | Array<number> } = { + esc: 27, + tab: 9, + enter: 13, + space: 32, + up: 38, + left: 37, + right: 39, + down: 40, + 'delete': [8, 46] +} + +// KeyboardEvent.key aliases +const keyNames: { [key: string]: string | Array<string> } = { + esc: 'Escape', + tab: 'Tab', + enter: 'Enter', + space: ' ', + // #7806: IE11 uses key names without `Arrow` prefix for arrow keys. + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + 'delete': ['Backspace', 'Delete'] +} + +// #4868: modifiers that prevent the execution of the listener +// need to explicitly return null so that we can determine whether to remove +// the listener for .once +const genGuard = condition => `if(${condition})return null;` + +const modifierCode: { [key: string]: string } = { + stop: '$event.stopPropagation();', + prevent: '$event.preventDefault();', + self: genGuard(`$event.target !== $event.currentTarget`), + ctrl: genGuard(`!$event.ctrlKey`), + shift: genGuard(`!$event.shiftKey`), + alt: genGuard(`!$event.altKey`), + meta: genGuard(`!$event.metaKey`), + left: genGuard(`'button' in $event && $event.button !== 0`), + middle: genGuard(`'button' in $event && $event.button !== 1`), + right: genGuard(`'button' in $event && $event.button !== 2`) +} + +export function genHandlers ( + events: ASTElementHandlers, + isNative: boolean, + warn: Function +): string { + let res = isNative ? 'nativeOn:{' : 'on:{' + for (const name in events) { + res += `"${name}":${genHandler(name, events[name])},` + } + return res.slice(0, -1) + '}' +} + +// Generate handler code with binding params on Weex +/* istanbul ignore next */ +function genWeexHandler (params: Array<any>, handlerCode: string) { + let innerHandlerCode = handlerCode + const exps = params.filter(exp => simplePathRE.test(exp) && exp !== '$event') + const bindings = exps.map(exp => ({ '@binding': exp })) + const args = exps.map((exp, i) => { + const key = `$_${i + 1}` + innerHandlerCode = innerHandlerCode.replace(exp, key) + return key + }) + args.push('$event') + return '{\n' + + `handler:function(${args.join(',')}){${innerHandlerCode}},\n` + + `params:${JSON.stringify(bindings)}\n` + + '}' +} + +function genHandler ( + name: string, + handler: ASTElementHandler | Array<ASTElementHandler> +): string { + if (!handler) { + return 'function(){}' + } + + if (Array.isArray(handler)) { + return `[${handler.map(handler => genHandler(name, handler)).join(',')}]` + } + + const isMethodPath = simplePathRE.test(handler.value) + const isFunctionExpression = fnExpRE.test(handler.value) + + if (!handler.modifiers) { + if (isMethodPath || isFunctionExpression) { + return handler.value + } + /* istanbul ignore if */ + if (__WEEX__ && handler.params) { + return genWeexHandler(handler.params, handler.value) + } + return `function($event){${handler.value}}` // inline statement + } else { + let code = '' + let genModifierCode = '' + const keys = [] + for (const key in handler.modifiers) { + if (modifierCode[key]) { + genModifierCode += modifierCode[key] + // left/right + if (keyCodes[key]) { + keys.push(key) + } + } else if (key === 'exact') { + const modifiers: ASTModifiers = (handler.modifiers: any) + genModifierCode += genGuard( + ['ctrl', 'shift', 'alt', 'meta'] + .filter(keyModifier => !modifiers[keyModifier]) + .map(keyModifier => `$event.${keyModifier}Key`) + .join('||') + ) + } else { + keys.push(key) + } + } + if (keys.length) { + code += genKeyFilter(keys) + } + // Make sure modifiers like prevent and stop get executed after key filtering + if (genModifierCode) { + code += genModifierCode + } + const handlerCode = isMethodPath + ? `return ${handler.value}($event)` + : isFunctionExpression + ? `return (${handler.value})($event)` + : handler.value + /* istanbul ignore if */ + if (__WEEX__ && handler.params) { + return genWeexHandler(handler.params, code + handlerCode) + } + return `function($event){${code}${handlerCode}}` + } +} + +function genKeyFilter (keys: Array<string>): string { + return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;` +} + +function genFilterCode (key: string): string { + const keyVal = parseInt(key, 10) + if (keyVal) { + return `$event.keyCode!==${keyVal}` + } + const keyCode = keyCodes[key] + const keyName = keyNames[key] + return ( + `_k($event.keyCode,` + + `${JSON.stringify(key)},` + + `${JSON.stringify(keyCode)},` + + `$event.key,` + + `${JSON.stringify(keyName)}` + + `)` + ) +} 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') +} |
