| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743 |
- var _ = require('../util')
- var publicDirectives = require('../directives/public')
- var internalDirectives = require('../directives/internal')
- var compileProps = require('./compile-props')
- var textParser = require('../parsers/text')
- var dirParser = require('../parsers/directive')
- var templateParser = require('../parsers/template')
- var resolveAsset = _.resolveAsset
- // special binding prefixes
- var bindRE = /^v-bind:|^:/
- var onRE = /^v-on:|^@/
- var argRE = /:(.*)$/
- var modifierRE = /\.[^\.]+/g
- var transitionRE = /^(v-bind:|:)?transition$/
- // terminal directives
- var terminalDirectives = [
- 'for',
- 'if'
- ]
- // default directive priority
- var DEFAULT_PRIORITY = 1000
- /**
- * Compile a template and return a reusable composite link
- * function, which recursively contains more link functions
- * inside. This top level compile function would normally
- * be called on instance root nodes, but can also be used
- * for partial compilation if the partial argument is true.
- *
- * The returned composite link function, when called, will
- * return an unlink function that tearsdown all directives
- * created during the linking phase.
- *
- * @param {Element|DocumentFragment} el
- * @param {Object} options
- * @param {Boolean} partial
- * @return {Function}
- */
- exports.compile = function (el, options, partial) {
- // link function for the node itself.
- var nodeLinkFn = partial || !options._asComponent
- ? compileNode(el, options)
- : null
- // link function for the childNodes
- var childLinkFn =
- !(nodeLinkFn && nodeLinkFn.terminal) &&
- el.tagName !== 'SCRIPT' &&
- el.hasChildNodes()
- ? compileNodeList(el.childNodes, options)
- : null
- /**
- * A composite linker function to be called on a already
- * compiled piece of DOM, which instantiates all directive
- * instances.
- *
- * @param {Vue} vm
- * @param {Element|DocumentFragment} el
- * @param {Vue} [host] - host vm of transcluded content
- * @param {Object} [scope] - v-for scope
- * @param {Fragment} [frag] - link context fragment
- * @return {Function|undefined}
- */
- return function compositeLinkFn (vm, el, host, scope, frag) {
- // cache childNodes before linking parent, fix #657
- var childNodes = _.toArray(el.childNodes)
- // link
- var dirs = linkAndCapture(function compositeLinkCapturer () {
- if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
- if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
- }, vm)
- return makeUnlinkFn(vm, dirs)
- }
- }
- /**
- * Apply a linker to a vm/element pair and capture the
- * directives created during the process.
- *
- * @param {Function} linker
- * @param {Vue} vm
- */
- function linkAndCapture (linker, vm) {
- var originalDirCount = vm._directives.length
- linker()
- var dirs = vm._directives.slice(originalDirCount)
- dirs.sort(directiveComparator)
- for (var i = 0, l = dirs.length; i < l; i++) {
- dirs[i]._bind()
- }
- return dirs
- }
- /**
- * Directive priority sort comparator
- *
- * @param {Object} a
- * @param {Object} b
- */
- function directiveComparator (a, b) {
- a = a.descriptor.def.priority || DEFAULT_PRIORITY
- b = b.descriptor.def.priority || DEFAULT_PRIORITY
- return a > b ? -1 : a === b ? 0 : 1
- }
- /**
- * Linker functions return an unlink function that
- * tearsdown all directives instances generated during
- * the process.
- *
- * We create unlink functions with only the necessary
- * information to avoid retaining additional closures.
- *
- * @param {Vue} vm
- * @param {Array} dirs
- * @param {Vue} [context]
- * @param {Array} [contextDirs]
- * @return {Function}
- */
- function makeUnlinkFn (vm, dirs, context, contextDirs) {
- return function unlink (destroying) {
- teardownDirs(vm, dirs, destroying)
- if (context && contextDirs) {
- teardownDirs(context, contextDirs)
- }
- }
- }
- /**
- * Teardown partial linked directives.
- *
- * @param {Vue} vm
- * @param {Array} dirs
- * @param {Boolean} destroying
- */
- function teardownDirs (vm, dirs, destroying) {
- var i = dirs.length
- while (i--) {
- dirs[i]._teardown()
- if (!destroying) {
- vm._directives.$remove(dirs[i])
- }
- }
- }
- /**
- * Compile link props on an instance.
- *
- * @param {Vue} vm
- * @param {Element} el
- * @param {Object} props
- * @param {Object} [scope]
- * @return {Function}
- */
- exports.compileAndLinkProps = function (vm, el, props, scope) {
- var propsLinkFn = compileProps(el, props)
- var propDirs = linkAndCapture(function () {
- propsLinkFn(vm, scope)
- }, vm)
- return makeUnlinkFn(vm, propDirs)
- }
- /**
- * Compile the root element of an instance.
- *
- * 1. attrs on context container (context scope)
- * 2. attrs on the component template root node, if
- * replace:true (child scope)
- *
- * If this is a fragment instance, we only need to compile 1.
- *
- * @param {Vue} vm
- * @param {Element} el
- * @param {Object} options
- * @param {Object} contextOptions
- * @return {Function}
- */
- exports.compileRoot = function (el, options, contextOptions) {
- var containerAttrs = options._containerAttrs
- var replacerAttrs = options._replacerAttrs
- var contextLinkFn, replacerLinkFn
- // only need to compile other attributes for
- // non-fragment instances
- if (el.nodeType !== 11) {
- // for components, container and replacer need to be
- // compiled separately and linked in different scopes.
- if (options._asComponent) {
- // 2. container attributes
- if (containerAttrs && contextOptions) {
- contextLinkFn = compileDirectives(containerAttrs, contextOptions)
- }
- if (replacerAttrs) {
- // 3. replacer attributes
- replacerLinkFn = compileDirectives(replacerAttrs, options)
- }
- } else {
- // non-component, just compile as a normal element.
- replacerLinkFn = compileDirectives(el.attributes, options)
- }
- } else if (process.env.NODE_ENV !== 'production' && containerAttrs) {
- // warn container directives for fragment instances
- var names = containerAttrs
- .filter(function (attr) {
- // allow vue-loader/vueify scoped css attributes
- return attr.name.indexOf('_v-') < 0 &&
- // allow event listeners
- !onRE.test(attr.name) &&
- // allow slots
- attr.name !== 'slot'
- })
- .map(function (attr) {
- return '"' + attr.name + '"'
- })
- if (names.length) {
- var plural = names.length > 1
- _.warn(
- 'Attribute' + (plural ? 's ' : ' ') + names.join(', ') +
- (plural ? ' are' : ' is') + ' ignored on component ' +
- '<' + options.el.tagName.toLowerCase() + '> because ' +
- 'the component is a fragment instance: ' +
- 'http://vuejs.org/guide/components.html#Fragment_Instance'
- )
- }
- }
- return function rootLinkFn (vm, el, scope) {
- // link context scope dirs
- var context = vm._context
- var contextDirs
- if (context && contextLinkFn) {
- contextDirs = linkAndCapture(function () {
- contextLinkFn(context, el, null, scope)
- }, context)
- }
- // link self
- var selfDirs = linkAndCapture(function () {
- if (replacerLinkFn) replacerLinkFn(vm, el)
- }, vm)
- // return the unlink function that tearsdown context
- // container directives.
- return makeUnlinkFn(vm, selfDirs, context, contextDirs)
- }
- }
- /**
- * Compile a node and return a nodeLinkFn based on the
- * node type.
- *
- * @param {Node} node
- * @param {Object} options
- * @return {Function|null}
- */
- function compileNode (node, options) {
- var type = node.nodeType
- if (type === 1 && node.tagName !== 'SCRIPT') {
- return compileElement(node, options)
- } else if (type === 3 && node.data.trim()) {
- return compileTextNode(node, options)
- } else {
- return null
- }
- }
- /**
- * Compile an element and return a nodeLinkFn.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Function|null}
- */
- function compileElement (el, options) {
- // preprocess textareas.
- // textarea treats its text content as the initial value.
- // just bind it as an attr directive for value.
- if (el.tagName === 'TEXTAREA') {
- var tokens = textParser.parse(el.value)
- if (tokens) {
- el.setAttribute(':value', textParser.tokensToExp(tokens))
- el.value = ''
- }
- }
- var linkFn
- var hasAttrs = el.hasAttributes()
- // check terminal directives (for & if)
- if (hasAttrs) {
- linkFn = checkTerminalDirectives(el, options)
- }
- // check element directives
- if (!linkFn) {
- linkFn = checkElementDirectives(el, options)
- }
- // check component
- if (!linkFn) {
- linkFn = checkComponent(el, options)
- }
- // normal directives
- if (!linkFn && hasAttrs) {
- linkFn = compileDirectives(el.attributes, options)
- }
- return linkFn
- }
- /**
- * Compile a textNode and return a nodeLinkFn.
- *
- * @param {TextNode} node
- * @param {Object} options
- * @return {Function|null} textNodeLinkFn
- */
- function compileTextNode (node, options) {
- var tokens = textParser.parse(node.data)
- if (!tokens) {
- return null
- }
- var frag = document.createDocumentFragment()
- var el, token
- for (var i = 0, l = tokens.length; i < l; i++) {
- token = tokens[i]
- el = token.tag
- ? processTextToken(token, options)
- : document.createTextNode(token.value)
- frag.appendChild(el)
- }
- return makeTextNodeLinkFn(tokens, frag, options)
- }
- /**
- * Process a single text token.
- *
- * @param {Object} token
- * @param {Object} options
- * @return {Node}
- */
- function processTextToken (token, options) {
- var el
- if (token.oneTime) {
- el = document.createTextNode(token.value)
- } else {
- if (token.html) {
- el = document.createComment('v-html')
- setTokenType('html')
- } else {
- // IE will clean up empty textNodes during
- // frag.cloneNode(true), so we have to give it
- // something here...
- el = document.createTextNode(' ')
- setTokenType('text')
- }
- }
- function setTokenType (type) {
- if (token.descriptor) return
- var parsed = dirParser.parse(token.value)
- token.descriptor = {
- name: type,
- def: publicDirectives[type],
- expression: parsed.expression,
- filters: parsed.filters
- }
- }
- return el
- }
- /**
- * Build a function that processes a textNode.
- *
- * @param {Array<Object>} tokens
- * @param {DocumentFragment} frag
- */
- function makeTextNodeLinkFn (tokens, frag) {
- return function textNodeLinkFn (vm, el, host, scope) {
- var fragClone = frag.cloneNode(true)
- var childNodes = _.toArray(fragClone.childNodes)
- var token, value, node
- for (var i = 0, l = tokens.length; i < l; i++) {
- token = tokens[i]
- value = token.value
- if (token.tag) {
- node = childNodes[i]
- if (token.oneTime) {
- value = (scope || vm).$eval(value)
- if (token.html) {
- _.replace(node, templateParser.parse(value, true))
- } else {
- node.data = value
- }
- } else {
- vm._bindDir(token.descriptor, node, host, scope)
- }
- }
- }
- _.replace(el, fragClone)
- }
- }
- /**
- * Compile a node list and return a childLinkFn.
- *
- * @param {NodeList} nodeList
- * @param {Object} options
- * @return {Function|undefined}
- */
- function compileNodeList (nodeList, options) {
- var linkFns = []
- var nodeLinkFn, childLinkFn, node
- for (var i = 0, l = nodeList.length; i < l; i++) {
- node = nodeList[i]
- nodeLinkFn = compileNode(node, options)
- childLinkFn =
- !(nodeLinkFn && nodeLinkFn.terminal) &&
- node.tagName !== 'SCRIPT' &&
- node.hasChildNodes()
- ? compileNodeList(node.childNodes, options)
- : null
- linkFns.push(nodeLinkFn, childLinkFn)
- }
- return linkFns.length
- ? makeChildLinkFn(linkFns)
- : null
- }
- /**
- * Make a child link function for a node's childNodes.
- *
- * @param {Array<Function>} linkFns
- * @return {Function} childLinkFn
- */
- function makeChildLinkFn (linkFns) {
- return function childLinkFn (vm, nodes, host, scope, frag) {
- var node, nodeLinkFn, childrenLinkFn
- for (var i = 0, n = 0, l = linkFns.length; i < l; n++) {
- node = nodes[n]
- nodeLinkFn = linkFns[i++]
- childrenLinkFn = linkFns[i++]
- // cache childNodes before linking parent, fix #657
- var childNodes = _.toArray(node.childNodes)
- if (nodeLinkFn) {
- nodeLinkFn(vm, node, host, scope, frag)
- }
- if (childrenLinkFn) {
- childrenLinkFn(vm, childNodes, host, scope, frag)
- }
- }
- }
- }
- /**
- * Check for element directives (custom elements that should
- * be resovled as terminal directives).
- *
- * @param {Element} el
- * @param {Object} options
- */
- function checkElementDirectives (el, options) {
- var tag = el.tagName.toLowerCase()
- if (_.commonTagRE.test(tag)) return
- var def = resolveAsset(options, 'elementDirectives', tag)
- if (def) {
- return makeTerminalNodeLinkFn(el, tag, '', options, def)
- }
- }
- /**
- * Check if an element is a component. If yes, return
- * a component link function.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Function|undefined}
- */
- function checkComponent (el, options) {
- var component = _.checkComponent(el, options)
- if (component) {
- var ref = _.findRef(el)
- var descriptor = {
- name: 'component',
- ref: ref,
- expression: component.id,
- def: internalDirectives.component,
- modifiers: {
- literal: !component.dynamic
- }
- }
- var componentLinkFn = function (vm, el, host, scope, frag) {
- if (ref) {
- _.defineReactive((scope || vm).$refs, ref, null)
- }
- vm._bindDir(descriptor, el, host, scope, frag)
- }
- componentLinkFn.terminal = true
- return componentLinkFn
- }
- }
- /**
- * Check an element for terminal directives in fixed order.
- * If it finds one, return a terminal link function.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Function} terminalLinkFn
- */
- function checkTerminalDirectives (el, options) {
- // skip v-pre
- if (_.attr(el, 'v-pre') !== null) {
- return skip
- }
- // skip v-else block, but only if following v-if
- if (el.hasAttribute('v-else')) {
- var prev = el.previousElementSibling
- if (prev && prev.hasAttribute('v-if')) {
- return skip
- }
- }
- var value, dirName
- for (var i = 0, l = terminalDirectives.length; i < l; i++) {
- dirName = terminalDirectives[i]
- /* eslint-disable no-cond-assign */
- if (value = el.getAttribute('v-' + dirName)) {
- return makeTerminalNodeLinkFn(el, dirName, value, options)
- }
- /* eslint-enable no-cond-assign */
- }
- }
- function skip () {}
- skip.terminal = true
- /**
- * Build a node link function for a terminal directive.
- * A terminal link function terminates the current
- * compilation recursion and handles compilation of the
- * subtree in the directive.
- *
- * @param {Element} el
- * @param {String} dirName
- * @param {String} value
- * @param {Object} options
- * @param {Object} [def]
- * @return {Function} terminalLinkFn
- */
- function makeTerminalNodeLinkFn (el, dirName, value, options, def) {
- var parsed = dirParser.parse(value)
- var descriptor = {
- name: dirName,
- expression: parsed.expression,
- filters: parsed.filters,
- raw: value,
- // either an element directive, or if/for
- def: def || publicDirectives[dirName]
- }
- // check ref for v-for
- if (dirName === 'for') {
- descriptor.ref = _.findRef(el)
- }
- var fn = function terminalNodeLinkFn (vm, el, host, scope, frag) {
- if (descriptor.ref) {
- _.defineReactive((scope || vm).$refs, descriptor.ref, null)
- }
- vm._bindDir(descriptor, el, host, scope, frag)
- }
- fn.terminal = true
- return fn
- }
- /**
- * Compile the directives on an element and return a linker.
- *
- * @param {Array|NamedNodeMap} attrs
- * @param {Object} options
- * @return {Function}
- */
- function compileDirectives (attrs, options) {
- var i = attrs.length
- var dirs = []
- var attr, name, value, rawName, rawValue, dirName, arg, modifiers, dirDef, tokens
- while (i--) {
- attr = attrs[i]
- name = rawName = attr.name
- value = rawValue = attr.value
- tokens = textParser.parse(value)
- // reset arg
- arg = null
- // check modifiers
- modifiers = parseModifiers(name)
- name = name.replace(modifierRE, '')
- // attribute interpolations
- if (tokens) {
- value = textParser.tokensToExp(tokens)
- arg = name
- pushDir('bind', publicDirectives.bind, true)
- // warn against mixing mustaches with v-bind
- if (process.env.NODE_ENV !== 'production') {
- if (name === 'class' && Array.prototype.some.call(attrs, function (attr) {
- return attr.name === ':class' || attr.name === 'v-bind:class'
- })) {
- _.warn(
- 'class="' + rawValue + '": Do not mix mustache interpolation ' +
- 'and v-bind for "class" on the same element. Use one or the other.'
- )
- }
- }
- } else
- // special attribute: transition
- if (transitionRE.test(name)) {
- modifiers.literal = !bindRE.test(name)
- pushDir('transition', internalDirectives.transition)
- } else
- // event handlers
- if (onRE.test(name)) {
- arg = name.replace(onRE, '')
- pushDir('on', publicDirectives.on)
- } else
- // attribute bindings
- if (bindRE.test(name)) {
- dirName = name.replace(bindRE, '')
- if (dirName === 'style' || dirName === 'class') {
- pushDir(dirName, internalDirectives[dirName])
- } else {
- arg = dirName
- pushDir('bind', publicDirectives.bind)
- }
- } else
- // normal directives
- if (name.indexOf('v-') === 0) {
- // check arg
- arg = (arg = name.match(argRE)) && arg[1]
- if (arg) {
- name = name.replace(argRE, '')
- }
- // extract directive name
- dirName = name.slice(2)
- // skip v-else (when used with v-show)
- if (dirName === 'else') {
- continue
- }
- dirDef = resolveAsset(options, 'directives', dirName)
- if (process.env.NODE_ENV !== 'production') {
- _.assertAsset(dirDef, 'directive', dirName)
- }
- if (dirDef) {
- pushDir(dirName, dirDef)
- }
- }
- }
- /**
- * Push a directive.
- *
- * @param {String} dirName
- * @param {Object|Function} def
- * @param {Boolean} [interp]
- */
- function pushDir (dirName, def, interp) {
- var parsed = dirParser.parse(value)
- dirs.push({
- name: dirName,
- attr: rawName,
- raw: rawValue,
- def: def,
- arg: arg,
- modifiers: modifiers,
- expression: parsed.expression,
- filters: parsed.filters,
- interp: interp
- })
- }
- if (dirs.length) {
- return makeNodeLinkFn(dirs)
- }
- }
- /**
- * Parse modifiers from directive attribute name.
- *
- * @param {String} name
- * @return {Object}
- */
- function parseModifiers (name) {
- var res = Object.create(null)
- var match = name.match(modifierRE)
- if (match) {
- var i = match.length
- while (i--) {
- res[match[i].slice(1)] = true
- }
- }
- return res
- }
- /**
- * Build a link function for all directives on a single node.
- *
- * @param {Array} directives
- * @return {Function} directivesLinkFn
- */
- function makeNodeLinkFn (directives) {
- return function nodeLinkFn (vm, el, host, scope, frag) {
- // reverse apply because it's sorted low to high
- var i = directives.length
- while (i--) {
- vm._bindDir(directives[i], el, host, scope, frag)
- }
- }
- }
|