This commit is contained in:
Evan You 2016-04-13 17:55:22 -04:00
parent 5c31f65684
commit 5228ccb5fd
9 changed files with 200 additions and 271 deletions

View File

@ -1,16 +0,0 @@
import { getAndRemoveAttr } from './helpers'
export function genClass (el) {
let ret = ''
const classBinding =
getAndRemoveAttr(el, ':class') ||
getAndRemoveAttr(el, 'v-bind:class')
if (classBinding) {
ret += `class: ${classBinding},`
}
const staticClass = getAndRemoveAttr(el, 'class')
if (staticClass) {
ret += `staticClass: "${staticClass}",`
}
return ret
}

View File

@ -0,0 +1,16 @@
import { genModel } from './model'
const dirMap = {
model: genModel
}
export function genDirectives (el) {
const dirs = el.directives
for (let i = 0; i < dirs.length; i++) {
let dir = dirs[i]
let gen = dirMap[dir.name]
if (gen) {
return gen(el, dir)
}
}
}

View File

@ -20,23 +20,6 @@ const modifierCode = {
self: 'if($event.target !== $event.currentTarget)return;'
}
export function addHandler (events, name, value, modifiers) {
// check capture modifier
if (modifiers && modifiers.capture) {
delete modifiers.capture
name = '!' + name // mark the event as captured
}
const newHandler = { value, modifiers }
const handlers = events[name]
if (isArray(handlers)) {
handlers.push(newHandler)
} else if (handlers) {
events[name] = [handlers, newHandler]
} else {
events[name] = newHandler
}
}
export function genHandlers (events) {
let res = 'on:{'
for (let name in events) {

View File

@ -1,140 +1,83 @@
import { genHandlers, addHandler } from './on'
import { genModel } from './model'
import { genClass } from './class'
import {
checkSVG,
parseText,
parseModifiers,
removeModifiers,
getAndRemoveAttr
} from './helpers'
const dirRE = /^v-|^@|^:/
const bindRE = /^:|^v-bind:/
const onRE = /^@|^v-on:/
const mustUsePropsRE = /^(value|selected|checked|muted)$/
import { genHandlers } from './events'
import { genDirectives } from './directives'
export function generate (ast) {
const code = ast ? genElement(ast) : '__h__("div")'
return new Function(`with (this) { return ${code}}`)
}
function genElement (el, key) {
let exp
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
return genFor(el, exp)
} else if ((exp = getAndRemoveAttr(el, 'v-if'))) {
return genIf(el, exp)
function genElement (el) {
if (el['for']) {
return genFor(el)
} else if (el['if']) {
return genIf(el)
} else if (el.tag === 'template') {
return genChildren(el)
} else if (el.tag === 'render') {
return genRender(el)
} else {
return `__h__('${el.tag}', ${genData(el, key)}, ${genChildren(el)})`
return `__h__('${el.tag}', ${genData(el)}, ${genChildren(el)})`
}
}
function genIf (el, exp) {
function genIf (el) {
const exp = el['if']
el['if'] = false // avoid recursion
return `(${exp}) ? ${genElement(el)} : null`
}
function genFor (el, exp) {
const inMatch = exp.match(/([a-zA-Z_][\w]*)\s+(?:in|of)\s+(.*)/)
if (!inMatch) {
throw new Error('Invalid v-for expression: ' + exp)
}
const alias = inMatch[1].trim()
exp = inMatch[2].trim()
let key = getAndRemoveAttr(el, 'track-by')
if (!key) {
key = 'undefined'
} else if (key !== '$index') {
key = alias + '["' + key + '"]'
}
return `(${exp}) && (${exp}).map(function (${alias}, $index) {return ${genElement(el, key)}})`
function genFor (el) {
const exp = el['for']
const alias = el.alias
el['for'] = false // avoid recursion
return `(${exp}) && (${exp}).map(function (${alias}, $index) {return ${genElement(el)}})`
}
function genData (el, key) {
if (!el.attrs.length) {
function genData (el) {
if (el.plain) {
return '{}'
}
let data = '{'
let attrs = 'attrs:{'
let props = 'props:{'
let events = {}
let hasAttrs = false
let hasProps = false
let hasEvents = false
// key
if (key) {
data += `key:${key},`
if (el.key) {
data += `key:${el.key},`
}
// check SVG namespace.
// this has the side effect of marking all children if the tag itself is <svg>
if (checkSVG(el)) {
// svg
if (el.svg) {
data += 'svg:true,'
}
// directives first.
// directives may mutate the el's other properties before they are generated.
if (el.directives) {
let dirs = genDirectives(el)
if (dirs) data += dirs + ','
}
// class
// do it before other attributes becaues it removes static class
// and class bindings from the element
data += genClass(el)
// parent elements my need to add props to children
// e.g. select
if (el.staticClass) {
data += `staticClass:"${el.staticClass}",`
}
if (el.classBinding) {
data += `class:${el.classBinding},`
}
// style
if (el.styleBinding) {
data += `style:${el.styleBinding},`
}
// props
if (el.props) {
hasProps = true
props += el.props + ','
data += 'props:{' + genProps(el.props) + '},'
}
// loop attributes
for (let i = 0, l = el.attrs.length; i < l; i++) {
let attr = el.attrs[i]
let name = attr.name
let value = attr.value
if (dirRE.test(name)) {
// modifiers
const modifiers = parseModifiers(name)
name = removeModifiers(name)
if (bindRE.test(name)) {
name = name.replace(bindRE, '')
if (name === 'style') {
data += `style: ${value},`
} else if (mustUsePropsRE.test(name)) {
hasProps = true
props += `"${name}": (${value}),`
} else {
hasAttrs = true
attrs += `"${name}": (${value}),`
}
} else if (onRE.test(name)) {
hasEvents = true
name = name.replace(onRE, '')
addHandler(events, name, value, modifiers)
} else if (name === 'v-model') {
hasProps = hasEvents = true
props += genModel(el, events, value, modifiers) + ','
} else {
// TODO: normal directives
}
} else {
hasAttrs = true
attrs += `"${name}": (${JSON.stringify(attr.value)}),`
}
// attributes
if (el.attrs) {
data += 'attrs:{' + genProps(el.attrs) + '},'
}
if (hasAttrs) {
data += attrs.slice(0, -1) + '},'
}
if (hasProps) {
data += props.slice(0, -1) + '},'
}
if (hasEvents) {
data += genHandlers(events)
// event handlers
if (el.events) {
data += genHandlers(el.events)
}
console.log(data)
return data.replace(/,$/, '') + '}'
}
@ -154,24 +97,20 @@ function genNode (node) {
}
function genText (text) {
if (text === ' ') {
return '" "'
} else {
const exp = parseText(text)
if (exp) {
return `(${exp}==null?'':String(${exp}))`
} else {
return JSON.stringify(text)
}
}
return text.expression
? `(${text.expression}==null?'':String(${text.expression}))`
: JSON.stringify(text.text)
}
function genRender (el) {
const method = el.attrsMap.method
const args = el.attrsMap.args
if (process.env.NODE_ENV !== 'production' && !method) {
console.error('method attribute is required on <render>.')
return 'undefined'
}
return `${method}(${args})`
return `${el.method}(${el.args || ''})`
}
function genProps (props) {
let res = ''
for (var i = 0; i < props.length; i++) {
let prop = props[i]
res += `"${prop.name}":${prop.value},`
}
return res.slice(0, -1)
}

View File

@ -1,6 +1,9 @@
import { addHandler } from './on'
import { addHandler } from '../helpers'
export function genModel (el, events, value, modifiers) {
export function genModel (el, dir) {
const events = (el.events || (el.events = {}))
const value = dir.value
const modifiers = dir.modifiers
if (el.tag === 'select') {
if (el.attrsMap.multiple != null) {
return genMultiSelect(events, value, el)
@ -51,7 +54,10 @@ function genMultiSelect (events, value, el) {
for (let i = 0; i < el.children.length; i++) {
let c = el.children[i]
if (c.tag === 'option') {
c.props = `selected:(${value}).indexOf(${getInputValue(c)})>-1`
(c.props || (c.props = [])).push({
name: 'selected',
value: `(${value}).indexOf(${getInputValue(c)})>-1`
})
}
}
return ''

View File

@ -1,19 +1,4 @@
export function getAndRemoveAttr (el, attr) {
let val
if ((val = el.attrsMap[attr])) {
el.attrsMap[attr] = null
for (let i = 0, l = el.attrs.length; i < l; i++) {
if (el.attrs[i].name === attr) {
el.attrs.splice(i, 1)
break
}
}
}
return val
}
const modifierRE = /\.[^\.]+/g
export function parseModifiers (name) {
const match = name.match(modifierRE)
if (match) {
@ -27,6 +12,46 @@ export function removeModifiers (name) {
return name.replace(modifierRE, '')
}
export function makeAttrsMap (attrs) {
const map = {}
for (let i = 0, l = attrs.length; i < l; i++) {
map[attrs[i].name] = attrs[i].value
}
return map
}
export function getAndRemoveAttr (el, attr) {
let val
if ((val = el.attrsMap[attr])) {
el.attrsMap[attr] = null
const list = el.attrsList
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].name === attr) {
list.splice(i, 1)
break
}
}
}
return val
}
export function addHandler (events, name, value, modifiers) {
// check capture modifier
if (modifiers && modifiers.capture) {
delete modifiers.capture
name = '!' + name // mark the event as captured
}
const newHandler = { value, modifiers }
const handlers = events[name]
if (Array.isArray(handlers)) {
handlers.push(newHandler)
} else if (handlers) {
events[name] = [handlers, newHandler]
} else {
events[name] = newHandler
}
}
const tagRE = /\{\{((?:.|\\n)+?)\}\}/g
export function parseText (text) {
if (!tagRE.test(text)) {
@ -50,39 +75,3 @@ export function parseText (text) {
}
return tokens.join('+')
}
// this map covers SVG elements that can appear as template root nodes
const svgMap = {
g: 1,
defs: 1,
symbol: 1,
use: 1,
image: 1,
text: 1,
circle: 1,
ellipse: 1,
line: 1,
path: 1,
polygon: 1,
polyline: 1,
rect: 1
}
export function checkSVG (el) {
if (el.tag === 'svg') {
// recursively mark all children as svg
markSVG(el)
}
return el.svg || svgMap[el.tag]
}
function markSVG (el) {
el.svg = true
if (el.children) {
for (var i = 0; i < el.children.length; i++) {
if (el.children[i].tag) {
markSVG(el.children[i])
}
}
}
}

View File

@ -1,11 +1,20 @@
import { decodeHTML } from 'entities'
import HTMLParser from './html-parser'
import {
parseText,
parseModifiers,
removeModifiers,
makeAttrsMap,
getAndRemoveAttr,
addHandler
} from './helpers'
const dirRE = /^v-|^@|^:/
const bindRE = /^:|^v-bind:/
const onRE = /^@|^v-on:/
const modifierRE = /\.[^\.]+/g
const mustUsePropsRE = /^(value|selected|checked|muted)$/
const forAliasRE = /([a-zA-Z_][\w]*)\s+(?:in|of)\s+(.*)/
// this map covers SVG elements that can appear as template root nodes
const svgMap = {
@ -44,7 +53,8 @@ export function parse (template, preserveWhitespace) {
start (tag, attrs, unary) {
let element = {
tag,
attrs,
plain: !attrs.length,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent: currentParent,
children: []
@ -59,7 +69,10 @@ export function parse (template, preserveWhitespace) {
svgIndex = stack.length
}
processControlFlow(element)
processFor(element)
processIf(element)
processRender(element)
processSlot(element)
processClassBinding(element)
processStyleBinding(element)
processAttributes(element)
@ -105,28 +118,65 @@ export function parse (template, preserveWhitespace) {
? ' '
: null
if (text) {
currentParent.children.push(text)
if (text !== ' ') {
let expression = parseText(text)
if (expression) {
currentParent.children.push({
expression
})
} else {
currentParent.children.push({ text })
}
} else {
currentParent.children.push({ text })
}
}
}
})
return root
}
function processControlFlow (el) {
function processFor (el) {
let exp
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
el['for'] = exp
const inMatch = exp.match(forAliasRE)
if (process.env.NODE_ENV !== 'production' && !inMatch) {
console.error(`Invalid v-for expression: ${exp}`)
}
el.alias = inMatch[1].trim()
el['for'] = inMatch[2].trim()
if ((exp = getAndRemoveAttr(el, 'track-by'))) {
el.key = exp
el.key = exp === '$index'
? exp
: el.alias + '["' + exp + '"]'
}
}
if ((exp = getAndRemoveAttr(el, 'v-if'))) {
}
function processIf (el) {
let exp = getAndRemoveAttr(el, 'v-if')
if (exp) {
el['if'] = exp
}
}
function processRender (el) {
if (el.tag === 'render') {
el.render = true
el.method = el.attrsMap.method
el.args = el.attrsMap.args
if (process.env.NODE_ENV !== 'production' && !el.method) {
console.error('method attribute is required on <render>.')
}
}
}
function processSlot () {
// todo
}
function processClassBinding (el) {
el['class'] = getAndRemoveAttr(el, 'class')
el.staticClass = getAndRemoveAttr(el, 'class')
el.classBinding =
getAndRemoveAttr(el, ':class') ||
getAndRemoveAttr(el, 'v-bind:class')
@ -139,22 +189,23 @@ function processStyleBinding (el) {
}
function processAttributes (el) {
for (let i = 0; i < el.attrs.length; i++) {
let name = el.attrs[i].name
let value = el.attrs[i].value
const list = el.attrsList
for (let i = 0; i < list.length; i++) {
let name = list[i].name
let value = list[i].value
if (dirRE.test(name)) {
name = name.replace(dirRE, '')
// modifiers
const modifiers = parseModifiers(name)
if (modifiers) {
name = name.replace(modifierRE, '')
name = removeModifiers(name)
}
if (bindRE.test(name)) { // v-bind
name = name.replace(bindRE, '')
if (mustUsePropsRE.test(name)) {
(el.props || (el.props = [])).push({ name, value })
} else {
(el.attrBindings || (el.attrBindings = [])).push({ name, value })
(el.attrs || (el.attrs = [])).push({ name, value })
}
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '')
@ -166,54 +217,12 @@ function processAttributes (el) {
modifiers
})
}
} else {
// literal attribute
(el.attrs || (el.attrs = [])).push({
name,
value: JSON.stringify(value)
})
}
}
}
function parseModifiers (name) {
const match = name.match(modifierRE)
if (match) {
const ret = {}
match.forEach(m => { ret[m.slice(1)] = true })
return ret
}
}
function makeAttrsMap (attrs) {
const map = {}
for (let i = 0, l = attrs.length; i < l; i++) {
map[attrs[i].name] = attrs[i].value
}
return map
}
function getAndRemoveAttr (el, attr) {
let val
if ((val = el.attrsMap[attr])) {
el.attrsMap[attr] = null
for (let i = 0, l = el.attrs.length; i < l; i++) {
if (el.attrs[i].name === attr) {
el.attrs.splice(i, 1)
break
}
}
}
return val
}
function addHandler (events, name, value, modifiers) {
// check capture modifier
if (modifiers && modifiers.capture) {
delete modifiers.capture
name = '!' + name // mark the event as captured
}
const newHandler = { value, modifiers }
const handlers = events[name]
if (Array.isArray(handlers)) {
handlers.push(newHandler)
} else if (handlers) {
events[name] = [handlers, newHandler]
} else {
events[name] = newHandler
}
}

View File

@ -31,7 +31,10 @@ function updateClass (oldVnode, vnode) {
let cls = staticClass
? staticClass + (dynamicClass ? ' ' + dynamicClass : '')
: dynamicClass
setClass(vnode.elm, cls)
if (cls !== oldVnode.class) {
setClass(vnode.elm, cls)
}
vnode.class = cls
}
}

View File