index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import { decodeHTML } from 'entities'
  2. import { parseHTML } from './html-parser'
  3. import { parseText } from './text-parser'
  4. import { hyphenate, makeMap, cached } from '../../shared/util'
  5. import {
  6. getAndRemoveAttr,
  7. addProp,
  8. addAttr,
  9. addStaticAttr,
  10. addHandler,
  11. addDirective,
  12. getBindingAttr
  13. } from '../helpers'
  14. const dirRE = /^v-|^@|^:/
  15. const bindRE = /^:|^v-bind:/
  16. const onRE = /^@|^v-on:/
  17. const argRE = /:(.*)$/
  18. const modifierRE = /\.[^\.]+/g
  19. const forAliasRE = /(.*)\s+(?:in|of)\s+(.*)/
  20. const forIteratorRE = /\((.*),(.*)\)/
  21. const camelRE = /[a-z\d][A-Z]/
  22. const decodeHTMLCached = cached(decodeHTML)
  23. // attributes that should be using props for binding
  24. const mustUseProp = makeMap('value,selected,checked,muted')
  25. // this map covers namespace elements that can appear as template root nodes
  26. const isSVG = makeMap('svg,g,defs,symbol,use,image,text,circle,ellipse,line,path,polygon,polyline,rect', true)
  27. function getTagNamespace (tag) {
  28. if (isSVG(tag)) {
  29. return 'svg'
  30. }
  31. }
  32. // make warning customizable depending on environment.
  33. let warn
  34. const baseWarn = msg => console.error(`[Vue parser]: ${msg}`)
  35. /**
  36. * Convert HTML string to AST.
  37. *
  38. * @param {String} template
  39. * @param {Object} options
  40. * @return {Object}
  41. */
  42. export function parse (template, options) {
  43. options = options || {}
  44. warn = options.warn || baseWarn
  45. const stack = []
  46. let root
  47. let currentParent
  48. let inNamespace = false
  49. let namespaceIndex = -1
  50. let currentNamespace = ''
  51. let inPre = false
  52. let warned = false
  53. parseHTML(template, {
  54. html5: true,
  55. start (tag, attrs, unary) {
  56. // check camelCase tag
  57. if (camelRE.test(tag)) {
  58. process.env.NODE_ENV !== 'production' && warn(
  59. `Found camelCase tag in template: <${tag}>. ` +
  60. `I've converted it to <${hyphenate(tag)}> for you.`
  61. )
  62. tag = hyphenate(tag)
  63. }
  64. tag = tag.toLowerCase()
  65. const element = {
  66. tag,
  67. attrsList: attrs,
  68. attrsMap: makeAttrsMap(attrs),
  69. parent: currentParent,
  70. children: []
  71. }
  72. // check namespace
  73. const namespace = getTagNamespace(tag)
  74. if (inNamespace) {
  75. element.ns = currentNamespace
  76. } else if (namespace) {
  77. element.ns = namespace
  78. inNamespace = true
  79. currentNamespace = namespace
  80. namespaceIndex = stack.length
  81. }
  82. if (!inPre) {
  83. processPre(element)
  84. if (element.pre) {
  85. inPre = true
  86. }
  87. }
  88. if (inPre) {
  89. processRawAttrs(element)
  90. } else {
  91. processFor(element)
  92. processIf(element)
  93. processOnce(element)
  94. // determine whether this is a plain element after
  95. // removing if/for/once attributes
  96. element.plain = !attrs.length
  97. processRender(element)
  98. processSlot(element)
  99. processComponent(element)
  100. processClassBinding(element)
  101. processStyleBinding(element)
  102. processTransition(element)
  103. processAttrs(element)
  104. }
  105. // tree management
  106. if (!root) {
  107. root = element
  108. } else if (process.env.NODE_ENV !== 'production' && !stack.length && !warned) {
  109. warned = true
  110. warn(
  111. `Component template should contain exactly one root element:\n\n${template}`
  112. )
  113. }
  114. if (currentParent) {
  115. if (element.else) {
  116. processElse(element, currentParent)
  117. } else {
  118. currentParent.children.push(element)
  119. }
  120. }
  121. if (!unary) {
  122. currentParent = element
  123. stack.push(element)
  124. }
  125. },
  126. end (tag) {
  127. // remove trailing whitespace
  128. const element = stack[stack.length - 1]
  129. const lastNode = element.children[element.children.length - 1]
  130. if (lastNode && lastNode.text === ' ') element.children.pop()
  131. // pop stack
  132. stack.length -= 1
  133. currentParent = stack[stack.length - 1]
  134. // check namespace state
  135. if (inNamespace && stack.length <= namespaceIndex) {
  136. inNamespace = false
  137. currentNamespace = ''
  138. namespaceIndex = -1
  139. }
  140. // check pre state
  141. if (element.pre) {
  142. inPre = false
  143. }
  144. },
  145. chars (text) {
  146. if (!currentParent) {
  147. if (process.env.NODE_ENV !== 'production' && !warned) {
  148. warned = true
  149. warn(
  150. 'Component template should contain exactly one root element:\n\n' + template
  151. )
  152. }
  153. return
  154. }
  155. text = currentParent.tag === 'pre' || text.trim()
  156. ? decodeHTMLCached(text)
  157. // only preserve whitespace if its not right after a starting tag
  158. : options.preserveWhitespace && currentParent.children.length
  159. ? ' '
  160. : null
  161. if (text) {
  162. let expression
  163. if (!inPre && text !== ' ' && (expression = parseText(text))) {
  164. currentParent.children.push({ expression })
  165. } else {
  166. currentParent.children.push({ text })
  167. }
  168. }
  169. }
  170. })
  171. return root
  172. }
  173. function processPre (el) {
  174. if (getAndRemoveAttr(el, 'v-pre') != null) {
  175. el.pre = true
  176. }
  177. }
  178. function processRawAttrs (el) {
  179. const l = el.attrsList.length
  180. if (l) {
  181. el.attrs = new Array(l)
  182. for (let i = 0; i < l; i++) {
  183. el.attrs[i] = {
  184. name: el.attrsList[i].name,
  185. value: JSON.stringify(el.attrsList[i].value)
  186. }
  187. }
  188. }
  189. }
  190. function processFor (el) {
  191. let exp
  192. if ((exp = getAndRemoveAttr(el, 'v-for'))) {
  193. const inMatch = exp.match(forAliasRE)
  194. if (!inMatch) {
  195. process.env.NODE_ENV !== 'production' && warn(
  196. `Invalid v-for expression: ${exp}`
  197. )
  198. return
  199. }
  200. el.for = inMatch[2].trim()
  201. const alias = inMatch[1].trim()
  202. const iteratorMatch = alias.match(forIteratorRE)
  203. if (iteratorMatch) {
  204. el.iterator = iteratorMatch[1].trim()
  205. el.alias = iteratorMatch[2].trim()
  206. } else {
  207. el.alias = alias
  208. }
  209. if ((exp = getAndRemoveAttr(el, 'track-by'))) {
  210. el.key = exp === '$index'
  211. ? exp
  212. : el.alias + '["' + exp + '"]'
  213. }
  214. }
  215. }
  216. function processIf (el) {
  217. let exp = getAndRemoveAttr(el, 'v-if')
  218. if (exp) {
  219. el.if = exp
  220. }
  221. if (getAndRemoveAttr(el, 'v-else') != null) {
  222. el.else = true
  223. }
  224. }
  225. function processElse (el, parent) {
  226. const prev = findPrevElement(parent.children)
  227. if (prev && (prev.if || prev.attrsMap['v-show'])) {
  228. if (prev.if) {
  229. // v-if
  230. prev.elseBlock = el
  231. } else {
  232. // v-show: simply add a v-show with reversed value
  233. addDirective(el, 'show', `!(${prev.attrsMap['v-show']})`)
  234. // also copy its transition
  235. el.transition = prev.transition
  236. // als set show to true
  237. el.show = true
  238. parent.children.push(el)
  239. }
  240. } else if (process.env.NODE_ENV !== 'production') {
  241. warn(
  242. `v-else used on element <${el.tag}> without corresponding v-if/v-show.`
  243. )
  244. }
  245. }
  246. function processOnce (el) {
  247. const once = getAndRemoveAttr(el, 'v-once')
  248. if (once != null) {
  249. el.once = true
  250. }
  251. }
  252. function processRender (el) {
  253. if (el.tag === 'render') {
  254. el.render = true
  255. el.renderMethod = el.attrsMap.method
  256. el.renderArgs = el.attrsMap[':args'] || el.attrsMap['v-bind:args']
  257. if (process.env.NODE_ENV !== 'production') {
  258. if (!el.renderMethod) {
  259. warn('method attribute is required on <render>.')
  260. }
  261. if (el.attrsMap.args) {
  262. warn('<render> args should use a dynamic binding, e.g. `:args="..."`.')
  263. }
  264. }
  265. }
  266. }
  267. function processSlot (el) {
  268. if (el.tag === 'slot') {
  269. el.slotName = getBindingAttr(el, 'name')
  270. } else {
  271. const slotTarget = getBindingAttr(el, 'slot')
  272. if (slotTarget) {
  273. el.slotTarget = slotTarget
  274. }
  275. }
  276. }
  277. function processComponent (el) {
  278. if (el.tag === 'component') {
  279. el.component = getBindingAttr(el, 'is')
  280. }
  281. }
  282. function processClassBinding (el) {
  283. const staticClass = getAndRemoveAttr(el, 'class')
  284. el.staticClass = parseText(staticClass) || JSON.stringify(staticClass)
  285. const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
  286. if (classBinding) {
  287. el.classBinding = classBinding
  288. }
  289. }
  290. function processStyleBinding (el) {
  291. const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
  292. if (styleBinding) {
  293. el.styleBinding = styleBinding
  294. }
  295. }
  296. function processTransition (el) {
  297. let transition = getBindingAttr(el, 'transition')
  298. if (transition === '""') {
  299. transition = true
  300. }
  301. if (transition) {
  302. el.transition = transition
  303. el.transitionOnAppear = getBindingAttr(el, 'transition-on-appear') != null
  304. }
  305. }
  306. function processAttrs (el) {
  307. const list = el.attrsList
  308. let i, l, name, value, arg, modifiers
  309. for (i = 0, l = list.length; i < l; i++) {
  310. name = list[i].name
  311. value = list[i].value
  312. if (dirRE.test(name)) {
  313. // modifiers
  314. modifiers = parseModifiers(name)
  315. if (modifiers) {
  316. name = name.replace(modifierRE, '')
  317. }
  318. if (bindRE.test(name)) { // v-bind
  319. name = name.replace(bindRE, '')
  320. if (mustUseProp(name)) {
  321. addProp(el, name, value)
  322. } else {
  323. addAttr(el, name, value)
  324. }
  325. } else if (onRE.test(name)) { // v-on
  326. name = name.replace(onRE, '')
  327. addHandler(el, name, value, modifiers)
  328. } else { // normal directives
  329. name = name.replace(dirRE, '')
  330. // parse arg
  331. if ((arg = name.match(argRE)) && (arg = arg[1])) {
  332. name = name.slice(0, -(arg.length + 1))
  333. }
  334. addDirective(el, name, value, arg, modifiers)
  335. }
  336. } else {
  337. // literal attribute
  338. let expression = parseText(value)
  339. if (expression) {
  340. addAttr(el, name, expression)
  341. } else {
  342. addStaticAttr(el, name, JSON.stringify(value))
  343. }
  344. }
  345. }
  346. }
  347. function parseModifiers (name) {
  348. const match = name.match(modifierRE)
  349. if (match) {
  350. const ret = {}
  351. match.forEach(m => { ret[m.slice(1)] = true })
  352. return ret
  353. }
  354. }
  355. function makeAttrsMap (attrs) {
  356. const map = {}
  357. for (let i = 0, l = attrs.length; i < l; i++) {
  358. if (process.env.NODE_ENV !== 'production' && map[attrs[i].name]) {
  359. warn('duplicate attribute: ' + attrs[i].name)
  360. }
  361. map[attrs[i].name] = attrs[i].value
  362. }
  363. return map
  364. }
  365. function findPrevElement (children) {
  366. let i = children.length
  367. while (i--) {
  368. if (children[i].tag) return children[i]
  369. }
  370. }