compile.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. import publicDirectives from '../directives/public/index'
  2. import internalDirectives from '../directives/internal/index'
  3. import { compileProps } from './compile-props'
  4. import { parseText, tokensToExp } from '../parsers/text'
  5. import { parseDirective } from '../parsers/directive'
  6. import { parseTemplate } from '../parsers/template'
  7. import { resolveAsset } from '../util/index'
  8. import {
  9. toArray,
  10. warn,
  11. remove,
  12. replace,
  13. commonTagRE,
  14. checkComponentAttr,
  15. findRef,
  16. defineReactive,
  17. getAttr
  18. } from '../util/index'
  19. // special binding prefixes
  20. const bindRE = /^v-bind:|^:/
  21. const onRE = /^v-on:|^@/
  22. const dirAttrRE = /^v-([^:]+)(?:$|:(.*)$)/
  23. const modifierRE = /\.[^\.]+/g
  24. const transitionRE = /^(v-bind:|:)?transition$/
  25. // default directive priority
  26. const DEFAULT_PRIORITY = 1000
  27. const DEFAULT_TERMINAL_PRIORITY = 2000
  28. /**
  29. * Compile a template and return a reusable composite link
  30. * function, which recursively contains more link functions
  31. * inside. This top level compile function would normally
  32. * be called on instance root nodes, but can also be used
  33. * for partial compilation if the partial argument is true.
  34. *
  35. * The returned composite link function, when called, will
  36. * return an unlink function that tearsdown all directives
  37. * created during the linking phase.
  38. *
  39. * @param {Element|DocumentFragment} el
  40. * @param {Object} options
  41. * @param {Boolean} partial
  42. * @return {Function}
  43. */
  44. export function compile (el, options, partial) {
  45. // link function for the node itself.
  46. var nodeLinkFn = partial || !options._asComponent
  47. ? compileNode(el, options)
  48. : null
  49. // link function for the childNodes
  50. var childLinkFn =
  51. !(nodeLinkFn && nodeLinkFn.terminal) &&
  52. el.tagName !== 'SCRIPT' &&
  53. el.hasChildNodes()
  54. ? compileNodeList(el.childNodes, options)
  55. : null
  56. /**
  57. * A composite linker function to be called on a already
  58. * compiled piece of DOM, which instantiates all directive
  59. * instances.
  60. *
  61. * @param {Vue} vm
  62. * @param {Element|DocumentFragment} el
  63. * @param {Vue} [host] - host vm of transcluded content
  64. * @param {Object} [scope] - v-for scope
  65. * @param {Fragment} [frag] - link context fragment
  66. * @return {Function|undefined}
  67. */
  68. return function compositeLinkFn (vm, el, host, scope, frag) {
  69. // cache childNodes before linking parent, fix #657
  70. var childNodes = toArray(el.childNodes)
  71. // link
  72. var dirs = linkAndCapture(function compositeLinkCapturer () {
  73. if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
  74. if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
  75. }, vm)
  76. return makeUnlinkFn(vm, dirs)
  77. }
  78. }
  79. /**
  80. * Apply a linker to a vm/element pair and capture the
  81. * directives created during the process.
  82. *
  83. * @param {Function} linker
  84. * @param {Vue} vm
  85. */
  86. function linkAndCapture (linker, vm) {
  87. /* istanbul ignore if */
  88. if (process.env.NODE_ENV === 'production') {
  89. // reset directives before every capture in production
  90. // mode, so that when unlinking we don't need to splice
  91. // them out (which turns out to be a perf hit).
  92. // they are kept in development mode because they are
  93. // useful for Vue's own tests.
  94. vm._directives = []
  95. }
  96. var originalDirCount = vm._directives.length
  97. linker()
  98. var dirs = vm._directives.slice(originalDirCount)
  99. dirs.sort(directiveComparator)
  100. for (var i = 0, l = dirs.length; i < l; i++) {
  101. dirs[i]._bind()
  102. }
  103. return dirs
  104. }
  105. /**
  106. * Directive priority sort comparator
  107. *
  108. * @param {Object} a
  109. * @param {Object} b
  110. */
  111. function directiveComparator (a, b) {
  112. a = a.descriptor.def.priority || DEFAULT_PRIORITY
  113. b = b.descriptor.def.priority || DEFAULT_PRIORITY
  114. return a > b ? -1 : a === b ? 0 : 1
  115. }
  116. /**
  117. * Linker functions return an unlink function that
  118. * tearsdown all directives instances generated during
  119. * the process.
  120. *
  121. * We create unlink functions with only the necessary
  122. * information to avoid retaining additional closures.
  123. *
  124. * @param {Vue} vm
  125. * @param {Array} dirs
  126. * @param {Vue} [context]
  127. * @param {Array} [contextDirs]
  128. * @return {Function}
  129. */
  130. function makeUnlinkFn (vm, dirs, context, contextDirs) {
  131. function unlink (destroying) {
  132. teardownDirs(vm, dirs, destroying)
  133. if (context && contextDirs) {
  134. teardownDirs(context, contextDirs)
  135. }
  136. }
  137. // expose linked directives
  138. unlink.dirs = dirs
  139. return unlink
  140. }
  141. /**
  142. * Teardown partial linked directives.
  143. *
  144. * @param {Vue} vm
  145. * @param {Array} dirs
  146. * @param {Boolean} destroying
  147. */
  148. function teardownDirs (vm, dirs, destroying) {
  149. var i = dirs.length
  150. while (i--) {
  151. dirs[i]._teardown()
  152. if (process.env.NODE_ENV !== 'production' && !destroying) {
  153. vm._directives.$remove(dirs[i])
  154. }
  155. }
  156. }
  157. /**
  158. * Compile link props on an instance.
  159. *
  160. * @param {Vue} vm
  161. * @param {Element} el
  162. * @param {Object} props
  163. * @param {Object} [scope]
  164. * @return {Function}
  165. */
  166. export function compileAndLinkProps (vm, el, props, scope) {
  167. var propsLinkFn = compileProps(el, props, vm)
  168. var propDirs = linkAndCapture(function () {
  169. propsLinkFn(vm, scope)
  170. }, vm)
  171. return makeUnlinkFn(vm, propDirs)
  172. }
  173. /**
  174. * Compile the root element of an instance.
  175. *
  176. * 1. attrs on context container (context scope)
  177. * 2. attrs on the component template root node, if
  178. * replace:true (child scope)
  179. *
  180. * If this is a fragment instance, we only need to compile 1.
  181. *
  182. * @param {Element} el
  183. * @param {Object} options
  184. * @param {Object} contextOptions
  185. * @return {Function}
  186. */
  187. export function compileRoot (el, options, contextOptions) {
  188. var containerAttrs = options._containerAttrs
  189. var replacerAttrs = options._replacerAttrs
  190. var contextLinkFn, replacerLinkFn
  191. // only need to compile other attributes for
  192. // non-fragment instances
  193. if (el.nodeType !== 11) {
  194. // for components, container and replacer need to be
  195. // compiled separately and linked in different scopes.
  196. if (options._asComponent) {
  197. // 2. container attributes
  198. if (containerAttrs && contextOptions) {
  199. contextLinkFn = compileDirectives(containerAttrs, contextOptions)
  200. }
  201. if (replacerAttrs) {
  202. // 3. replacer attributes
  203. replacerLinkFn = compileDirectives(replacerAttrs, options)
  204. }
  205. } else {
  206. // non-component, just compile as a normal element.
  207. replacerLinkFn = compileDirectives(el.attributes, options)
  208. }
  209. } else if (process.env.NODE_ENV !== 'production' && containerAttrs) {
  210. // warn container directives for fragment instances
  211. var names = containerAttrs
  212. .filter(function (attr) {
  213. // allow vue-loader/vueify scoped css attributes
  214. return attr.name.indexOf('_v-') < 0 &&
  215. // allow event listeners
  216. !onRE.test(attr.name) &&
  217. // allow slots
  218. attr.name !== 'slot'
  219. })
  220. .map(function (attr) {
  221. return '"' + attr.name + '"'
  222. })
  223. if (names.length) {
  224. var plural = names.length > 1
  225. warn(
  226. 'Attribute' + (plural ? 's ' : ' ') + names.join(', ') +
  227. (plural ? ' are' : ' is') + ' ignored on component ' +
  228. '<' + options.el.tagName.toLowerCase() + '> because ' +
  229. 'the component is a fragment instance: ' +
  230. 'http://vuejs.org/guide/components.html#Fragment-Instance'
  231. )
  232. }
  233. }
  234. options._containerAttrs = options._replacerAttrs = null
  235. return function rootLinkFn (vm, el, scope) {
  236. // link context scope dirs
  237. var context = vm._context
  238. var contextDirs
  239. if (context && contextLinkFn) {
  240. contextDirs = linkAndCapture(function () {
  241. contextLinkFn(context, el, null, scope)
  242. }, context)
  243. }
  244. // link self
  245. var selfDirs = linkAndCapture(function () {
  246. if (replacerLinkFn) replacerLinkFn(vm, el)
  247. }, vm)
  248. // return the unlink function that tearsdown context
  249. // container directives.
  250. return makeUnlinkFn(vm, selfDirs, context, contextDirs)
  251. }
  252. }
  253. /**
  254. * Compile a node and return a nodeLinkFn based on the
  255. * node type.
  256. *
  257. * @param {Node} node
  258. * @param {Object} options
  259. * @return {Function|null}
  260. */
  261. function compileNode (node, options) {
  262. var type = node.nodeType
  263. if (type === 1 && node.tagName !== 'SCRIPT') {
  264. return compileElement(node, options)
  265. } else if (type === 3 && node.data.trim()) {
  266. return compileTextNode(node, options)
  267. } else {
  268. return null
  269. }
  270. }
  271. /**
  272. * Compile an element and return a nodeLinkFn.
  273. *
  274. * @param {Element} el
  275. * @param {Object} options
  276. * @return {Function|null}
  277. */
  278. function compileElement (el, options) {
  279. // preprocess textareas.
  280. // textarea treats its text content as the initial value.
  281. // just bind it as an attr directive for value.
  282. if (el.tagName === 'TEXTAREA') {
  283. var tokens = parseText(el.value)
  284. if (tokens) {
  285. el.setAttribute(':value', tokensToExp(tokens))
  286. el.value = ''
  287. }
  288. }
  289. var linkFn
  290. var hasAttrs = el.hasAttributes()
  291. var attrs = hasAttrs && toArray(el.attributes)
  292. // check terminal directives (for & if)
  293. if (hasAttrs) {
  294. linkFn = checkTerminalDirectives(el, attrs, options)
  295. }
  296. // check element directives
  297. if (!linkFn) {
  298. linkFn = checkElementDirectives(el, options)
  299. }
  300. // check component
  301. if (!linkFn) {
  302. linkFn = checkComponent(el, options)
  303. }
  304. // normal directives
  305. if (!linkFn && hasAttrs) {
  306. linkFn = compileDirectives(attrs, options)
  307. }
  308. return linkFn
  309. }
  310. /**
  311. * Compile a textNode and return a nodeLinkFn.
  312. *
  313. * @param {TextNode} node
  314. * @param {Object} options
  315. * @return {Function|null} textNodeLinkFn
  316. */
  317. function compileTextNode (node, options) {
  318. // skip marked text nodes
  319. if (node._skip) {
  320. return removeText
  321. }
  322. var tokens = parseText(node.wholeText)
  323. if (!tokens) {
  324. return null
  325. }
  326. // mark adjacent text nodes as skipped,
  327. // because we are using node.wholeText to compile
  328. // all adjacent text nodes together. This fixes
  329. // issues in IE where sometimes it splits up a single
  330. // text node into multiple ones.
  331. var next = node.nextSibling
  332. while (next && next.nodeType === 3) {
  333. next._skip = true
  334. next = next.nextSibling
  335. }
  336. var frag = document.createDocumentFragment()
  337. var el, token
  338. for (var i = 0, l = tokens.length; i < l; i++) {
  339. token = tokens[i]
  340. el = token.tag
  341. ? processTextToken(token, options)
  342. : document.createTextNode(token.value)
  343. frag.appendChild(el)
  344. }
  345. return makeTextNodeLinkFn(tokens, frag, options)
  346. }
  347. /**
  348. * Linker for an skipped text node.
  349. *
  350. * @param {Vue} vm
  351. * @param {Text} node
  352. */
  353. function removeText (vm, node) {
  354. remove(node)
  355. }
  356. /**
  357. * Process a single text token.
  358. *
  359. * @param {Object} token
  360. * @param {Object} options
  361. * @return {Node}
  362. */
  363. function processTextToken (token, options) {
  364. var el
  365. if (token.oneTime) {
  366. el = document.createTextNode(token.value)
  367. } else {
  368. if (token.html) {
  369. el = document.createComment('v-html')
  370. setTokenType('html')
  371. } else {
  372. // IE will clean up empty textNodes during
  373. // frag.cloneNode(true), so we have to give it
  374. // something here...
  375. el = document.createTextNode(' ')
  376. setTokenType('text')
  377. }
  378. }
  379. function setTokenType (type) {
  380. if (token.descriptor) return
  381. var parsed = parseDirective(token.value)
  382. token.descriptor = {
  383. name: type,
  384. def: publicDirectives[type],
  385. expression: parsed.expression,
  386. filters: parsed.filters
  387. }
  388. }
  389. return el
  390. }
  391. /**
  392. * Build a function that processes a textNode.
  393. *
  394. * @param {Array<Object>} tokens
  395. * @param {DocumentFragment} frag
  396. */
  397. function makeTextNodeLinkFn (tokens, frag) {
  398. return function textNodeLinkFn (vm, el, host, scope) {
  399. var fragClone = frag.cloneNode(true)
  400. var childNodes = toArray(fragClone.childNodes)
  401. var token, value, node
  402. for (var i = 0, l = tokens.length; i < l; i++) {
  403. token = tokens[i]
  404. value = token.value
  405. if (token.tag) {
  406. node = childNodes[i]
  407. if (token.oneTime) {
  408. value = (scope || vm).$eval(value)
  409. if (token.html) {
  410. replace(node, parseTemplate(value, true))
  411. } else {
  412. node.data = value
  413. }
  414. } else {
  415. vm._bindDir(token.descriptor, node, host, scope)
  416. }
  417. }
  418. }
  419. replace(el, fragClone)
  420. }
  421. }
  422. /**
  423. * Compile a node list and return a childLinkFn.
  424. *
  425. * @param {NodeList} nodeList
  426. * @param {Object} options
  427. * @return {Function|undefined}
  428. */
  429. function compileNodeList (nodeList, options) {
  430. var linkFns = []
  431. var nodeLinkFn, childLinkFn, node
  432. for (var i = 0, l = nodeList.length; i < l; i++) {
  433. node = nodeList[i]
  434. nodeLinkFn = compileNode(node, options)
  435. childLinkFn =
  436. !(nodeLinkFn && nodeLinkFn.terminal) &&
  437. node.tagName !== 'SCRIPT' &&
  438. node.hasChildNodes()
  439. ? compileNodeList(node.childNodes, options)
  440. : null
  441. linkFns.push(nodeLinkFn, childLinkFn)
  442. }
  443. return linkFns.length
  444. ? makeChildLinkFn(linkFns)
  445. : null
  446. }
  447. /**
  448. * Make a child link function for a node's childNodes.
  449. *
  450. * @param {Array<Function>} linkFns
  451. * @return {Function} childLinkFn
  452. */
  453. function makeChildLinkFn (linkFns) {
  454. return function childLinkFn (vm, nodes, host, scope, frag) {
  455. var node, nodeLinkFn, childrenLinkFn
  456. for (var i = 0, n = 0, l = linkFns.length; i < l; n++) {
  457. node = nodes[n]
  458. nodeLinkFn = linkFns[i++]
  459. childrenLinkFn = linkFns[i++]
  460. // cache childNodes before linking parent, fix #657
  461. var childNodes = toArray(node.childNodes)
  462. if (nodeLinkFn) {
  463. nodeLinkFn(vm, node, host, scope, frag)
  464. }
  465. if (childrenLinkFn) {
  466. childrenLinkFn(vm, childNodes, host, scope, frag)
  467. }
  468. }
  469. }
  470. }
  471. /**
  472. * Check for element directives (custom elements that should
  473. * be resovled as terminal directives).
  474. *
  475. * @param {Element} el
  476. * @param {Object} options
  477. */
  478. function checkElementDirectives (el, options) {
  479. var tag = el.tagName.toLowerCase()
  480. if (commonTagRE.test(tag)) {
  481. return
  482. }
  483. var def = resolveAsset(options, 'elementDirectives', tag)
  484. if (def) {
  485. return makeTerminalNodeLinkFn(el, tag, '', options, def)
  486. }
  487. }
  488. /**
  489. * Check if an element is a component. If yes, return
  490. * a component link function.
  491. *
  492. * @param {Element} el
  493. * @param {Object} options
  494. * @return {Function|undefined}
  495. */
  496. function checkComponent (el, options) {
  497. var component = checkComponentAttr(el, options)
  498. if (component) {
  499. var ref = findRef(el)
  500. var descriptor = {
  501. name: 'component',
  502. ref: ref,
  503. expression: component.id,
  504. def: internalDirectives.component,
  505. modifiers: {
  506. literal: !component.dynamic
  507. }
  508. }
  509. var componentLinkFn = function (vm, el, host, scope, frag) {
  510. if (ref) {
  511. defineReactive((scope || vm).$refs, ref, null)
  512. }
  513. vm._bindDir(descriptor, el, host, scope, frag)
  514. }
  515. componentLinkFn.terminal = true
  516. return componentLinkFn
  517. }
  518. }
  519. /**
  520. * Check an element for terminal directives in fixed order.
  521. * If it finds one, return a terminal link function.
  522. *
  523. * @param {Element} el
  524. * @param {Array} attrs
  525. * @param {Object} options
  526. * @return {Function} terminalLinkFn
  527. */
  528. function checkTerminalDirectives (el, attrs, options) {
  529. // skip v-pre
  530. if (getAttr(el, 'v-pre') !== null) {
  531. return skip
  532. }
  533. // skip v-else block, but only if following v-if
  534. if (el.hasAttribute('v-else')) {
  535. var prev = el.previousElementSibling
  536. if (prev && prev.hasAttribute('v-if')) {
  537. return skip
  538. }
  539. }
  540. var attr, name, value, modifiers, matched, dirName, rawName, arg, def, termDef
  541. for (var i = 0, j = attrs.length; i < j; i++) {
  542. attr = attrs[i]
  543. name = attr.name.replace(modifierRE, '')
  544. if ((matched = name.match(dirAttrRE))) {
  545. def = resolveAsset(options, 'directives', matched[1])
  546. if (def && def.terminal) {
  547. if (!termDef || ((def.priority || DEFAULT_TERMINAL_PRIORITY) > termDef.priority)) {
  548. termDef = def
  549. rawName = attr.name
  550. modifiers = parseModifiers(attr.name)
  551. value = attr.value
  552. dirName = matched[1]
  553. arg = matched[2]
  554. }
  555. }
  556. }
  557. }
  558. if (termDef) {
  559. return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers)
  560. }
  561. }
  562. function skip () {}
  563. skip.terminal = true
  564. /**
  565. * Build a node link function for a terminal directive.
  566. * A terminal link function terminates the current
  567. * compilation recursion and handles compilation of the
  568. * subtree in the directive.
  569. *
  570. * @param {Element} el
  571. * @param {String} dirName
  572. * @param {String} value
  573. * @param {Object} options
  574. * @param {Object} def
  575. * @param {String} [rawName]
  576. * @param {String} [arg]
  577. * @param {Object} [modifiers]
  578. * @return {Function} terminalLinkFn
  579. */
  580. function makeTerminalNodeLinkFn (el, dirName, value, options, def, rawName, arg, modifiers) {
  581. var parsed = parseDirective(value)
  582. var descriptor = {
  583. name: dirName,
  584. arg: arg,
  585. expression: parsed.expression,
  586. filters: parsed.filters,
  587. raw: value,
  588. attr: rawName,
  589. modifiers: modifiers,
  590. def: def
  591. }
  592. // check ref for v-for and router-view
  593. if (dirName === 'for' || dirName === 'router-view') {
  594. descriptor.ref = findRef(el)
  595. }
  596. var fn = function terminalNodeLinkFn (vm, el, host, scope, frag) {
  597. if (descriptor.ref) {
  598. defineReactive((scope || vm).$refs, descriptor.ref, null)
  599. }
  600. vm._bindDir(descriptor, el, host, scope, frag)
  601. }
  602. fn.terminal = true
  603. return fn
  604. }
  605. /**
  606. * Compile the directives on an element and return a linker.
  607. *
  608. * @param {Array|NamedNodeMap} attrs
  609. * @param {Object} options
  610. * @return {Function}
  611. */
  612. function compileDirectives (attrs, options) {
  613. var i = attrs.length
  614. var dirs = []
  615. var attr, name, value, rawName, rawValue, dirName, arg, modifiers, dirDef, tokens, matched
  616. while (i--) {
  617. attr = attrs[i]
  618. name = rawName = attr.name
  619. value = rawValue = attr.value
  620. tokens = parseText(value)
  621. // reset arg
  622. arg = null
  623. // check modifiers
  624. modifiers = parseModifiers(name)
  625. name = name.replace(modifierRE, '')
  626. // attribute interpolations
  627. if (tokens) {
  628. value = tokensToExp(tokens)
  629. arg = name
  630. pushDir('bind', publicDirectives.bind, tokens)
  631. // warn against mixing mustaches with v-bind
  632. if (process.env.NODE_ENV !== 'production') {
  633. if (name === 'class' && Array.prototype.some.call(attrs, function (attr) {
  634. return attr.name === ':class' || attr.name === 'v-bind:class'
  635. })) {
  636. warn(
  637. 'class="' + rawValue + '": Do not mix mustache interpolation ' +
  638. 'and v-bind for "class" on the same element. Use one or the other.',
  639. options
  640. )
  641. }
  642. }
  643. } else
  644. // special attribute: transition
  645. if (transitionRE.test(name)) {
  646. modifiers.literal = !bindRE.test(name)
  647. pushDir('transition', internalDirectives.transition)
  648. } else
  649. // event handlers
  650. if (onRE.test(name)) {
  651. arg = name.replace(onRE, '')
  652. pushDir('on', publicDirectives.on)
  653. } else
  654. // attribute bindings
  655. if (bindRE.test(name)) {
  656. dirName = name.replace(bindRE, '')
  657. if (dirName === 'style' || dirName === 'class') {
  658. pushDir(dirName, internalDirectives[dirName])
  659. } else {
  660. arg = dirName
  661. pushDir('bind', publicDirectives.bind)
  662. }
  663. } else
  664. // normal directives
  665. if ((matched = name.match(dirAttrRE))) {
  666. dirName = matched[1]
  667. arg = matched[2]
  668. // skip v-else (when used with v-show)
  669. if (dirName === 'else') {
  670. continue
  671. }
  672. dirDef = resolveAsset(options, 'directives', dirName, true)
  673. if (dirDef) {
  674. pushDir(dirName, dirDef)
  675. }
  676. }
  677. }
  678. /**
  679. * Push a directive.
  680. *
  681. * @param {String} dirName
  682. * @param {Object|Function} def
  683. * @param {Array} [interpTokens]
  684. */
  685. function pushDir (dirName, def, interpTokens) {
  686. var hasOneTimeToken = interpTokens && hasOneTime(interpTokens)
  687. var parsed = !hasOneTimeToken && parseDirective(value)
  688. dirs.push({
  689. name: dirName,
  690. attr: rawName,
  691. raw: rawValue,
  692. def: def,
  693. arg: arg,
  694. modifiers: modifiers,
  695. // conversion from interpolation strings with one-time token
  696. // to expression is differed until directive bind time so that we
  697. // have access to the actual vm context for one-time bindings.
  698. expression: parsed && parsed.expression,
  699. filters: parsed && parsed.filters,
  700. interp: interpTokens,
  701. hasOneTime: hasOneTimeToken
  702. })
  703. }
  704. if (dirs.length) {
  705. return makeNodeLinkFn(dirs)
  706. }
  707. }
  708. /**
  709. * Parse modifiers from directive attribute name.
  710. *
  711. * @param {String} name
  712. * @return {Object}
  713. */
  714. function parseModifiers (name) {
  715. var res = Object.create(null)
  716. var match = name.match(modifierRE)
  717. if (match) {
  718. var i = match.length
  719. while (i--) {
  720. res[match[i].slice(1)] = true
  721. }
  722. }
  723. return res
  724. }
  725. /**
  726. * Build a link function for all directives on a single node.
  727. *
  728. * @param {Array} directives
  729. * @return {Function} directivesLinkFn
  730. */
  731. function makeNodeLinkFn (directives) {
  732. return function nodeLinkFn (vm, el, host, scope, frag) {
  733. // reverse apply because it's sorted low to high
  734. var i = directives.length
  735. while (i--) {
  736. vm._bindDir(directives[i], el, host, scope, frag)
  737. }
  738. }
  739. }
  740. /**
  741. * Check if an interpolation string contains one-time tokens.
  742. *
  743. * @param {Array} tokens
  744. * @return {Boolean}
  745. */
  746. function hasOneTime (tokens) {
  747. var i = tokens.length
  748. while (i--) {
  749. if (tokens[i].oneTime) return true
  750. }
  751. }