strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret }
strats.watch = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ if (!childVal) return Object.create(parentVal || null) if (process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } if (!parentVal) return childVal const ret = {} extend(ret, parentVal) for (const key in childVal) { let parent = ret[key] const child = childVal[key] if (parent && !Array.isArray(parent)) { parent = [parent] } ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret }
function updateStyle(oldVnode, vnode) { if (!oldVnode.data.style && !vnode.data.style) { return } let cur, name const elm = vnode.elm const oldStyle = oldVnode.data.style || {} let style = vnode.data.style || {} const needClone = style.__ob__ // handle array syntax if (Array.isArray(style)) { style = vnode.data.style = toObject(style) } // clone the style for future updates, // in case the user mutates the style object in-place. if (needClone) { style = vnode.data.style = extend({}, style) } for (name in oldStyle) { if (!style[name]) { elm.setStyle(normalize(name), '') } } for (name in style) { cur = style[name] elm.setStyle(normalize(name), cur) } }
/** * Normalize all injections into Object-based format * 将所有注入规范化为基于对象的格式 */ function normalizeInject (options: Object, vm: ?Component) { const inject = options.inject if (!inject) return const normalized = options.inject = {} if (Array.isArray(inject)) { // 将数组改为形如{from:val} 的对象 for (let i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] } } } else if (isPlainObject(inject)) { // 对象就枚举 ==> 改变成2个不同的{}形式 for (const key in inject) { const val = inject[key] normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val } } } else if (process.env.NODE_ENV !== 'production') { // 不是arr和obj那就错啦 warn( `Invalid value for option "inject": expected an Array or an Object, ` + `but got ${toRawType(inject)}.`, vm ) } }
strats.watch = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ // 不存在子直接返回父的复制 (因为create会挂载到__proto__原型链上) if (!childVal) return Object.create(parentVal || null) if (process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) // 检验真对象[Object Object] } if (!parentVal) return childVal // 没有父返回子 const ret = {} extend(ret, parentVal) // 浅拷贝一份父的 for (const key in childVal) { let parent = ret[key] const child = childVal[key] // 父存在 、转数组 if (parent && !Array.isArray(parent)) { parent = [parent] } // 和mergeHook一样的处理 ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret }
it('not specified getTagNamespace option', () => { const options = extend({}, baseOptions) delete options.getTagNamespace const ast = parse('<svg><text>hello world</text></svg>', options) expect(ast.tag).toBe('svg') expect(ast.ns).toBeUndefined() })
function resolveTransition (def?: string | Object): ?Object { if (!def) { return } /* istanbul ignore else */ if (typeof def === 'object') { const res = {} if (def.css !== false) { extend(res, autoCssTransition(def.name || 'v')) } extend(res, def) return res } else if (typeof def === 'string') { return autoCssTransition(def) } }
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) { if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { return } let key, cur, old const elm = vnode.elm const oldAttrs = oldVnode.data.attrs || {} let attrs: any = vnode.data.attrs || {} // clone observed objects, as the user probably wants to mutate it if (isDef(attrs.__ob__)) { attrs = vnode.data.attrs = extend({}, attrs) } for (key in attrs) { cur = attrs[key] old = oldAttrs[key] if (old !== cur) { setAttr(elm, key, cur) } } // #4391: in IE9, setting type can reset value for input[type=radio] /* istanbul ignore if */ if (isIE9 && attrs.value !== oldAttrs.value) { setAttr(elm, 'value', attrs.value) } for (key in oldAttrs) { if (isUndef(attrs[key])) { if (isXlink(key)) { elm.removeAttributeNS(xlinkNS, getXlinkProp(key)) } else if (!isEnumeratedAttr(key)) { elm.removeAttribute(key) } } } }
// merge static and dynamic style data on the same vnode function normalizeStyleData (data: VNodeData): ?Object { const style = normalizeStyleBinding(data.style) // static style is pre-processed into an object during compilation // and is always a fresh object, so it's safe to merge into it return data.staticStyle ? extend(data.staticStyle, style) : style }
it('forgivingly handle < in plain text', () => { const options = extend({}, baseOptions) const ast = parse('<p>1 < 2 < 3</p>', options) expect(ast.tag).toBe('p') expect(ast.children.length).toBe(1) expect(ast.children[0].type).toBe(3) expect(ast.children[0].text).toBe('1 < 2 < 3') })
it('should ignore comments', () => { const options = extend({}, baseOptions) const ast = parse(`<div>123<!--comment here--></div>`, options) expect(ast.tag).toBe('div') expect(ast.children.length).toBe(1) expect(ast.children[0].type).toBe(3) expect(ast.children[0].text).toBe('123') })
function toObject(arr) { const res = {} for (var i = 0; i < arr.length; i++) { if (arr[i]) { extend(res, arr[i]) } } return res }
strats.computed = function ( // 计算属性 parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) // [Object Object] } /** * 1、无父拿子 * 2、复制父 * 3、有子合并 * 4、返回合并/复制结果 **/ if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret }
it('generate component with comment', () => { const options = extend({ comments: true }, baseOptions) const template = '<div><!--comment--></div>' const generatedCode = `with(this){return _c('div',[_e("comment")])}` const ast = parse(template, options) optimize(ast, options) const res = generate(ast, options) expect(res.render).toBe(generatedCode) })
it('generate comments with special characters', () => { const options = extend({ comments: true }, baseOptions) const template = '<div><!--\n\'comment\'\n--></div>' const generatedCode = `with(this){return _c('div',[_e("\\n'comment'\\n")])}` const ast = parse(template, options) optimize(ast, options) const res = generate(ast, options) expect(res.render).toBe(generatedCode) })
it('IE conditional comments', () => { const options = extend({}, baseOptions) const ast = parse(` <div> <!--[if lte IE 8]> <p>Test 1</p> <![endif]--> </div> `, options) expect(ast.tag).toBe('div') expect(ast.children.length).toBe(0) })
it('should kept comments', () => { const options = extend({ comments: true }, baseOptions) const ast = parse(`<div>123<!--comment here--></div>`, options) expect(ast.tag).toBe('div') expect(ast.children.length).toBe(2) expect(ast.children[0].type).toBe(3) expect(ast.children[0].text).toBe('123') expect(ast.children[1].type).toBe(3) // parse comment with ASTText expect(ast.children[1].isComment).toBe(true) // parse comment with ASTText expect(ast.children[1].text).toBe('comment here') })
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}` ) } } }
it('ignore the first newline in <pre> tag', function () { const options = extend({}, baseOptions) const ast = parse('<div><pre>\nabc</pre>\ndef<pre>\n\nabc</pre></div>', options) const pre = ast.children[0] expect(pre.children[0].type).toBe(3) expect(pre.children[0].text).toBe('abc') const text = ast.children[1] expect(text.type).toBe(3) expect(text.text).toBe('\ndef') const pre2 = ast.children[2] expect(pre2.children[0].type).toBe(3) expect(pre2.children[0].text).toBe('\nabc') })
it('preserve whitespace in <pre> tag', function () { const options = extend({}, baseOptions) const ast = parse('<pre><code> \n<span>hi</span>\n </code><span> </span></pre>', options) const code = ast.children[0] expect(code.children[0].type).toBe(3) expect(code.children[0].text).toBe(' \n') expect(code.children[2].type).toBe(3) expect(code.children[2].text).toBe('\n ') const span = ast.children[1] expect(span.children[0].type).toBe(3) expect(span.children[0].text).toBe(' ') })
/** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */ function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): Object { const res = Object.create(parentVal || null) if (childVal) { process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res } }
export default function renderDOMProps (node: VNodeWithData): string { let props = node.data.domProps let res = '' let parent = node.parent while (isDef(parent)) { if (parent.data && parent.data.domProps) { props = extend(extend({}, props), parent.data.domProps) } parent = parent.parent } if (isUndef(props)) { return res } const attrs = node.data.attrs for (const key in props) { if (key === 'innerHTML') { setText(node, props[key], true) } else if (key === 'textContent') { setText(node, props[key], false) } else if (key === 'value' && node.tag === 'textarea') { setText(node, props[key], false) } else { // $flow-disable-line (WTF?) const attr = propsToAttrMap[key] || key.toLowerCase() if (isRenderableAttr(attr) && // avoid rendering double-bound props/attrs twice !(isDef(attrs) && isDef(attrs[attr])) ) { res += renderAttr(attr, props[key]) } } } return res }
export function compile ( template: string, options?: CompilerOptions ): CompiledResult { options = options || {} const errors = [] // allow injecting modules/directives const baseModules = baseOptions.modules || [] const modules = options.modules ? baseModules.concat(options.modules) : baseModules const directives = options.directives ? extend(extend({}, baseOptions.directives), options.directives) : baseOptions.directives const compiled = baseCompile(template, { modules, directives, warn: msg => { errors.push(msg) } }) compiled.errors = errors.concat(detectErrors(compiled.ast)) return compiled }
it('pre/post transforms', () => { const options = extend({}, baseOptions) const spy1 = jasmine.createSpy('preTransform') const spy2 = jasmine.createSpy('postTransform') options.modules = options.modules.concat([{ preTransformNode (el) { spy1(el.tag) }, postTransformNode (el) { expect(el.attrs.length).toBe(1) spy2(el.tag) } }]) parse('<img v-pre src="hi">', options) expect(spy1).toHaveBeenCalledWith('img') expect(spy2).toHaveBeenCalledWith('img') })
strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object { /* istanbul ignore if */ if (!childVal) return parentVal if (!parentVal) return childVal const ret = {} extend(ret, parentVal) for (const key in childVal) { let parent = ret[key] const child = childVal[key] if (parent && !Array.isArray(parent)) { parent = [parent] } ret[key] = parent ? parent.concat(child) : [child] } return ret }
function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) { return } let key, cur const elm: any = vnode.elm const oldProps = oldVnode.data.domProps || {} let props = vnode.data.domProps || {} // clone observed objects, as the user probably wants to mutate it if (isDef(props.__ob__)) { props = vnode.data.domProps = extend({}, props) } for (key in oldProps) { if (isUndef(props[key])) { elm[key] = '' } } for (key in props) { cur = props[key] // ignore children if the node has textContent or innerHTML, // as these will throw away existing DOM nodes and cause removal errors // on subsequent patches (#3360) if (key === 'textContent' || key === 'innerHTML') { if (vnode.children) vnode.children.length = 0 if (cur === oldProps[key]) continue } if (key === 'value') { // store value as _value as well since // non-string values will be stringified elm._value = cur // avoid resetting cursor position when value is the same const strCur = isUndef(cur) ? '' : String(cur) if (shouldUpdateValue(elm, vnode, strCur)) { elm.value = strCur } } else { elm[key] = cur } } }
function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { if ((!oldVnode.data || !oldVnode.data.style) && !vnode.data.style) { return } let cur, name const el: any = vnode.elm const oldStyle: any = oldVnode.data.style || {} let style: any = vnode.data.style || {} // handle string if (typeof style === 'string') { el.style.cssText = style return } const needClone = style.__ob__ // handle array syntax if (Array.isArray(style)) { style = vnode.data.style = toObject(style) } // clone the style for future updates, // in case the user mutates the style object in-place. if (needClone) { style = vnode.data.style = extend({}, style) } for (name in oldStyle) { if (!style[name]) { el.style[normalize(name)] = '' } } for (name in style) { cur = style[name] if (cur !== oldStyle[name]) { // ie9 setting to null has no effect, must use empty string el.style[normalize(name)] = cur == null ? '' : cur } } }
strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ if (!childVal) return Object.create(parentVal || null) if (!parentVal) return childVal const ret = {} extend(ret, parentVal) for (const key in childVal) { let parent = ret[key] const child = childVal[key] if (parent && !Array.isArray(parent)) { parent = [parent] } ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret }
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 }
function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { if (!oldVnode.data.domProps && !vnode.data.domProps) { return } let key, cur const elm: any = vnode.elm const oldProps = oldVnode.data.domProps || {} let props = vnode.data.domProps || {} // clone observed objects, as the user probably wants to mutate it if (props.__ob__) { props = vnode.data.domProps = extend({}, props) } for (key in oldProps) { if (props[key] == null) { elm[key] = undefined } } for (key in props) { // ignore children if the node has textContent or innerHTML, // as these will throw away existing DOM nodes and cause removal errors // on subsequent patches (#3360) if ((key === 'textContent' || key === 'innerHTML') && vnode.children) { vnode.children.length = 0 } cur = props[key] if (key === 'value') { // store value as _value as well since // non-string values will be stringified elm._value = cur // avoid resetting cursor position when value is the same const strCur = cur == null ? '' : String(cur) if (elm.value !== strCur && !elm.composing) { elm.value = strCur } } else { elm[key] = cur } } }