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/parser/index.js | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/compiler/parser/index.js')
| -rw-r--r-- | node_modules/vue/src/compiler/parser/index.js | 675 |
1 files changed, 675 insertions, 0 deletions
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 + } +} |
