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  }