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