從ast模板對象中取出相應的屬性。css
// 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 }
獲取動態屬性值html
:|v-bind
,調用getAndRemoveAttr
讀取相應的屬性值parseFilters
解析返回值中可能存在的過濾器,並返回false
,返回相應的靜態屬性,並將靜態屬性格式化爲字符串""demo1""
,返回/** 第三個參數傳true會在獲取不到動態屬性的時候取靜態屬性 */ 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) } } }
{{input | filter1 | filter2 }} 解析爲:_f("filter2")(_f("filter1")(input))vue
/* @flow */ /** 匹配任意非空字符,),.,+,-,_,$,] * 排除一些其餘用法產生的/,諸如a++ / b, a-- / b, a/b, (a + a1) / b, ../path */ const validDivisionCharRE = /[\w).+\-_$\]]/ /** 解析出filter的條件是:匹配到|, * 而且|不在單引號,雙引號,模板引用符,正則,括號,中括號,大括號中, * 而且不是|| */ 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) // 0x5C => \ 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 && // | 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}` } }
/* @flow */ /** * Expand input[v-model] with dyanmic type bindings into v-if-else chains * Turn this: * <input v-model="data[type]" :type="type"> * into this: * <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> * <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> * <input v-else :type="type" v-model="data[type]"> */ import { addRawAttr, getBindingAttr, getAndRemoveAttr } from 'compiler/helpers' import { processFor, processElement, addIfCondition, createASTElement } from 'compiler/parser/index' /** 處理input標籤的v-model */ function preTransformNode (el: ASTElement, options: CompilerOptions) { if (el.tag === 'input') { const map = el.attrsMap if (!map['v-model']) { return } let typeBinding if (map[':type'] || map['v-bind:type']) { typeBinding = getBindingAttr(el, 'type') } if (!map.type && !typeBinding && map['v-bind']) { typeBinding = `(${map['v-bind']}).type` } if (typeBinding) { const ifCondition = getAndRemoveAttr(el, 'v-if', true) const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : `` const hasElse = getAndRemoveAttr(el, 'v-else', true) != null const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) // 1. checkbox const branch0 = cloneASTElement(el) // process for on the main node,若是有v-for,將v-for解析爲for,alias等屬性,並添加到branch0上 processFor(branch0) // ast直接添加type屬性 addRawAttr(branch0, 'type', 'checkbox') processElement(branch0, options) branch0.processed = true // prevent it from double-processed branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra addIfCondition(branch0, { exp: branch0.if, block: branch0 }) // 2. add radio else-if condition const branch1 = cloneASTElement(el) getAndRemoveAttr(branch1, 'v-for', true) addRawAttr(branch1, 'type', 'radio') processElement(branch1, options) addIfCondition(branch0, { exp: `(${typeBinding})==='radio'` + ifConditionExtra, block: branch1 }) // 3. other const branch2 = cloneASTElement(el) getAndRemoveAttr(branch2, 'v-for', true) addRawAttr(branch2, ':type', typeBinding) processElement(branch2, options) addIfCondition(branch0, { exp: ifCondition, block: branch2 }) if (hasElse) { branch0.else = true } else if (elseIfCondition) { branch0.elseif = elseIfCondition } return branch0 } } } function cloneASTElement (el) { return createASTElement(el.tag, el.attrsList.slice(), el.parent) } export default { preTransformNode }
extend(el, res),el.for, el.alias, el.iterator1, el.iterator2node
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}` ) } } }
這個函數解析v-for字符串,並返回,好比 item in items返回{ for: items, alias: item }
; (item, index) in items
返回{ for: items, alias: item, iterator1: index }
; (item, key, index) in items
返回{ for: items, alias: item, iterator1: key, iterator2: index }
;git
/** * item in items *?最小貪婪匹配,如 a in b in c 則匹配 * a 而不是 a in b, in item 則匹配 '' */ export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/ /** * 匹配多個參數。 */ export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ const stripParensRE = /^\(|\)$/g /** 解析v-for表達式,並返回 */ 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 }
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) /** * 賦值 el.staticClass, classBinding, staticStyle, styleBinding */ for (let i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element } /** 處理attrsList 剩餘屬性 */ processAttrs(element) }
{name: 'slot', value: slotTarget}
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 :input.number modifiers = parseModifiers(name) if (modifiers) { name = name.replace(modifierRE, '') // :input } if (bindRE.test(name)) { // v-bind name = name.replace(bindRE, '') // input value = parseFilters(value) // _f('filter1')(value) isProp = false if (modifiers) { if (modifiers.prop) { isProp = true name = camelize(name) if (name === 'innerHtml') name = 'innerHTML' } if (modifiers.camel) { name = camelize(name) } // 雙向綁定,經過emit事件來觸發 if (modifiers.sync) { /** 在el的event或者nativeevent中添加事件 * el.event.value = {value: value=$event} || [...] */ addHandler( el, `update:${camelize(name)}`, genAssignmentCode(value, `$event`) // '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') } } } }
/** * 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 {exp: "test", key: null} * - test[key] {exp: "test", key: "key"} * - test[test1[key]] {exp: "test", key: "test1[key]"} * - test["a"][key] {exp: "test["a"]", key: "key"} * - xxx.test[a[a].test1[key]] {exp: "xxx.test", key: "a[a].test1[key]"} * - test.xxx.a["asa"][test1[key]] {exp: "test.xxx.a["asa"]", key: "test1[key]"} * */ let len, str, chr, index, expressionPos, expressionEndPos type ModelParseResult = { exp: string, key: string | null } /** 獲取最靠後的一個完整項爲key */ 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 } } }
根據不一樣的tag類型,將v-model轉化成不一樣的事件綁定onchange、oninput等github
export default function model ( el: ASTElement, dir: ASTDirective, _warn: Function ): ?boolean { warn = _warn const value = dir.value const modifiers = dir.modifiers const tag = el.tag const type = el.attrsMap.type if (process.env.NODE_ENV !== 'production') { // inputs with type="file" are read only and setting the input's // value will throw an error. if (tag === 'input' && type === 'file') { warn( `<${el.tag} v-model="${value}" type="file">:\n` + `File inputs are read only. Use a v-on:change listener instead.` ) } } if (el.component) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime return false } else if (tag === 'select') { genSelect(el, value, modifiers) } else if (tag === 'input' && type === 'checkbox') { genCheckboxModel(el, value, modifiers) } else if (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers) } else if (tag === 'input' || tag === 'textarea') { genDefaultModel(el, value, modifiers) } else if (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime return false } else if (process.env.NODE_ENV !== 'production') { warn( `<${el.tag} v-model="${value}">: ` + `v-model is not supported on this element type. ` + 'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.' ) } // ensure runtime directive metadata return true }
返回設置value值的一個對象集合express
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}}` // 相似 function ($$v) { data=_n($$v.tirm()) } } }
/** * Cross-platform codegen helper for generating v-model value assignment code. * 返回相似 data=_n($$v.tirm()); * $set(data, key, _n($$v.tirm())); */ 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})` } }
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 }
建立可遍歷的空數組segmentfault
如下標籤若是隻寫了左側的tag,瀏覽器會自動補全數組
export const canBeLeftOpenTag = makeMap( 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' )
自閉合標籤瀏覽器
export const isUnaryTag = makeMap( 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + 'link,meta,param,source,track,wbr' )
export const isNonPhrasingTag = makeMap( 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + 'title,tr,track' )
/** 解析起始標籤,使用正則匹配attrs,並將匹配到的正則數組放到attrs數組裏面 */ function parseStartTag () { // 標籤名 const start = html.match(startTagOpen) if (start) { const match = { tagName: start[1], attrs: [], start: index } advance(start[0].length) let end, attr // 解析attr while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) { attr.start = index advance(attr[0].length) attr.end = index match.attrs.push(attr) } if (end) { // 是否匹配到自閉合符號/,匹配到則設置標誌屬性unarySlash='/' match.unarySlash = end[1] advance(end[0].length) match.end = index return match } } }
/** 解析上一步獲取的正則attrs,保存爲{name, value}格式, * 而且將被瀏覽器轉譯的換行或特殊字符或者href裏面的換行反轉爲相應符號, * 最後將tagname,attrs等傳遞給調用函數的start函數 */ 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] // 優先獲取匹配到的第三個正則捕獲 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 (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { attrs[i].start = args.start + args[0].match(/^\s*/).length attrs[i].end = args.end } } if (!unary) { stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end }) lastTag = tagName } if (options.start) { options.start(tagName, attrs, unary, match.start, match.end) } }
向ast對象裏面添加attr{name, value, ...}
// add a raw attr (use this in preTransforms) export function addRawAttr (el: ASTElement, name: string, value: any, range?: Range) { el.attrsMap[name] = value el.attrsList.push(rangeSetItem({ name, value }, range)) }
2.6版本取消scope,slot,slot-scope屬性,採用v-slot
v-slot僅適用於template或者組件
針對tempalte,給ast賦值:
el.slotTarget = name el.slotTargetDynamic = dynamic el.slotScope = slotBinding.value || emptySlotScopeToke
針對組件,給ast賦值:
el.scopedSlots = {slotTarget, slotTargetDynamic, slotScope}
而且將el不含v-slot屬性的child賦值給scopedslots.children,el.children = []
// handle content being passed to a component as slot, // e.g. <template slot="xxx">, <div slot-scope="xxx"> function processSlotContent (el) { 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.`, el.rawAttrsMap['scope'], 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.`, el.rawAttrsMap['slot-scope'], true ) } el.slotScope = slotScope } // slot="xxx" const slotTarget = getBindingAttr(el, 'slot') if (slotTarget) { el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']) // 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, getRawBindingAttr(el, 'slot')) } } // 2.6 v-slot syntax if (process.env.NEW_SLOT_SYNTAX) { if (el.tag === 'template') { // v-slot on <template> const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { if (process.env.NODE_ENV !== 'production') { if (el.slotTarget || el.slotScope) { warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } if (el.parent && !maybeComponent(el.parent)) { warn( `<template v-slot> can only appear at the root level inside ` + `the receiving the component`, el ) } } const { name, dynamic } = getSlotName(slotBinding) el.slotTarget = name el.slotTargetDynamic = dynamic el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf } } else { // v-slot on component, denotes default slot const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { if (process.env.NODE_ENV !== 'production') { if (!maybeComponent(el)) { warn( `v-slot can only be used on components or <template>.`, slotBinding ) } if (el.slotScope || el.slotTarget) { warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } if (el.scopedSlots) { warn( `To avoid scope ambiguity, the default slot should also use ` + `<template> syntax when there are other named slots.`, slotBinding ) } } // add the component's children to its default slot const slots = el.scopedSlots || (el.scopedSlots = {}) const { name, dynamic } = getSlotName(slotBinding) const slotContainer = slots[name] = createASTElement('template', [], el) slotContainer.slotTarget = name slotContainer.slotTargetDynamic = dynamic slotContainer.children = el.children.filter((c: any) => { if (!c.slotScope) { // 內部的slotScope所有無效 c.parent = slotContainer return true } }) slotContainer.slotScope = slotBinding.value || emptySlotScopeToken // remove children as they are returned from scopedSlots now el.children = [] // mark el non-plain so data gets generated el.plain = false } } } }
返回style對象
export const parseStyleText = cached(function (cssText) { const res = {} // 負向捕獲,匹配後面沒有閉括號的分號 const listDelimiter = /;(?![^(]*\))/g // split傳入正則做爲分隔符,正則裏面的捕獲也會成爲數組的數組項 const propertyDelimiter = /:(.+)/ cssText.split(listDelimiter).forEach(function (item) { if (item) { const tmp = item.split(propertyDelimiter) tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) } }) return res })