3ac525d80452987929451cfc2e5c00244be167cd64905a8d4310c9616e1093f1b97f6c82f62bf87f2beeea11ce34da827e673cc0a4a0d14fb3fb370828cc9e 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. /* @flow */
  2. import he from 'he'
  3. import { parseHTML } from './html-parser'
  4. import { parseText } from './text-parser'
  5. import { parseFilters } from './filter-parser'
  6. import { genAssignmentCode } from '../directives/model'
  7. import { extend, cached, no, camelize, hyphenate } from 'shared/util'
  8. import { isIE, isEdge, isServerRendering } from 'core/util/env'
  9. import {
  10. addProp,
  11. addAttr,
  12. baseWarn,
  13. addHandler,
  14. addDirective,
  15. getBindingAttr,
  16. getAndRemoveAttr,
  17. getRawBindingAttr,
  18. pluckModuleFunction,
  19. getAndRemoveAttrByRegex
  20. } from '../helpers'
  21. export const onRE = /^@|^v-on:/
  22. export const dirRE = process.env.VBIND_PROP_SHORTHAND
  23. ? /^v-|^@|^:|^\.|^#/
  24. : /^v-|^@|^:|^#/
  25. export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
  26. export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
  27. const stripParensRE = /^\(|\)$/g
  28. const dynamicArgRE = /^\[.*\]$/
  29. const argRE = /:(.*)$/
  30. export const bindRE = /^:|^\.|^v-bind:/
  31. const propBindRE = /^\./
  32. const modifierRE = /\.[^.\]]+(?=[^\]]*$)/g
  33. const slotRE = /^v-slot(:|$)|^#/
  34. const lineBreakRE = /[\r\n]/
  35. const whitespaceRE = /\s+/g
  36. const invalidAttributeRE = /[\s"'<>\/=]/
  37. const decodeHTMLCached = cached(he.decode)
  38. export const emptySlotScopeToken = `_empty_`
  39. // configurable state
  40. export let warn: any
  41. let delimiters
  42. let transforms
  43. let preTransforms
  44. let postTransforms
  45. let platformIsPreTag
  46. let platformMustUseProp
  47. let platformGetTagNamespace
  48. let maybeComponent
  49. export function createASTElement (
  50. tag: string,
  51. attrs: Array<ASTAttr>,
  52. parent: ASTElement | void
  53. ): ASTElement {
  54. return {
  55. type: 1,
  56. tag,
  57. attrsList: attrs,
  58. attrsMap: makeAttrsMap(attrs),
  59. rawAttrsMap: {},
  60. parent,
  61. children: []
  62. }
  63. }
  64. /**
  65. * Convert HTML string to AST.
  66. */
  67. export function parse (
  68. template: string,
  69. options: CompilerOptions
  70. ): ASTElement | void {
  71. warn = options.warn || baseWarn
  72. platformIsPreTag = options.isPreTag || no
  73. platformMustUseProp = options.mustUseProp || no
  74. platformGetTagNamespace = options.getTagNamespace || no
  75. const isReservedTag = options.isReservedTag || no
  76. maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
  77. transforms = pluckModuleFunction(options.modules, 'transformNode')
  78. preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  79. postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
  80. delimiters = options.delimiters
  81. const stack = []
  82. const preserveWhitespace = options.preserveWhitespace !== false
  83. const whitespaceOption = options.whitespace
  84. let root
  85. let currentParent
  86. let inVPre = false
  87. let inPre = false
  88. let warned = false
  89. function warnOnce (msg, range) {
  90. if (!warned) {
  91. warned = true
  92. warn(msg, range)
  93. }
  94. }
  95. function closeElement (element) {
  96. trimEndingWhitespace(element)
  97. if (!inVPre && !element.processed) {
  98. element = processElement(element, options)
  99. }
  100. // tree management
  101. if (!stack.length && element !== root) {
  102. // allow root elements with v-if, v-else-if and v-else
  103. if (root.if && (element.elseif || element.else)) {
  104. if (process.env.NODE_ENV !== 'production') {
  105. checkRootConstraints(element)
  106. }
  107. addIfCondition(root, {
  108. exp: element.elseif,
  109. block: element
  110. })
  111. } else if (process.env.NODE_ENV !== 'production') {
  112. warnOnce(
  113. `Component template should contain exactly one root element. ` +
  114. `If you are using v-if on multiple elements, ` +
  115. `use v-else-if to chain them instead.`,
  116. { start: element.start }
  117. )
  118. }
  119. }
  120. if (currentParent && !element.forbidden) {
  121. if (element.elseif || element.else) {
  122. processIfConditions(element, currentParent)
  123. } else {
  124. if (element.slotScope) {
  125. // scoped slot
  126. // keep it in the children list so that v-else(-if) conditions can
  127. // find it as the prev node.
  128. const name = element.slotTarget || '"default"'
  129. ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
  130. }
  131. currentParent.children.push(element)
  132. element.parent = currentParent
  133. }
  134. }
  135. // final children cleanup
  136. // filter out scoped slots
  137. element.children = element.children.filter(c => !(c: any).slotScope)
  138. // remove trailing whitespace node again
  139. trimEndingWhitespace(element)
  140. // check pre state
  141. if (element.pre) {
  142. inVPre = false
  143. }
  144. if (platformIsPreTag(element.tag)) {
  145. inPre = false
  146. }
  147. // apply post-transforms
  148. for (let i = 0; i < postTransforms.length; i++) {
  149. postTransforms[i](element, options)
  150. }
  151. }
  152. function trimEndingWhitespace (el) {
  153. // remove trailing whitespace node
  154. if (!inPre) {
  155. let lastNode
  156. while (
  157. (lastNode = el.children[el.children.length - 1]) &&
  158. lastNode.type === 3 &&
  159. lastNode.text === ' '
  160. ) {
  161. el.children.pop()
  162. }
  163. }
  164. }
  165. function checkRootConstraints (el) {
  166. if (el.tag === 'slot' || el.tag === 'template') {
  167. warnOnce(
  168. `Cannot use <${el.tag}> as component root element because it may ` +
  169. 'contain multiple nodes.',
  170. { start: el.start }
  171. )
  172. }
  173. if (el.attrsMap.hasOwnProperty('v-for')) {
  174. warnOnce(
  175. 'Cannot use v-for on stateful component root element because ' +
  176. 'it renders multiple elements.',
  177. el.rawAttrsMap['v-for']
  178. )
  179. }
  180. }
  181. parseHTML(template, {
  182. warn,
  183. expectHTML: options.expectHTML,
  184. isUnaryTag: options.isUnaryTag,
  185. canBeLeftOpenTag: options.canBeLeftOpenTag,
  186. shouldDecodeNewlines: options.shouldDecodeNewlines,
  187. shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
  188. shouldKeepComment: options.comments,
  189. outputSourceRange: options.outputSourceRange,
  190. start (tag, attrs, unary, start, end) {
  191. // check namespace.
  192. // inherit parent ns if there is one
  193. const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
  194. // handle IE svg bug
  195. /* istanbul ignore if */
  196. if (isIE && ns === 'svg') {
  197. attrs = guardIESVGBug(attrs)
  198. }
  199. let element: ASTElement = createASTElement(tag, attrs, currentParent)
  200. if (ns) {
  201. element.ns = ns
  202. }
  203. if (process.env.NODE_ENV !== 'production') {
  204. if (options.outputSourceRange) {
  205. element.start = start
  206. element.end = end
  207. element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
  208. cumulated[attr.name] = attr
  209. return cumulated
  210. }, {})
  211. }
  212. attrs.forEach(attr => {
  213. if (invalidAttributeRE.test(attr.name)) {
  214. warn(
  215. `Invalid dynamic argument expression: attribute names cannot contain ` +
  216. `spaces, quotes, <, >, / or =.`,
  217. {
  218. start: attr.start + attr.name.indexOf(`[`),
  219. end: attr.start + attr.name.length
  220. }
  221. )
  222. }
  223. })
  224. }
  225. if (isForbiddenTag(element) && !isServerRendering()) {
  226. element.forbidden = true
  227. process.env.NODE_ENV !== 'production' && warn(
  228. 'Templates should only be responsible for mapping the state to the ' +
  229. 'UI. Avoid placing tags with side-effects in your templates, such as ' +
  230. `<${tag}>` + ', as they will not be parsed.',
  231. { start: element.start }
  232. )
  233. }
  234. // apply pre-transforms
  235. for (let i = 0; i < preTransforms.length; i++) {
  236. element = preTransforms[i](element, options) || element
  237. }
  238. if (!inVPre) {
  239. processPre(element)
  240. if (element.pre) {
  241. inVPre = true
  242. }
  243. }
  244. if (platformIsPreTag(element.tag)) {
  245. inPre = true
  246. }
  247. if (inVPre) {
  248. processRawAttrs(element)
  249. } else if (!element.processed) {
  250. // structural directives
  251. processFor(element)
  252. processIf(element)
  253. processOnce(element)
  254. }
  255. if (!root) {
  256. root = element
  257. if (process.env.NODE_ENV !== 'production') {
  258. checkRootConstraints(root)
  259. }
  260. }
  261. if (!unary) {
  262. currentParent = element
  263. stack.push(element)
  264. } else {
  265. closeElement(element)
  266. }
  267. },
  268. end (tag, start, end) {
  269. const element = stack[stack.length - 1]
  270. // pop stack
  271. stack.length -= 1
  272. currentParent = stack[stack.length - 1]
  273. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
  274. element.end = end
  275. }
  276. closeElement(element)
  277. },
  278. chars (text: string, start: number, end: number) {
  279. if (!currentParent) {
  280. if (process.env.NODE_ENV !== 'production') {
  281. if (text === template) {
  282. warnOnce(
  283. 'Component template requires a root element, rather than just text.',
  284. { start }
  285. )
  286. } else if ((text = text.trim())) {
  287. warnOnce(
  288. `text "${text}" outside root element will be ignored.`,
  289. { start }
  290. )
  291. }
  292. }
  293. return
  294. }
  295. // IE textarea placeholder bug
  296. /* istanbul ignore if */
  297. if (isIE &&
  298. currentParent.tag === 'textarea' &&
  299. currentParent.attrsMap.placeholder === text
  300. ) {
  301. return
  302. }
  303. const children = currentParent.children
  304. if (inPre || text.trim()) {
  305. text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
  306. } else if (!children.length) {
  307. // remove the whitespace-only node right after an opening tag
  308. text = ''
  309. } else if (whitespaceOption) {
  310. if (whitespaceOption === 'condense') {
  311. // in condense mode, remove the whitespace node if it contains
  312. // line break, otherwise condense to a single space
  313. text = lineBreakRE.test(text) ? '' : ' '
  314. } else {
  315. text = ' '
  316. }
  317. } else {
  318. text = preserveWhitespace ? ' ' : ''
  319. }
  320. if (text) {
  321. if (!inPre && whitespaceOption === 'condense') {
  322. // condense consecutive whitespaces into single space
  323. text = text.replace(whitespaceRE, ' ')
  324. }
  325. let res
  326. let child: ?ASTNode
  327. if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
  328. child = {
  329. type: 2,
  330. expression: res.expression,
  331. tokens: res.tokens,
  332. text
  333. }
  334. } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
  335. child = {
  336. type: 3,
  337. text
  338. }
  339. }
  340. if (child) {
  341. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
  342. child.start = start
  343. child.end = end
  344. }
  345. children.push(child)
  346. }
  347. }
  348. },
  349. comment (text: string, start, end) {
  350. // adding anything as a sibling to the root node is forbidden
  351. // comments should still be allowed, but ignored
  352. if (currentParent) {
  353. const child: ASTText = {
  354. type: 3,
  355. text,
  356. isComment: true
  357. }
  358. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
  359. child.start = start
  360. child.end = end
  361. }
  362. currentParent.children.push(child)
  363. }
  364. }
  365. })
  366. return root
  367. }
  368. function processPre (el) {
  369. if (getAndRemoveAttr(el, 'v-pre') != null) {
  370. el.pre = true
  371. }
  372. }
  373. function processRawAttrs (el) {
  374. const list = el.attrsList
  375. const len = list.length
  376. if (len) {
  377. const attrs: Array<ASTAttr> = el.attrs = new Array(len)
  378. for (let i = 0; i < len; i++) {
  379. attrs[i] = {
  380. name: list[i].name,
  381. value: JSON.stringify(list[i].value)
  382. }
  383. if (list[i].start != null) {
  384. attrs[i].start = list[i].start
  385. attrs[i].end = list[i].end
  386. }
  387. }
  388. } else if (!el.pre) {
  389. // non root node in pre blocks with no attributes
  390. el.plain = true
  391. }
  392. }
  393. export function processElement (
  394. element: ASTElement,
  395. options: CompilerOptions
  396. ) {
  397. processKey(element)
  398. // determine whether this is a plain element after
  399. // removing structural attributes
  400. element.plain = (
  401. !element.key &&
  402. !element.scopedSlots &&
  403. !element.attrsList.length
  404. )
  405. processRef(element)
  406. processSlotContent(element)
  407. processSlotOutlet(element)
  408. processComponent(element)
  409. for (let i = 0; i < transforms.length; i++) {
  410. element = transforms[i](element, options) || element
  411. }
  412. processAttrs(element)
  413. return element
  414. }
  415. function processKey (el) {
  416. const exp = getBindingAttr(el, 'key')
  417. if (exp) {
  418. if (process.env.NODE_ENV !== 'production') {
  419. if (el.tag === 'template') {
  420. warn(
  421. `<template> cannot be keyed. Place the key on real elements instead.`,
  422. getRawBindingAttr(el, 'key')
  423. )
  424. }
  425. if (el.for) {
  426. const iterator = el.iterator2 || el.iterator1
  427. const parent = el.parent
  428. if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
  429. warn(
  430. `Do not use v-for index as key on <transition-group> children, ` +
  431. `this is the same as not using keys.`,
  432. getRawBindingAttr(el, 'key'),
  433. true /* tip */
  434. )
  435. }
  436. }
  437. }
  438. el.key = exp
  439. }
  440. }
  441. function processRef (el) {
  442. const ref = getBindingAttr(el, 'ref')
  443. if (ref) {
  444. el.ref = ref
  445. el.refInFor = checkInFor(el)
  446. }
  447. }
  448. export function processFor (el: ASTElement) {
  449. let exp
  450. if ((exp = getAndRemoveAttr(el, 'v-for'))) {
  451. const res = parseFor(exp)
  452. if (res) {
  453. extend(el, res)
  454. } else if (process.env.NODE_ENV !== 'production') {
  455. warn(
  456. `Invalid v-for expression: ${exp}`,
  457. el.rawAttrsMap['v-for']
  458. )
  459. }
  460. }
  461. }
  462. type ForParseResult = {
  463. for: string;
  464. alias: string;
  465. iterator1?: string;
  466. iterator2?: string;
  467. };
  468. export function parseFor (exp: string): ?ForParseResult {
  469. const inMatch = exp.match(forAliasRE)
  470. if (!inMatch) return
  471. const res = {}
  472. res.for = inMatch[2].trim()
  473. const alias = inMatch[1].trim().replace(stripParensRE, '')
  474. const iteratorMatch = alias.match(forIteratorRE)
  475. if (iteratorMatch) {
  476. res.alias = alias.replace(forIteratorRE, '').trim()
  477. res.iterator1 = iteratorMatch[1].trim()
  478. if (iteratorMatch[2]) {
  479. res.iterator2 = iteratorMatch[2].trim()
  480. }
  481. } else {
  482. res.alias = alias
  483. }
  484. return res
  485. }
  486. function processIf (el) {
  487. const exp = getAndRemoveAttr(el, 'v-if')
  488. if (exp) {
  489. el.if = exp
  490. addIfCondition(el, {
  491. exp: exp,
  492. block: el
  493. })
  494. } else {
  495. if (getAndRemoveAttr(el, 'v-else') != null) {
  496. el.else = true
  497. }
  498. const elseif = getAndRemoveAttr(el, 'v-else-if')
  499. if (elseif) {
  500. el.elseif = elseif
  501. }
  502. }
  503. }
  504. function processIfConditions (el, parent) {
  505. const prev = findPrevElement(parent.children)
  506. if (prev && prev.if) {
  507. addIfCondition(prev, {
  508. exp: el.elseif,
  509. block: el
  510. })
  511. } else if (process.env.NODE_ENV !== 'production') {
  512. warn(
  513. `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
  514. `used on element <${el.tag}> without corresponding v-if.`,
  515. el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
  516. )
  517. }
  518. }
  519. function findPrevElement (children: Array<any>): ASTElement | void {
  520. let i = children.length
  521. while (i--) {
  522. if (children[i].type === 1) {
  523. return children[i]
  524. } else {
  525. if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
  526. warn(
  527. `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
  528. `will be ignored.`,
  529. children[i]
  530. )
  531. }
  532. children.pop()
  533. }
  534. }
  535. }
  536. export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
  537. if (!el.ifConditions) {
  538. el.ifConditions = []
  539. }
  540. el.ifConditions.push(condition)
  541. }
  542. function processOnce (el) {
  543. const once = getAndRemoveAttr(el, 'v-once')
  544. if (once != null) {
  545. el.once = true
  546. }
  547. }
  548. // handle content being passed to a component as slot,
  549. // e.g. <template slot="xxx">, <div slot-scope="xxx">
  550. function processSlotContent (el) {
  551. let slotScope
  552. if (el.tag === 'template') {
  553. slotScope = getAndRemoveAttr(el, 'scope')
  554. /* istanbul ignore if */
  555. if (process.env.NODE_ENV !== 'production' && slotScope) {
  556. warn(
  557. `the "scope" attribute for scoped slots have been deprecated and ` +
  558. `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
  559. `can also be used on plain elements in addition to <template> to ` +
  560. `denote scoped slots.`,
  561. el.rawAttrsMap['scope'],
  562. true
  563. )
  564. }
  565. el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
  566. } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
  567. /* istanbul ignore if */
  568. if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
  569. warn(
  570. `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
  571. `(v-for takes higher priority). Use a wrapper <template> for the ` +
  572. `scoped slot to make it clearer.`,
  573. el.rawAttrsMap['slot-scope'],
  574. true
  575. )
  576. }
  577. el.slotScope = slotScope
  578. }
  579. // slot="xxx"
  580. const slotTarget = getBindingAttr(el, 'slot')
  581. if (slotTarget) {
  582. el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
  583. el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
  584. // preserve slot as an attribute for native shadow DOM compat
  585. // only for non-scoped slots.
  586. if (el.tag !== 'template' && !el.slotScope) {
  587. addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
  588. }
  589. }
  590. // 2.6 v-slot syntax
  591. if (process.env.NEW_SLOT_SYNTAX) {
  592. if (el.tag === 'template') {
  593. // v-slot on <template>
  594. const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
  595. if (slotBinding) {
  596. if (process.env.NODE_ENV !== 'production') {
  597. if (el.slotTarget || el.slotScope) {
  598. warn(
  599. `Unexpected mixed usage of different slot syntaxes.`,
  600. el
  601. )
  602. }
  603. if (el.parent && !maybeComponent(el.parent)) {
  604. warn(
  605. `<template v-slot> can only appear at the root level inside ` +
  606. `the receiving component`,
  607. el
  608. )
  609. }
  610. }
  611. const { name, dynamic } = getSlotName(slotBinding)
  612. el.slotTarget = name
  613. el.slotTargetDynamic = dynamic
  614. el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
  615. }
  616. } else {
  617. // v-slot on component, denotes default slot
  618. const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
  619. if (slotBinding) {
  620. if (process.env.NODE_ENV !== 'production') {
  621. if (!maybeComponent(el)) {
  622. warn(
  623. `v-slot can only be used on components or <template>.`,
  624. slotBinding
  625. )
  626. }
  627. if (el.slotScope || el.slotTarget) {
  628. warn(
  629. `Unexpected mixed usage of different slot syntaxes.`,
  630. el
  631. )
  632. }
  633. if (el.scopedSlots) {
  634. warn(
  635. `To avoid scope ambiguity, the default slot should also use ` +
  636. `<template> syntax when there are other named slots.`,
  637. slotBinding
  638. )
  639. }
  640. }
  641. // add the component's children to its default slot
  642. const slots = el.scopedSlots || (el.scopedSlots = {})
  643. const { name, dynamic } = getSlotName(slotBinding)
  644. const slotContainer = slots[name] = createASTElement('template', [], el)
  645. slotContainer.slotTarget = name
  646. slotContainer.slotTargetDynamic = dynamic
  647. slotContainer.children = el.children.filter((c: any) => {
  648. if (!c.slotScope) {
  649. c.parent = slotContainer
  650. return true
  651. }
  652. })
  653. slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
  654. // remove children as they are returned from scopedSlots now
  655. el.children = []
  656. // mark el non-plain so data gets generated
  657. el.plain = false
  658. }
  659. }
  660. }
  661. }
  662. function getSlotName (binding) {
  663. let name = binding.name.replace(slotRE, '')
  664. if (!name) {
  665. if (binding.name[0] !== '#') {
  666. name = 'default'
  667. } else if (process.env.NODE_ENV !== 'production') {
  668. warn(
  669. `v-slot shorthand syntax requires a slot name.`,
  670. binding
  671. )
  672. }
  673. }
  674. return dynamicArgRE.test(name)
  675. // dynamic [name]
  676. ? { name: name.slice(1, -1), dynamic: true }
  677. // static name
  678. : { name: `"${name}"`, dynamic: false }
  679. }
  680. // handle <slot/> outlets
  681. function processSlotOutlet (el) {
  682. if (el.tag === 'slot') {
  683. el.slotName = getBindingAttr(el, 'name')
  684. if (process.env.NODE_ENV !== 'production' && el.key) {
  685. warn(
  686. `\`key\` does not work on <slot> because slots are abstract outlets ` +
  687. `and can possibly expand into multiple elements. ` +
  688. `Use the key on a wrapping element instead.`,
  689. getRawBindingAttr(el, 'key')
  690. )
  691. }
  692. }
  693. }
  694. function processComponent (el) {
  695. let binding
  696. if ((binding = getBindingAttr(el, 'is'))) {
  697. el.component = binding
  698. }
  699. if (getAndRemoveAttr(el, 'inline-template') != null) {
  700. el.inlineTemplate = true
  701. }
  702. }
  703. function processAttrs (el) {
  704. const list = el.attrsList
  705. let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  706. for (i = 0, l = list.length; i < l; i++) {
  707. name = rawName = list[i].name
  708. value = list[i].value
  709. if (dirRE.test(name)) {
  710. // mark element as dynamic
  711. el.hasBindings = true
  712. // modifiers
  713. modifiers = parseModifiers(name.replace(dirRE, ''))
  714. // support .foo shorthand syntax for the .prop modifier
  715. if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
  716. (modifiers || (modifiers = {})).prop = true
  717. name = `.` + name.slice(1).replace(modifierRE, '')
  718. } else if (modifiers) {
  719. name = name.replace(modifierRE, '')
  720. }
  721. if (bindRE.test(name)) { // v-bind
  722. name = name.replace(bindRE, '')
  723. value = parseFilters(value)
  724. isDynamic = dynamicArgRE.test(name)
  725. if (isDynamic) {
  726. name = name.slice(1, -1)
  727. }
  728. if (
  729. process.env.NODE_ENV !== 'production' &&
  730. value.trim().length === 0
  731. ) {
  732. warn(
  733. `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
  734. )
  735. }
  736. if (modifiers) {
  737. if (modifiers.prop && !isDynamic) {
  738. name = camelize(name)
  739. if (name === 'innerHtml') name = 'innerHTML'
  740. }
  741. if (modifiers.camel && !isDynamic) {
  742. name = camelize(name)
  743. }
  744. if (modifiers.sync) {
  745. syncGen = genAssignmentCode(value, `$event`)
  746. if (!isDynamic) {
  747. addHandler(
  748. el,
  749. `update:${camelize(name)}`,
  750. syncGen,
  751. null,
  752. false,
  753. warn,
  754. list[i]
  755. )
  756. if (hyphenate(name) !== camelize(name)) {
  757. addHandler(
  758. el,
  759. `update:${hyphenate(name)}`,
  760. syncGen,
  761. null,
  762. false,
  763. warn,
  764. list[i]
  765. )
  766. }
  767. } else {
  768. // handler w/ dynamic event name
  769. addHandler(
  770. el,
  771. `"update:"+(${name})`,
  772. syncGen,
  773. null,
  774. false,
  775. warn,
  776. list[i],
  777. true // dynamic
  778. )
  779. }
  780. }
  781. }
  782. if ((modifiers && modifiers.prop) || (
  783. !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
  784. )) {
  785. addProp(el, name, value, list[i], isDynamic)
  786. } else {
  787. addAttr(el, name, value, list[i], isDynamic)
  788. }
  789. } else if (onRE.test(name)) { // v-on
  790. name = name.replace(onRE, '')
  791. isDynamic = dynamicArgRE.test(name)
  792. if (isDynamic) {
  793. name = name.slice(1, -1)
  794. }
  795. addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
  796. } else { // normal directives
  797. name = name.replace(dirRE, '')
  798. // parse arg
  799. const argMatch = name.match(argRE)
  800. let arg = argMatch && argMatch[1]
  801. isDynamic = false
  802. if (arg) {
  803. name = name.slice(0, -(arg.length + 1))
  804. if (dynamicArgRE.test(arg)) {
  805. arg = arg.slice(1, -1)
  806. isDynamic = true
  807. }
  808. }
  809. addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
  810. if (process.env.NODE_ENV !== 'production' && name === 'model') {
  811. checkForAliasModel(el, value)
  812. }
  813. }
  814. } else {
  815. // literal attribute
  816. if (process.env.NODE_ENV !== 'production') {
  817. const res = parseText(value, delimiters)
  818. if (res) {
  819. warn(
  820. `${name}="${value}": ` +
  821. 'Interpolation inside attributes has been removed. ' +
  822. 'Use v-bind or the colon shorthand instead. For example, ' +
  823. 'instead of <div id="{{ val }}">, use <div :id="val">.',
  824. list[i]
  825. )
  826. }
  827. }
  828. addAttr(el, name, JSON.stringify(value), list[i])
  829. // #6887 firefox doesn't update muted state if set via attribute
  830. // even immediately after element creation
  831. if (!el.component &&
  832. name === 'muted' &&
  833. platformMustUseProp(el.tag, el.attrsMap.type, name)) {
  834. addProp(el, name, 'true', list[i])
  835. }
  836. }
  837. }
  838. }
  839. function checkInFor (el: ASTElement): boolean {
  840. let parent = el
  841. while (parent) {
  842. if (parent.for !== undefined) {
  843. return true
  844. }
  845. parent = parent.parent
  846. }
  847. return false
  848. }
  849. function parseModifiers (name: string): Object | void {
  850. const match = name.match(modifierRE)
  851. if (match) {
  852. const ret = {}
  853. match.forEach(m => { ret[m.slice(1)] = true })
  854. return ret
  855. }
  856. }
  857. function makeAttrsMap (attrs: Array<Object>): Object {
  858. const map = {}
  859. for (let i = 0, l = attrs.length; i < l; i++) {
  860. if (
  861. process.env.NODE_ENV !== 'production' &&
  862. map[attrs[i].name] && !isIE && !isEdge
  863. ) {
  864. warn('duplicate attribute: ' + attrs[i].name, attrs[i])
  865. }
  866. map[attrs[i].name] = attrs[i].value
  867. }
  868. return map
  869. }
  870. // for script (e.g. type="x/template") or style, do not decode content
  871. function isTextTag (el): boolean {
  872. return el.tag === 'script' || el.tag === 'style'
  873. }
  874. function isForbiddenTag (el): boolean {
  875. return (
  876. el.tag === 'style' ||
  877. (el.tag === 'script' && (
  878. !el.attrsMap.type ||
  879. el.attrsMap.type === 'text/javascript'
  880. ))
  881. )
  882. }
  883. const ieNSBug = /^xmlns:NS\d+/
  884. const ieNSPrefix = /^NS\d+:/
  885. /* istanbul ignore next */
  886. function guardIESVGBug (attrs) {
  887. const res = []
  888. for (let i = 0; i < attrs.length; i++) {
  889. const attr = attrs[i]
  890. if (!ieNSBug.test(attr.name)) {
  891. attr.name = attr.name.replace(ieNSPrefix, '')
  892. res.push(attr)
  893. }
  894. }
  895. return res
  896. }
  897. function checkForAliasModel (el, value) {
  898. let _el = el
  899. while (_el) {
  900. if (_el.for && _el.alias === value) {
  901. warn(
  902. `<${el.tag} v-model="${value}">: ` +
  903. `You are binding v-model directly to a v-for iteration alias. ` +
  904. `This will not be able to modify the v-for source array because ` +
  905. `writing to the alias is like modifying a function local variable. ` +
  906. `Consider using an array of objects and use v-model on an object property instead.`,
  907. el.rawAttrsMap['v-model']
  908. )
  909. }
  910. _el = _el.parent
  911. }
  912. }