diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/compiler | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/compiler')
17 files changed, 2585 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') +} diff --git a/node_modules/vue/src/compiler/create-compiler.js b/node_modules/vue/src/compiler/create-compiler.js new file mode 100644 index 00000000..6671dcfe --- /dev/null +++ b/node_modules/vue/src/compiler/create-compiler.js @@ -0,0 +1,55 @@ +/* @flow */ + +import { extend } from 'shared/util' +import { detectErrors } from './error-detector' +import { createCompileToFunctionFn } from './to-function' + +export function createCompilerCreator (baseCompile: Function): Function { + return function createCompiler (baseOptions: CompilerOptions) { + function compile ( + template: string, + options?: CompilerOptions + ): CompiledResult { + const finalOptions = Object.create(baseOptions) + const errors = [] + const tips = [] + finalOptions.warn = (msg, tip) => { + (tip ? tips : errors).push(msg) + } + + if (options) { + // merge custom modules + if (options.modules) { + finalOptions.modules = + (baseOptions.modules || []).concat(options.modules) + } + // merge custom directives + if (options.directives) { + finalOptions.directives = extend( + Object.create(baseOptions.directives || null), + options.directives + ) + } + // copy other options + for (const key in options) { + if (key !== 'modules' && key !== 'directives') { + finalOptions[key] = options[key] + } + } + } + + const compiled = baseCompile(template, finalOptions) + if (process.env.NODE_ENV !== 'production') { + errors.push.apply(errors, detectErrors(compiled.ast)) + } + compiled.errors = errors + compiled.tips = tips + return compiled + } + + return { + compile, + compileToFunctions: createCompileToFunctionFn(compile) + } + } +} diff --git a/node_modules/vue/src/compiler/directives/bind.js b/node_modules/vue/src/compiler/directives/bind.js new file mode 100644 index 00000000..e1035727 --- /dev/null +++ b/node_modules/vue/src/compiler/directives/bind.js @@ -0,0 +1,11 @@ +/* @flow */ + +export default function bind (el: ASTElement, dir: ASTDirective) { + el.wrapData = (code: string) => { + return `_b(${code},'${el.tag}',${dir.value},${ + dir.modifiers && dir.modifiers.prop ? 'true' : 'false' + }${ + dir.modifiers && dir.modifiers.sync ? ',true' : '' + })` + } +} diff --git a/node_modules/vue/src/compiler/directives/index.js b/node_modules/vue/src/compiler/directives/index.js new file mode 100644 index 00000000..8f4342c0 --- /dev/null +++ b/node_modules/vue/src/compiler/directives/index.js @@ -0,0 +1,11 @@ +/* @flow */ + +import on from './on' +import bind from './bind' +import { noop } from 'shared/util' + +export default { + on, + bind, + cloak: noop +} diff --git a/node_modules/vue/src/compiler/directives/model.js b/node_modules/vue/src/compiler/directives/model.js new file mode 100644 index 00000000..39bdbfda --- /dev/null +++ b/node_modules/vue/src/compiler/directives/model.js @@ -0,0 +1,148 @@ +/* @flow */ + +/** + * Cross-platform code generation for component v-model + */ +export function genComponentModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const { number, trim } = modifiers || {} + + const baseValueExpression = '$$v' + let valueExpression = baseValueExpression + if (trim) { + valueExpression = + `(typeof ${baseValueExpression} === 'string'` + + `? ${baseValueExpression}.trim()` + + `: ${baseValueExpression})` + } + if (number) { + valueExpression = `_n(${valueExpression})` + } + const assignment = genAssignmentCode(value, valueExpression) + + el.model = { + value: `(${value})`, + expression: `"${value}"`, + callback: `function (${baseValueExpression}) {${assignment}}` + } +} + +/** + * Cross-platform codegen helper for generating v-model value assignment code. + */ +export function genAssignmentCode ( + value: string, + assignment: string +): string { + const res = parseModel(value) + if (res.key === null) { + return `${value}=${assignment}` + } else { + return `$set(${res.exp}, ${res.key}, ${assignment})` + } +} + +/** + * Parse a v-model expression into a base path and a final key segment. + * Handles both dot-path and possible square brackets. + * + * Possible cases: + * + * - test + * - test[key] + * - test[test1[key]] + * - test["a"][key] + * - xxx.test[a[a].test1[key]] + * - test.xxx.a["asa"][test1[key]] + * + */ + +let len, str, chr, index, expressionPos, expressionEndPos + +type ModelParseResult = { + exp: string, + key: string | null +} + +export function parseModel (val: string): ModelParseResult { + // Fix https://github.com/vuejs/vue/pull/7730 + // allow v-model="obj.val " (trailing whitespace) + val = val.trim() + len = val.length + + if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { + index = val.lastIndexOf('.') + if (index > -1) { + return { + exp: val.slice(0, index), + key: '"' + val.slice(index + 1) + '"' + } + } else { + return { + exp: val, + key: null + } + } + } + + str = val + index = expressionPos = expressionEndPos = 0 + + while (!eof()) { + chr = next() + /* istanbul ignore if */ + if (isStringStart(chr)) { + parseString(chr) + } else if (chr === 0x5B) { + parseBracket(chr) + } + } + + return { + exp: val.slice(0, expressionPos), + key: val.slice(expressionPos + 1, expressionEndPos) + } +} + +function next (): number { + return str.charCodeAt(++index) +} + +function eof (): boolean { + return index >= len +} + +function isStringStart (chr: number): boolean { + return chr === 0x22 || chr === 0x27 +} + +function parseBracket (chr: number): void { + let inBracket = 1 + expressionPos = index + while (!eof()) { + chr = next() + if (isStringStart(chr)) { + parseString(chr) + continue + } + if (chr === 0x5B) inBracket++ + if (chr === 0x5D) inBracket-- + if (inBracket === 0) { + expressionEndPos = index + break + } + } +} + +function parseString (chr: number): void { + const stringQuote = chr + while (!eof()) { + chr = next() + if (chr === stringQuote) { + break + } + } +} diff --git a/node_modules/vue/src/compiler/directives/on.js b/node_modules/vue/src/compiler/directives/on.js new file mode 100644 index 00000000..893a678e --- /dev/null +++ b/node_modules/vue/src/compiler/directives/on.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { warn } from 'core/util/index' + +export default function on (el: ASTElement, dir: ASTDirective) { + if (process.env.NODE_ENV !== 'production' && dir.modifiers) { + warn(`v-on without argument does not support modifiers.`) + } + el.wrapListeners = (code: string) => `_g(${code},${dir.value})` +} diff --git a/node_modules/vue/src/compiler/error-detector.js b/node_modules/vue/src/compiler/error-detector.js new file mode 100644 index 00000000..e729c499 --- /dev/null +++ b/node_modules/vue/src/compiler/error-detector.js @@ -0,0 +1,108 @@ +/* @flow */ + +import { dirRE, onRE } from './parser/index' + +// these keywords should not appear inside expressions, but operators like +// typeof, instanceof and in are allowed +const prohibitedKeywordRE = new RegExp('\\b' + ( + 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + + 'super,throw,while,yield,delete,export,import,return,switch,default,' + + 'extends,finally,continue,debugger,function,arguments' +).split(',').join('\\b|\\b') + '\\b') + +// these unary operators should not be used as property/method names +const unaryOperatorsRE = new RegExp('\\b' + ( + 'delete,typeof,void' +).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') + +// strip strings in expressions +const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g + +// detect problematic expressions in a template +export function detectErrors (ast: ?ASTNode): Array<string> { + const errors: Array<string> = [] + if (ast) { + checkNode(ast, errors) + } + return errors +} + +function checkNode (node: ASTNode, errors: Array<string>) { + if (node.type === 1) { + for (const name in node.attrsMap) { + if (dirRE.test(name)) { + const value = node.attrsMap[name] + if (value) { + if (name === 'v-for') { + checkFor(node, `v-for="${value}"`, errors) + } else if (onRE.test(name)) { + checkEvent(value, `${name}="${value}"`, errors) + } else { + checkExpression(value, `${name}="${value}"`, errors) + } + } + } + } + if (node.children) { + for (let i = 0; i < node.children.length; i++) { + checkNode(node.children[i], errors) + } + } + } else if (node.type === 2) { + checkExpression(node.expression, node.text, errors) + } +} + +function checkEvent (exp: string, text: string, errors: Array<string>) { + const stipped = exp.replace(stripStringRE, '') + const keywordMatch: any = stipped.match(unaryOperatorsRE) + if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { + errors.push( + `avoid using JavaScript unary operator as property name: ` + + `"${keywordMatch[0]}" in expression ${text.trim()}` + ) + } + checkExpression(exp, text, errors) +} + +function checkFor (node: ASTElement, text: string, errors: Array<string>) { + checkExpression(node.for || '', text, errors) + checkIdentifier(node.alias, 'v-for alias', text, errors) + checkIdentifier(node.iterator1, 'v-for iterator', text, errors) + checkIdentifier(node.iterator2, 'v-for iterator', text, errors) +} + +function checkIdentifier ( + ident: ?string, + type: string, + text: string, + errors: Array<string> +) { + if (typeof ident === 'string') { + try { + new Function(`var ${ident}=_`) + } catch (e) { + errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) + } + } +} + +function checkExpression (exp: string, text: string, errors: Array<string>) { + try { + new Function(`return ${exp}`) + } catch (e) { + const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) + if (keywordMatch) { + errors.push( + `avoid using JavaScript keyword as property name: ` + + `"${keywordMatch[0]}"\n Raw expression: ${text.trim()}` + ) + } else { + errors.push( + `invalid expression: ${e.message} in\n\n` + + ` ${exp}\n\n` + + ` Raw expression: ${text.trim()}\n` + ) + } + } +} diff --git a/node_modules/vue/src/compiler/helpers.js b/node_modules/vue/src/compiler/helpers.js new file mode 100644 index 00000000..6a7375a3 --- /dev/null +++ b/node_modules/vue/src/compiler/helpers.js @@ -0,0 +1,164 @@ +/* @flow */ + +import { emptyObject } from 'shared/util' +import { parseFilters } from './parser/filter-parser' + +export function baseWarn (msg: string) { + console.error(`[Vue compiler]: ${msg}`) +} + +export function pluckModuleFunction<F: Function> ( + modules: ?Array<Object>, + key: string +): Array<F> { + return modules + ? modules.map(m => m[key]).filter(_ => _) + : [] +} + +export function addProp (el: ASTElement, name: string, value: string) { + (el.props || (el.props = [])).push({ name, value }) + el.plain = false +} + +export function addAttr (el: ASTElement, name: string, value: any) { + (el.attrs || (el.attrs = [])).push({ name, value }) + el.plain = false +} + +// add a raw attr (use this in preTransforms) +export function addRawAttr (el: ASTElement, name: string, value: any) { + el.attrsMap[name] = value + el.attrsList.push({ name, value }) +} + +export function addDirective ( + el: ASTElement, + name: string, + rawName: string, + value: string, + arg: ?string, + modifiers: ?ASTModifiers +) { + (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers }) + el.plain = false +} + +export function addHandler ( + el: ASTElement, + name: string, + value: string, + modifiers: ?ASTModifiers, + important?: boolean, + warn?: Function +) { + modifiers = modifiers || emptyObject + // warn prevent and passive modifier + /* istanbul ignore if */ + if ( + process.env.NODE_ENV !== 'production' && warn && + modifiers.prevent && modifiers.passive + ) { + warn( + 'passive and prevent can\'t be used together. ' + + 'Passive handler can\'t prevent default event.' + ) + } + + // check capture modifier + if (modifiers.capture) { + delete modifiers.capture + name = '!' + name // mark the event as captured + } + if (modifiers.once) { + delete modifiers.once + name = '~' + name // mark the event as once + } + /* istanbul ignore if */ + if (modifiers.passive) { + delete modifiers.passive + name = '&' + name // mark the event as passive + } + + // normalize click.right and click.middle since they don't actually fire + // this is technically browser-specific, but at least for now browsers are + // the only target envs that have right/middle clicks. + if (name === 'click') { + if (modifiers.right) { + name = 'contextmenu' + delete modifiers.right + } else if (modifiers.middle) { + name = 'mouseup' + } + } + + let events + if (modifiers.native) { + delete modifiers.native + events = el.nativeEvents || (el.nativeEvents = {}) + } else { + events = el.events || (el.events = {}) + } + + const newHandler: any = { + value: value.trim() + } + if (modifiers !== emptyObject) { + newHandler.modifiers = modifiers + } + + const handlers = events[name] + /* istanbul ignore if */ + if (Array.isArray(handlers)) { + important ? handlers.unshift(newHandler) : handlers.push(newHandler) + } else if (handlers) { + events[name] = important ? [newHandler, handlers] : [handlers, newHandler] + } else { + events[name] = newHandler + } + + el.plain = false +} + +export function getBindingAttr ( + el: ASTElement, + name: string, + getStatic?: boolean +): ?string { + const dynamicValue = + getAndRemoveAttr(el, ':' + name) || + getAndRemoveAttr(el, 'v-bind:' + name) + if (dynamicValue != null) { + return parseFilters(dynamicValue) + } else if (getStatic !== false) { + const staticValue = getAndRemoveAttr(el, name) + if (staticValue != null) { + return JSON.stringify(staticValue) + } + } +} + +// note: this only removes the attr from the Array (attrsList) so that it +// doesn't get processed by processAttrs. +// By default it does NOT remove it from the map (attrsMap) because the map is +// needed during codegen. +export function getAndRemoveAttr ( + el: ASTElement, + name: string, + removeFromMap?: boolean +): ?string { + let val + if ((val = el.attrsMap[name]) != null) { + const list = el.attrsList + for (let i = 0, l = list.length; i < l; i++) { + if (list[i].name === name) { + list.splice(i, 1) + break + } + } + } + if (removeFromMap) { + delete el.attrsMap[name] + } + return val +} diff --git a/node_modules/vue/src/compiler/index.js b/node_modules/vue/src/compiler/index.js new file mode 100644 index 00000000..56b54fd3 --- /dev/null +++ b/node_modules/vue/src/compiler/index.js @@ -0,0 +1,25 @@ +/* @flow */ + +import { parse } from './parser/index' +import { optimize } from './optimizer' +import { generate } from './codegen/index' +import { createCompilerCreator } from './create-compiler' + +// `createCompilerCreator` allows creating compilers that use alternative +// parser/optimizer/codegen, e.g the SSR optimizing compiler. +// Here we just export a default compiler using the default parts. +export const createCompiler = createCompilerCreator(function baseCompile ( + template: string, + options: CompilerOptions +): CompiledResult { + const ast = parse(template.trim(), options) + if (options.optimize !== false) { + optimize(ast, options) + } + const code = generate(ast, options) + return { + ast, + render: code.render, + staticRenderFns: code.staticRenderFns + } +}) diff --git a/node_modules/vue/src/compiler/optimizer.js b/node_modules/vue/src/compiler/optimizer.js new file mode 100644 index 00000000..ce432035 --- /dev/null +++ b/node_modules/vue/src/compiler/optimizer.js @@ -0,0 +1,128 @@ +/* @flow */ + +import { makeMap, isBuiltInTag, cached, no } from 'shared/util' + +let isStaticKey +let isPlatformReservedTag + +const genStaticKeysCached = cached(genStaticKeys) + +/** + * Goal of the optimizer: walk the generated template AST tree + * and detect sub-trees that are purely static, i.e. parts of + * the DOM that never needs to change. + * + * Once we detect these sub-trees, we can: + * + * 1. Hoist them into constants, so that we no longer need to + * create fresh nodes for them on each re-render; + * 2. Completely skip them in the patching process. + */ +export function optimize (root: ?ASTElement, options: CompilerOptions) { + if (!root) return + isStaticKey = genStaticKeysCached(options.staticKeys || '') + isPlatformReservedTag = options.isReservedTag || no + // first pass: mark all non-static nodes. + markStatic(root) + // second pass: mark static roots. + markStaticRoots(root, false) +} + +function genStaticKeys (keys: string): Function { + return makeMap( + 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + + (keys ? ',' + keys : '') + ) +} + +function markStatic (node: ASTNode) { + node.static = isStatic(node) + if (node.type === 1) { + // do not make component slot content static. this avoids + // 1. components not able to mutate slot nodes + // 2. static slot content fails for hot-reloading + if ( + !isPlatformReservedTag(node.tag) && + node.tag !== 'slot' && + node.attrsMap['inline-template'] == null + ) { + return + } + for (let i = 0, l = node.children.length; i < l; i++) { + const child = node.children[i] + markStatic(child) + if (!child.static) { + node.static = false + } + } + if (node.ifConditions) { + for (let i = 1, l = node.ifConditions.length; i < l; i++) { + const block = node.ifConditions[i].block + markStatic(block) + if (!block.static) { + node.static = false + } + } + } + } +} + +function markStaticRoots (node: ASTNode, isInFor: boolean) { + if (node.type === 1) { + if (node.static || node.once) { + node.staticInFor = isInFor + } + // For a node to qualify as a static root, it should have children that + // are not just static text. Otherwise the cost of hoisting out will + // outweigh the benefits and it's better off to just always render it fresh. + if (node.static && node.children.length && !( + node.children.length === 1 && + node.children[0].type === 3 + )) { + node.staticRoot = true + return + } else { + node.staticRoot = false + } + if (node.children) { + for (let i = 0, l = node.children.length; i < l; i++) { + markStaticRoots(node.children[i], isInFor || !!node.for) + } + } + if (node.ifConditions) { + for (let i = 1, l = node.ifConditions.length; i < l; i++) { + markStaticRoots(node.ifConditions[i].block, isInFor) + } + } + } +} + +function isStatic (node: ASTNode): boolean { + if (node.type === 2) { // expression + return false + } + if (node.type === 3) { // text + return true + } + return !!(node.pre || ( + !node.hasBindings && // no dynamic bindings + !node.if && !node.for && // not v-if or v-for or v-else + !isBuiltInTag(node.tag) && // not a built-in + isPlatformReservedTag(node.tag) && // not a component + !isDirectChildOfTemplateFor(node) && + Object.keys(node).every(isStaticKey) + )) +} + +function isDirectChildOfTemplateFor (node: ASTElement): boolean { + while (node.parent) { + node = node.parent + if (node.tag !== 'template') { + return false + } + if (node.for) { + return true + } + } + return false +} diff --git a/node_modules/vue/src/compiler/parser/entity-decoder.js b/node_modules/vue/src/compiler/parser/entity-decoder.js new file mode 100644 index 00000000..2836294f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/entity-decoder.js @@ -0,0 +1,11 @@ +/* @flow */ + +let decoder + +export default { + decode (html: string): string { + decoder = decoder || document.createElement('div') + decoder.innerHTML = html + return decoder.textContent + } +} diff --git a/node_modules/vue/src/compiler/parser/filter-parser.js b/node_modules/vue/src/compiler/parser/filter-parser.js new file mode 100644 index 00000000..10939d3f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/filter-parser.js @@ -0,0 +1,97 @@ +/* @flow */ + +const validDivisionCharRE = /[\w).+\-_$\]]/ + +export function parseFilters (exp: string): string { + let inSingle = false + let inDouble = false + let inTemplateString = false + let inRegex = false + let curly = 0 + let square = 0 + let paren = 0 + let lastFilterIndex = 0 + let c, prev, i, expression, filters + + for (i = 0; i < exp.length; i++) { + prev = c + c = exp.charCodeAt(i) + if (inSingle) { + if (c === 0x27 && prev !== 0x5C) inSingle = false + } else if (inDouble) { + if (c === 0x22 && prev !== 0x5C) inDouble = false + } else if (inTemplateString) { + if (c === 0x60 && prev !== 0x5C) inTemplateString = false + } else if (inRegex) { + if (c === 0x2f && prev !== 0x5C) inRegex = false + } else if ( + c === 0x7C && // pipe + exp.charCodeAt(i + 1) !== 0x7C && + exp.charCodeAt(i - 1) !== 0x7C && + !curly && !square && !paren + ) { + if (expression === undefined) { + // first filter, end of expression + lastFilterIndex = i + 1 + expression = exp.slice(0, i).trim() + } else { + pushFilter() + } + } else { + switch (c) { + case 0x22: inDouble = true; break // " + case 0x27: inSingle = true; break // ' + case 0x60: inTemplateString = true; break // ` + case 0x28: paren++; break // ( + case 0x29: paren--; break // ) + case 0x5B: square++; break // [ + case 0x5D: square--; break // ] + case 0x7B: curly++; break // { + case 0x7D: curly--; break // } + } + if (c === 0x2f) { // / + let j = i - 1 + let p + // find first non-whitespace prev char + for (; j >= 0; j--) { + p = exp.charAt(j) + if (p !== ' ') break + } + if (!p || !validDivisionCharRE.test(p)) { + inRegex = true + } + } + } + } + + if (expression === undefined) { + expression = exp.slice(0, i).trim() + } else if (lastFilterIndex !== 0) { + pushFilter() + } + + function pushFilter () { + (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) + lastFilterIndex = i + 1 + } + + if (filters) { + for (i = 0; i < filters.length; i++) { + expression = wrapFilter(expression, filters[i]) + } + } + + return expression +} + +function wrapFilter (exp: string, filter: string): string { + const i = filter.indexOf('(') + if (i < 0) { + // _f: resolveFilter + return `_f("${filter}")(${exp})` + } else { + const name = filter.slice(0, i) + const args = filter.slice(i + 1) + return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` + } +} diff --git a/node_modules/vue/src/compiler/parser/html-parser.js b/node_modules/vue/src/compiler/parser/html-parser.js new file mode 100644 index 00000000..e2203613 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/html-parser.js @@ -0,0 +1,310 @@ +/** + * Not type-checking this file because it's mostly vendor code. + */ + +/*! + * HTML Parser By John Resig (ejohn.org) + * Modified by Juriy "kangax" Zaytsev + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + */ + +import { makeMap, no } from 'shared/util' +import { isNonPhrasingTag } from 'web/compiler/util' + +// Regular Expressions for parsing tags and attributes +const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ +// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName +// but for Vue templates we can enforce a simple charset +const ncname = '[a-zA-Z_][\\w\\-\\.]*' +const qnameCapture = `((?:${ncname}\\:)?${ncname})` +const startTagOpen = new RegExp(`^<${qnameCapture}`) +const startTagClose = /^\s*(\/?)>/ +const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) +const doctype = /^<!DOCTYPE [^>]+>/i +// #7298: escape - to avoid being pased as HTML comment when inlined in page +const comment = /^<!\--/ +const conditionalComment = /^<!\[/ + +let IS_REGEX_CAPTURING_BROKEN = false +'x'.replace(/x(.)?/g, function (m, g) { + IS_REGEX_CAPTURING_BROKEN = g === '' +}) + +// Special Elements (can contain anything) +export const isPlainTextElement = makeMap('script,style,textarea', true) +const reCache = {} + +const decodingMap = { + '<': '<', + '>': '>', + '"': '"', + '&': '&', + ' ': '\n', + '	': '\t' +} +const encodedAttr = /&(?:lt|gt|quot|amp);/g +const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g + +// #5992 +const isIgnoreNewlineTag = makeMap('pre,textarea', true) +const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n' + +function decodeAttr (value, shouldDecodeNewlines) { + const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr + return value.replace(re, match => decodingMap[match]) +} + +export function parseHTML (html, options) { + const stack = [] + const expectHTML = options.expectHTML + const isUnaryTag = options.isUnaryTag || no + const canBeLeftOpenTag = options.canBeLeftOpenTag || no + let index = 0 + let last, lastTag + while (html) { + last = html + // Make sure we're not in a plaintext content element like script/style + if (!lastTag || !isPlainTextElement(lastTag)) { + let textEnd = html.indexOf('<') + if (textEnd === 0) { + // Comment: + if (comment.test(html)) { + const commentEnd = html.indexOf('-->') + + if (commentEnd >= 0) { + if (options.shouldKeepComment) { + options.comment(html.substring(4, commentEnd)) + } + advance(commentEnd + 3) + continue + } + } + + // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment + if (conditionalComment.test(html)) { + const conditionalEnd = html.indexOf(']>') + + if (conditionalEnd >= 0) { + advance(conditionalEnd + 2) + continue + } + } + + // Doctype: + const doctypeMatch = html.match(doctype) + if (doctypeMatch) { + advance(doctypeMatch[0].length) + continue + } + + // End tag: + const endTagMatch = html.match(endTag) + if (endTagMatch) { + const curIndex = index + advance(endTagMatch[0].length) + parseEndTag(endTagMatch[1], curIndex, index) + continue + } + + // Start tag: + const startTagMatch = parseStartTag() + if (startTagMatch) { + handleStartTag(startTagMatch) + if (shouldIgnoreFirstNewline(lastTag, html)) { + advance(1) + } + continue + } + } + + let text, rest, next + if (textEnd >= 0) { + rest = html.slice(textEnd) + while ( + !endTag.test(rest) && + !startTagOpen.test(rest) && + !comment.test(rest) && + !conditionalComment.test(rest) + ) { + // < in plain text, be forgiving and treat it as text + next = rest.indexOf('<', 1) + if (next < 0) break + textEnd += next + rest = html.slice(textEnd) + } + text = html.substring(0, textEnd) + advance(textEnd) + } + + if (textEnd < 0) { + text = html + html = '' + } + + if (options.chars && text) { + options.chars(text) + } + } else { + let endTagLength = 0 + const stackedTag = lastTag.toLowerCase() + const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')) + const rest = html.replace(reStackedTag, function (all, text, endTag) { + endTagLength = endTag.length + if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { + text = text + .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298 + .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1') + } + if (shouldIgnoreFirstNewline(stackedTag, text)) { + text = text.slice(1) + } + if (options.chars) { + options.chars(text) + } + return '' + }) + index += html.length - rest.length + html = rest + parseEndTag(stackedTag, index - endTagLength, index) + } + + if (html === last) { + options.chars && options.chars(html) + if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) { + options.warn(`Mal-formatted tag at end of template: "${html}"`) + } + break + } + } + + // Clean up any remaining tags + parseEndTag() + + function advance (n) { + index += n + html = html.substring(n) + } + + function parseStartTag () { + const start = html.match(startTagOpen) + if (start) { + const match = { + tagName: start[1], + attrs: [], + start: index + } + advance(start[0].length) + let end, attr + while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { + advance(attr[0].length) + match.attrs.push(attr) + } + if (end) { + match.unarySlash = end[1] + advance(end[0].length) + match.end = index + return match + } + } + } + + function handleStartTag (match) { + const tagName = match.tagName + const unarySlash = match.unarySlash + + if (expectHTML) { + if (lastTag === 'p' && isNonPhrasingTag(tagName)) { + parseEndTag(lastTag) + } + if (canBeLeftOpenTag(tagName) && lastTag === tagName) { + parseEndTag(tagName) + } + } + + const unary = isUnaryTag(tagName) || !!unarySlash + + const l = match.attrs.length + const attrs = new Array(l) + for (let i = 0; i < l; i++) { + const args = match.attrs[i] + // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 + if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) { + if (args[3] === '') { delete args[3] } + if (args[4] === '') { delete args[4] } + if (args[5] === '') { delete args[5] } + } + const value = args[3] || args[4] || args[5] || '' + const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' + ? options.shouldDecodeNewlinesForHref + : options.shouldDecodeNewlines + attrs[i] = { + name: args[1], + value: decodeAttr(value, shouldDecodeNewlines) + } + } + + if (!unary) { + stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }) + lastTag = tagName + } + + if (options.start) { + options.start(tagName, attrs, unary, match.start, match.end) + } + } + + function parseEndTag (tagName, start, end) { + let pos, lowerCasedTagName + if (start == null) start = index + if (end == null) end = index + + if (tagName) { + lowerCasedTagName = tagName.toLowerCase() + } + + // Find the closest opened tag of the same type + if (tagName) { + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos].lowerCasedTag === lowerCasedTagName) { + break + } + } + } else { + // If no tag name is provided, clean shop + pos = 0 + } + + if (pos >= 0) { + // Close all the open elements, up the stack + for (let i = stack.length - 1; i >= pos; i--) { + if (process.env.NODE_ENV !== 'production' && + (i > pos || !tagName) && + options.warn + ) { + options.warn( + `tag <${stack[i].tag}> has no matching end tag.` + ) + } + if (options.end) { + options.end(stack[i].tag, start, end) + } + } + + // Remove the open elements from the stack + stack.length = pos + lastTag = pos && stack[pos - 1].tag + } else if (lowerCasedTagName === 'br') { + if (options.start) { + options.start(tagName, [], true, start, end) + } + } else if (lowerCasedTagName === 'p') { + if (options.start) { + options.start(tagName, [], false, start, end) + } + if (options.end) { + options.end(tagName, start, end) + } + } + } +} diff --git a/node_modules/vue/src/compiler/parser/index.js b/node_modules/vue/src/compiler/parser/index.js new file mode 100644 index 00000000..a603b36b --- /dev/null +++ b/node_modules/vue/src/compiler/parser/index.js @@ -0,0 +1,675 @@ +/* @flow */ + +import he from 'he' +import { parseHTML } from './html-parser' +import { parseText } from './text-parser' +import { parseFilters } from './filter-parser' +import { genAssignmentCode } from '../directives/model' +import { extend, cached, no, camelize } from 'shared/util' +import { isIE, isEdge, isServerRendering } from 'core/util/env' + +import { + addProp, + addAttr, + baseWarn, + addHandler, + addDirective, + getBindingAttr, + getAndRemoveAttr, + pluckModuleFunction +} from '../helpers' + +export const onRE = /^@|^v-on:/ +export const dirRE = /^v-|^@|^:/ +export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/ +export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ +const stripParensRE = /^\(|\)$/g + +const argRE = /:(.*)$/ +export const bindRE = /^:|^v-bind:/ +const modifierRE = /\.[^.]+/g + +const decodeHTMLCached = cached(he.decode) + +// configurable state +export let warn: any +let delimiters +let transforms +let preTransforms +let postTransforms +let platformIsPreTag +let platformMustUseProp +let platformGetTagNamespace + +type Attr = { name: string; value: string }; + +export function createASTElement ( + tag: string, + attrs: Array<Attr>, + parent: ASTElement | void +): ASTElement { + return { + type: 1, + tag, + attrsList: attrs, + attrsMap: makeAttrsMap(attrs), + parent, + children: [] + } +} + +/** + * Convert HTML string to AST. + */ +export function parse ( + template: string, + options: CompilerOptions +): ASTElement | void { + warn = options.warn || baseWarn + + platformIsPreTag = options.isPreTag || no + platformMustUseProp = options.mustUseProp || no + platformGetTagNamespace = options.getTagNamespace || no + + transforms = pluckModuleFunction(options.modules, 'transformNode') + preTransforms = pluckModuleFunction(options.modules, 'preTransformNode') + postTransforms = pluckModuleFunction(options.modules, 'postTransformNode') + + delimiters = options.delimiters + + const stack = [] + const preserveWhitespace = options.preserveWhitespace !== false + let root + let currentParent + let inVPre = false + let inPre = false + let warned = false + + function warnOnce (msg) { + if (!warned) { + warned = true + warn(msg) + } + } + + function closeElement (element) { + // check pre state + if (element.pre) { + inVPre = false + } + if (platformIsPreTag(element.tag)) { + inPre = false + } + // apply post-transforms + for (let i = 0; i < postTransforms.length; i++) { + postTransforms[i](element, options) + } + } + + parseHTML(template, { + warn, + expectHTML: options.expectHTML, + isUnaryTag: options.isUnaryTag, + canBeLeftOpenTag: options.canBeLeftOpenTag, + shouldDecodeNewlines: options.shouldDecodeNewlines, + shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, + shouldKeepComment: options.comments, + start (tag, attrs, unary) { + // check namespace. + // inherit parent ns if there is one + const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag) + + // handle IE svg bug + /* istanbul ignore if */ + if (isIE && ns === 'svg') { + attrs = guardIESVGBug(attrs) + } + + let element: ASTElement = createASTElement(tag, attrs, currentParent) + if (ns) { + element.ns = ns + } + + if (isForbiddenTag(element) && !isServerRendering()) { + element.forbidden = true + process.env.NODE_ENV !== 'production' && warn( + 'Templates should only be responsible for mapping the state to the ' + + 'UI. Avoid placing tags with side-effects in your templates, such as ' + + `<${tag}>` + ', as they will not be parsed.' + ) + } + + // apply pre-transforms + for (let i = 0; i < preTransforms.length; i++) { + element = preTransforms[i](element, options) || element + } + + if (!inVPre) { + processPre(element) + if (element.pre) { + inVPre = true + } + } + if (platformIsPreTag(element.tag)) { + inPre = true + } + if (inVPre) { + processRawAttrs(element) + } else if (!element.processed) { + // structural directives + processFor(element) + processIf(element) + processOnce(element) + // element-scope stuff + processElement(element, options) + } + + function checkRootConstraints (el) { + if (process.env.NODE_ENV !== 'production') { + if (el.tag === 'slot' || el.tag === 'template') { + warnOnce( + `Cannot use <${el.tag}> as component root element because it may ` + + 'contain multiple nodes.' + ) + } + if (el.attrsMap.hasOwnProperty('v-for')) { + warnOnce( + 'Cannot use v-for on stateful component root element because ' + + 'it renders multiple elements.' + ) + } + } + } + + // tree management + if (!root) { + root = element + checkRootConstraints(root) + } else if (!stack.length) { + // allow root elements with v-if, v-else-if and v-else + if (root.if && (element.elseif || element.else)) { + checkRootConstraints(element) + addIfCondition(root, { + exp: element.elseif, + block: element + }) + } else if (process.env.NODE_ENV !== 'production') { + warnOnce( + `Component template should contain exactly one root element. ` + + `If you are using v-if on multiple elements, ` + + `use v-else-if to chain them instead.` + ) + } + } + if (currentParent && !element.forbidden) { + if (element.elseif || element.else) { + processIfConditions(element, currentParent) + } else if (element.slotScope) { // scoped slot + currentParent.plain = false + const name = element.slotTarget || '"default"' + ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element + } else { + currentParent.children.push(element) + element.parent = currentParent + } + } + if (!unary) { + currentParent = element + stack.push(element) + } else { + closeElement(element) + } + }, + + end () { + // remove trailing whitespace + const element = stack[stack.length - 1] + const lastNode = element.children[element.children.length - 1] + if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) { + element.children.pop() + } + // pop stack + stack.length -= 1 + currentParent = stack[stack.length - 1] + closeElement(element) + }, + + chars (text: string) { + if (!currentParent) { + if (process.env.NODE_ENV !== 'production') { + if (text === template) { + warnOnce( + 'Component template requires a root element, rather than just text.' + ) + } else if ((text = text.trim())) { + warnOnce( + `text "${text}" outside root element will be ignored.` + ) + } + } + return + } + // IE textarea placeholder bug + /* istanbul ignore if */ + if (isIE && + currentParent.tag === 'textarea' && + currentParent.attrsMap.placeholder === text + ) { + return + } + const children = currentParent.children + text = inPre || text.trim() + ? isTextTag(currentParent) ? text : decodeHTMLCached(text) + // only preserve whitespace if its not right after a starting tag + : preserveWhitespace && children.length ? ' ' : '' + if (text) { + let res + if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { + children.push({ + type: 2, + expression: res.expression, + tokens: res.tokens, + text + }) + } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { + children.push({ + type: 3, + text + }) + } + } + }, + comment (text: string) { + currentParent.children.push({ + type: 3, + text, + isComment: true + }) + } + }) + return root +} + +function processPre (el) { + if (getAndRemoveAttr(el, 'v-pre') != null) { + el.pre = true + } +} + +function processRawAttrs (el) { + const l = el.attrsList.length + if (l) { + const attrs = el.attrs = new Array(l) + for (let i = 0; i < l; i++) { + attrs[i] = { + name: el.attrsList[i].name, + value: JSON.stringify(el.attrsList[i].value) + } + } + } else if (!el.pre) { + // non root node in pre blocks with no attributes + el.plain = true + } +} + +export function processElement (element: ASTElement, options: CompilerOptions) { + processKey(element) + + // determine whether this is a plain element after + // removing structural attributes + element.plain = !element.key && !element.attrsList.length + + processRef(element) + processSlot(element) + processComponent(element) + for (let i = 0; i < transforms.length; i++) { + element = transforms[i](element, options) || element + } + processAttrs(element) +} + +function processKey (el) { + const exp = getBindingAttr(el, 'key') + if (exp) { + if (process.env.NODE_ENV !== 'production' && el.tag === 'template') { + warn(`<template> cannot be keyed. Place the key on real elements instead.`) + } + el.key = exp + } +} + +function processRef (el) { + const ref = getBindingAttr(el, 'ref') + if (ref) { + el.ref = ref + el.refInFor = checkInFor(el) + } +} + +export function processFor (el: ASTElement) { + let exp + if ((exp = getAndRemoveAttr(el, 'v-for'))) { + const res = parseFor(exp) + if (res) { + extend(el, res) + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid v-for expression: ${exp}` + ) + } + } +} + +type ForParseResult = { + for: string; + alias: string; + iterator1?: string; + iterator2?: string; +}; + +export function parseFor (exp: string): ?ForParseResult { + const inMatch = exp.match(forAliasRE) + if (!inMatch) return + const res = {} + res.for = inMatch[2].trim() + const alias = inMatch[1].trim().replace(stripParensRE, '') + const iteratorMatch = alias.match(forIteratorRE) + if (iteratorMatch) { + res.alias = alias.replace(forIteratorRE, '') + res.iterator1 = iteratorMatch[1].trim() + if (iteratorMatch[2]) { + res.iterator2 = iteratorMatch[2].trim() + } + } else { + res.alias = alias + } + return res +} + +function processIf (el) { + const exp = getAndRemoveAttr(el, 'v-if') + if (exp) { + el.if = exp + addIfCondition(el, { + exp: exp, + block: el + }) + } else { + if (getAndRemoveAttr(el, 'v-else') != null) { + el.else = true + } + const elseif = getAndRemoveAttr(el, 'v-else-if') + if (elseif) { + el.elseif = elseif + } + } +} + +function processIfConditions (el, parent) { + const prev = findPrevElement(parent.children) + if (prev && prev.if) { + addIfCondition(prev, { + exp: el.elseif, + block: el + }) + } else if (process.env.NODE_ENV !== 'production') { + warn( + `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` + + `used on element <${el.tag}> without corresponding v-if.` + ) + } +} + +function findPrevElement (children: Array<any>): ASTElement | void { + let i = children.length + while (i--) { + if (children[i].type === 1) { + return children[i] + } else { + if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') { + warn( + `text "${children[i].text.trim()}" between v-if and v-else(-if) ` + + `will be ignored.` + ) + } + children.pop() + } + } +} + +export function addIfCondition (el: ASTElement, condition: ASTIfCondition) { + if (!el.ifConditions) { + el.ifConditions = [] + } + el.ifConditions.push(condition) +} + +function processOnce (el) { + const once = getAndRemoveAttr(el, 'v-once') + if (once != null) { + el.once = true + } +} + +function processSlot (el) { + if (el.tag === 'slot') { + el.slotName = getBindingAttr(el, 'name') + if (process.env.NODE_ENV !== 'production' && el.key) { + warn( + `\`key\` does not work on <slot> because slots are abstract outlets ` + + `and can possibly expand into multiple elements. ` + + `Use the key on a wrapping element instead.` + ) + } + } else { + let slotScope + if (el.tag === 'template') { + slotScope = getAndRemoveAttr(el, 'scope') + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && slotScope) { + warn( + `the "scope" attribute for scoped slots have been deprecated and ` + + `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` + + `can also be used on plain elements in addition to <template> to ` + + `denote scoped slots.`, + true + ) + } + el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope') + } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) { + warn( + `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` + + `(v-for takes higher priority). Use a wrapper <template> for the ` + + `scoped slot to make it clearer.`, + true + ) + } + el.slotScope = slotScope + } + const slotTarget = getBindingAttr(el, 'slot') + if (slotTarget) { + el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget + // preserve slot as an attribute for native shadow DOM compat + // only for non-scoped slots. + if (el.tag !== 'template' && !el.slotScope) { + addAttr(el, 'slot', slotTarget) + } + } + } +} + +function processComponent (el) { + let binding + if ((binding = getBindingAttr(el, 'is'))) { + el.component = binding + } + if (getAndRemoveAttr(el, 'inline-template') != null) { + el.inlineTemplate = true + } +} + +function processAttrs (el) { + const list = el.attrsList + let i, l, name, rawName, value, modifiers, isProp + for (i = 0, l = list.length; i < l; i++) { + name = rawName = list[i].name + value = list[i].value + if (dirRE.test(name)) { + // mark element as dynamic + el.hasBindings = true + // modifiers + modifiers = parseModifiers(name) + if (modifiers) { + name = name.replace(modifierRE, '') + } + if (bindRE.test(name)) { // v-bind + name = name.replace(bindRE, '') + value = parseFilters(value) + isProp = false + if (modifiers) { + if (modifiers.prop) { + isProp = true + name = camelize(name) + if (name === 'innerHtml') name = 'innerHTML' + } + if (modifiers.camel) { + name = camelize(name) + } + if (modifiers.sync) { + addHandler( + el, + `update:${camelize(name)}`, + genAssignmentCode(value, `$event`) + ) + } + } + if (isProp || ( + !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) + )) { + addProp(el, name, value) + } else { + addAttr(el, name, value) + } + } else if (onRE.test(name)) { // v-on + name = name.replace(onRE, '') + addHandler(el, name, value, modifiers, false, warn) + } else { // normal directives + name = name.replace(dirRE, '') + // parse arg + const argMatch = name.match(argRE) + const arg = argMatch && argMatch[1] + if (arg) { + name = name.slice(0, -(arg.length + 1)) + } + addDirective(el, name, rawName, value, arg, modifiers) + if (process.env.NODE_ENV !== 'production' && name === 'model') { + checkForAliasModel(el, value) + } + } + } else { + // literal attribute + if (process.env.NODE_ENV !== 'production') { + const res = parseText(value, delimiters) + if (res) { + warn( + `${name}="${value}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div id="{{ val }}">, use <div :id="val">.' + ) + } + } + addAttr(el, name, JSON.stringify(value)) + // #6887 firefox doesn't update muted state if set via attribute + // even immediately after element creation + if (!el.component && + name === 'muted' && + platformMustUseProp(el.tag, el.attrsMap.type, name)) { + addProp(el, name, 'true') + } + } + } +} + +function checkInFor (el: ASTElement): boolean { + let parent = el + while (parent) { + if (parent.for !== undefined) { + return true + } + parent = parent.parent + } + return false +} + +function parseModifiers (name: string): Object | void { + const match = name.match(modifierRE) + if (match) { + const ret = {} + match.forEach(m => { ret[m.slice(1)] = true }) + return ret + } +} + +function makeAttrsMap (attrs: Array<Object>): Object { + const map = {} + for (let i = 0, l = attrs.length; i < l; i++) { + if ( + process.env.NODE_ENV !== 'production' && + map[attrs[i].name] && !isIE && !isEdge + ) { + warn('duplicate attribute: ' + attrs[i].name) + } + map[attrs[i].name] = attrs[i].value + } + return map +} + +// for script (e.g. type="x/template") or style, do not decode content +function isTextTag (el): boolean { + return el.tag === 'script' || el.tag === 'style' +} + +function isForbiddenTag (el): boolean { + return ( + el.tag === 'style' || + (el.tag === 'script' && ( + !el.attrsMap.type || + el.attrsMap.type === 'text/javascript' + )) + ) +} + +const ieNSBug = /^xmlns:NS\d+/ +const ieNSPrefix = /^NS\d+:/ + +/* istanbul ignore next */ +function guardIESVGBug (attrs) { + const res = [] + for (let i = 0; i < attrs.length; i++) { + const attr = attrs[i] + if (!ieNSBug.test(attr.name)) { + attr.name = attr.name.replace(ieNSPrefix, '') + res.push(attr) + } + } + return res +} + +function checkForAliasModel (el, value) { + let _el = el + while (_el) { + if (_el.for && _el.alias === value) { + warn( + `<${el.tag} v-model="${value}">: ` + + `You are binding v-model directly to a v-for iteration alias. ` + + `This will not be able to modify the v-for source array because ` + + `writing to the alias is like modifying a function local variable. ` + + `Consider using an array of objects and use v-model on an object property instead.` + ) + } + _el = _el.parent + } +} diff --git a/node_modules/vue/src/compiler/parser/text-parser.js b/node_modules/vue/src/compiler/parser/text-parser.js new file mode 100644 index 00000000..284fca33 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/text-parser.js @@ -0,0 +1,53 @@ +/* @flow */ + +import { cached } from 'shared/util' +import { parseFilters } from './filter-parser' + +const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g +const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g + +const buildRegex = cached(delimiters => { + const open = delimiters[0].replace(regexEscapeRE, '\\$&') + const close = delimiters[1].replace(regexEscapeRE, '\\$&') + return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') +}) + +type TextParseResult = { + expression: string, + tokens: Array<string | { '@binding': string }> +} + +export function parseText ( + text: string, + delimiters?: [string, string] +): TextParseResult | void { + const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE + if (!tagRE.test(text)) { + return + } + const tokens = [] + const rawTokens = [] + let lastIndex = tagRE.lastIndex = 0 + let match, index, tokenValue + while ((match = tagRE.exec(text))) { + index = match.index + // push text token + if (index > lastIndex) { + rawTokens.push(tokenValue = text.slice(lastIndex, index)) + tokens.push(JSON.stringify(tokenValue)) + } + // tag token + const exp = parseFilters(match[1].trim()) + tokens.push(`_s(${exp})`) + rawTokens.push({ '@binding': exp }) + lastIndex = index + match[0].length + } + if (lastIndex < text.length) { + rawTokens.push(tokenValue = text.slice(lastIndex)) + tokens.push(JSON.stringify(tokenValue)) + } + return { + expression: tokens.join('+'), + tokens: rawTokens + } +} diff --git a/node_modules/vue/src/compiler/to-function.js b/node_modules/vue/src/compiler/to-function.js new file mode 100644 index 00000000..7fe0f13f --- /dev/null +++ b/node_modules/vue/src/compiler/to-function.js @@ -0,0 +1,99 @@ +/* @flow */ + +import { noop, extend } from 'shared/util' +import { warn as baseWarn, tip } from 'core/util/debug' + +type CompiledFunctionResult = { + render: Function; + staticRenderFns: Array<Function>; +}; + +function createFunction (code, errors) { + try { + return new Function(code) + } catch (err) { + errors.push({ err, code }) + return noop + } +} + +export function createCompileToFunctionFn (compile: Function): Function { + const cache = Object.create(null) + + return function compileToFunctions ( + template: string, + options?: CompilerOptions, + vm?: Component + ): CompiledFunctionResult { + options = extend({}, options) + const warn = options.warn || baseWarn + delete options.warn + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + // detect possible CSP restriction + try { + new Function('return 1') + } catch (e) { + if (e.toString().match(/unsafe-eval|CSP/)) { + warn( + 'It seems you are using the standalone build of Vue.js in an ' + + 'environment with Content Security Policy that prohibits unsafe-eval. ' + + 'The template compiler cannot work in this environment. Consider ' + + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + + 'templates into render functions.' + ) + } + } + } + + // check cache + const key = options.delimiters + ? String(options.delimiters) + template + : template + if (cache[key]) { + return cache[key] + } + + // compile + const compiled = compile(template, options) + + // check compilation errors/tips + if (process.env.NODE_ENV !== 'production') { + if (compiled.errors && compiled.errors.length) { + warn( + `Error compiling template:\n\n${template}\n\n` + + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', + vm + ) + } + if (compiled.tips && compiled.tips.length) { + compiled.tips.forEach(msg => tip(msg, vm)) + } + } + + // turn code into functions + const res = {} + const fnGenErrors = [] + res.render = createFunction(compiled.render, fnGenErrors) + res.staticRenderFns = compiled.staticRenderFns.map(code => { + return createFunction(code, fnGenErrors) + }) + + // check function generation errors. + // this should only happen if there is a bug in the compiler itself. + // mostly for codegen development use + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { + warn( + `Failed to generate render function:\n\n` + + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), + vm + ) + } + } + + return (cache[key] = res) + } +} |
