compile.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. var _ = require('../util')
  2. var config = require('../config')
  3. var Direcitve = require('../directive')
  4. var textParser = require('../parse/text')
  5. var dirParser = require('../parse/directive')
  6. var templateParser = require('../parse/template')
  7. /**
  8. * The main entrance to the compilation process.
  9. * Calling this function requires the instance's `$el` to
  10. * be already set up, and it should be called only once
  11. * during an instance's entire lifecycle.
  12. */
  13. exports._compile = function () {
  14. if (this._blockNodes) {
  15. this._blockNodes.forEach(this._compileNode, this)
  16. } else {
  17. this._compileNode(this.$el)
  18. }
  19. }
  20. /**
  21. * Generic compile function. Determines the actual compile
  22. * function to use based on the nodeType.
  23. *
  24. * @param {Node} node
  25. */
  26. exports._compileNode = function (node) {
  27. switch (node.nodeType) {
  28. case 1: // element
  29. if (node.tagName !== 'SCRIPT') {
  30. this._compileElement(node)
  31. }
  32. break
  33. case 3: // text
  34. if (config.interpolate) {
  35. this._compileTextNode(node)
  36. }
  37. break
  38. case 8: // comment
  39. this._compileComment(node)
  40. break
  41. }
  42. }
  43. /**
  44. * Compile an Element
  45. *
  46. * @param {Element} node
  47. */
  48. exports._compileElement = function (node) {
  49. var tag = node.tagName
  50. // textarea is pretty annoying
  51. // because its value creates childNodes which
  52. // we don't want to compile.
  53. if (tag === 'TEXTAREA' && node.value) {
  54. node.value = this.$interpolate(node.value)
  55. }
  56. var hasAttributes = node.hasAttributes()
  57. // check priority directives
  58. if (hasAttributes) {
  59. if (this._checkPriorityDirectives(node)) {
  60. return
  61. }
  62. }
  63. // check tag components
  64. if (
  65. tag.indexOf('-') > 0 &&
  66. this.$options.components[tag]
  67. ) {
  68. this._bindDirective('component', tag, node)
  69. return
  70. }
  71. // compile normal directives
  72. if (hasAttributes) {
  73. this._compileAttrs(node)
  74. }
  75. // recursively compile childNodes
  76. if (node.hasChildNodes()) {
  77. _.toArray(node.childNodes)
  78. .forEach(this._compileNode, this)
  79. }
  80. }
  81. /**
  82. * Compile attribtues on an Element
  83. *
  84. * @param {Element} node
  85. */
  86. exports._compileAttrs = function (node) {
  87. var attrs = _.toArray(node.attributes)
  88. var i = attrs.length
  89. var registry = this.$options.directives
  90. var dirs = []
  91. var attr, attrName, dir, dirName
  92. while (i--) {
  93. attr = attrs[i]
  94. attrName = attr.name
  95. if (attrName.indexOf(config.prefix) === 0) {
  96. dirName = attrName.slice(config.prefix.length)
  97. if (registry[dirName]) {
  98. node.removeAttribute(attrName)
  99. dirs.push({
  100. name: dirName,
  101. value: attr.value
  102. })
  103. } else {
  104. _.warn('Failed to resolve directive: ' + dirName)
  105. }
  106. } else if (config.interpolate) {
  107. this._bindAttr(node, attr)
  108. }
  109. }
  110. // sort the directives by priority, low to high
  111. dirs.sort(function (a, b) {
  112. a = registry[a.name].priority || 0
  113. b = registry[b.name].priority || 0
  114. return a > b ? 1 : -1
  115. })
  116. i = dirs.length
  117. while (i--) {
  118. dir = dirs[i]
  119. this._bindDirective(dir.name, dir.value, node)
  120. }
  121. }
  122. /**
  123. * Compile a TextNode
  124. *
  125. * @param {TextNode} node
  126. */
  127. exports._compileTextNode = function (node) {
  128. var tokens = textParser.parse(node.nodeValue)
  129. if (!tokens) {
  130. return
  131. }
  132. var el, token, value
  133. for (var i = 0, l = tokens.length; i < l; i++) {
  134. token = tokens[i]
  135. if (token.tag) {
  136. if (token.oneTime) {
  137. value = this.$get(token.value)
  138. el = token.html
  139. ? templateParser.parse(value, true)
  140. : document.createTextNode(value)
  141. _.before(el, node)
  142. } else {
  143. value = token.value
  144. if (token.html) {
  145. el = document.createComment('vue-html')
  146. _.before(el, node)
  147. this._bindDirective('html', value, el)
  148. } else {
  149. el = document.createTextNode('')
  150. _.before(el, node)
  151. this._bindDirective('text', value, el)
  152. }
  153. }
  154. } else {
  155. el = document.createTextNode(token.value)
  156. _.before(el, node)
  157. }
  158. }
  159. _.remove(node)
  160. }
  161. /**
  162. * Compile a comment node (check for block flow-controls)
  163. *
  164. * @param {CommentNode} node
  165. */
  166. exports._compileComment = function (node) {
  167. }
  168. /**
  169. * Check an attribute for potential bindings.
  170. */
  171. exports._bindAttr = function (node, attr) {
  172. var name = attr.name
  173. var value = attr.value
  174. // check if this is a param attribute.
  175. var params = this.$options.paramAttributes
  176. var isParam =
  177. node === this.$el && // only check on root node
  178. params &&
  179. params.indexOf(name) > -1
  180. if (isParam) {
  181. node.removeAttribute(name)
  182. }
  183. // parse attribute value
  184. var tokens = textParser.parse(value)
  185. if (!tokens) {
  186. if (isParam) {
  187. this.$set(name, value)
  188. }
  189. return
  190. }
  191. // only 1 binding tag allowed
  192. if (tokens.length > 1) {
  193. _.warn(
  194. 'Invalid attribute binding: "' + value + '"' +
  195. '\nUse one single interpolation tag in ' +
  196. 'attribute bindings.'
  197. )
  198. return
  199. }
  200. // param attributes are bound as v-with
  201. var dirName = isParam
  202. ? 'with'
  203. : 'attr'
  204. // wrap namespaced attribute so it won't mess up
  205. // the directive parser
  206. var arg = name.indexOf(':') > 0
  207. ? "'" + name + "'"
  208. : name
  209. // bind
  210. this._bindDirective(
  211. dirName,
  212. arg + ':' + tokens[0].value,
  213. node
  214. )
  215. }
  216. /**
  217. * Check for priority directives that would potentially
  218. * skip other directives:
  219. *
  220. * - v-pre
  221. * - v-repeat
  222. * - v-if
  223. * - v-component
  224. *
  225. * @param {Element} node
  226. * @return {Boolean}
  227. */
  228. exports._checkPriorityDirectives = function (node) {
  229. var value
  230. /* jshint boss: true */
  231. if (_.attr(node, 'pre') !== null) {
  232. return true
  233. } else if (value = _.attr(node, 'repeat')) {
  234. this._bindDirective('repeat', value)
  235. return true
  236. } else if (value = _.attr(node, 'if')) {
  237. this._bindDirective('if', value)
  238. return true
  239. } else if (value = _.attr(node, 'component')) {
  240. this._bindDirective('component', value)
  241. return true
  242. }
  243. }
  244. /**
  245. * Bind a directive.
  246. *
  247. * @param {String} name
  248. * @param {String} value
  249. * @param {Element} node
  250. */
  251. exports._bindDirective = function (name, value, node) {
  252. var descriptors = dirParser.parse(value)
  253. var dirs = this._directives
  254. for (var i = 0, l = descriptors.length; i < l; i++) {
  255. dirs.push(
  256. new Direcitve(name, node, this, descriptors[i])
  257. )
  258. }
  259. }