render.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /* @flow */
  2. import config from '../config'
  3. import VNode, { emptyVNode, cloneVNode, cloneVNodes } from '../vdom/vnode'
  4. import { normalizeChildren } from '../vdom/helpers/index'
  5. import {
  6. warn, formatComponentName, bind, isObject, toObject,
  7. nextTick, resolveAsset, _toString, toNumber, looseEqual, looseIndexOf
  8. } from '../util/index'
  9. import { createElement } from '../vdom/create-element'
  10. export function initRender (vm: Component) {
  11. vm.$vnode = null // the placeholder node in parent tree
  12. vm._vnode = null // the root of the child tree
  13. vm._staticTrees = null
  14. vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context
  15. vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext)
  16. // bind the public createElement fn to this instance
  17. // so that we get proper render context inside it.
  18. vm.$createElement = bind(createElement, vm)
  19. if (vm.$options.el) {
  20. vm.$mount(vm.$options.el)
  21. }
  22. }
  23. export function renderMixin (Vue: Class<Component>) {
  24. Vue.prototype.$nextTick = function (fn: Function) {
  25. nextTick(fn, this)
  26. }
  27. Vue.prototype._render = function (): VNode {
  28. const vm: Component = this
  29. const {
  30. render,
  31. staticRenderFns,
  32. _parentVnode
  33. } = vm.$options
  34. if (vm._isMounted) {
  35. // clone slot nodes on re-renders
  36. for (const key in vm.$slots) {
  37. vm.$slots[key] = cloneVNodes(vm.$slots[key])
  38. }
  39. }
  40. if (staticRenderFns && !vm._staticTrees) {
  41. vm._staticTrees = []
  42. }
  43. // set parent vnode. this allows render functions to have access
  44. // to the data on the placeholder node.
  45. vm.$vnode = _parentVnode
  46. // render self
  47. let vnode
  48. try {
  49. vnode = render.call(vm._renderProxy, vm.$createElement)
  50. } catch (e) {
  51. if (process.env.NODE_ENV !== 'production') {
  52. warn(`Error when rendering ${formatComponentName(vm)}:`)
  53. }
  54. /* istanbul ignore else */
  55. if (config.errorHandler) {
  56. config.errorHandler.call(null, e, vm)
  57. } else {
  58. if (config._isServer) {
  59. throw e
  60. } else {
  61. console.error(e)
  62. }
  63. }
  64. // return previous vnode to prevent render error causing blank component
  65. vnode = vm._vnode
  66. }
  67. // return empty vnode in case the render function errored out
  68. if (!(vnode instanceof VNode)) {
  69. if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
  70. warn(
  71. 'Multiple root nodes returned from render function. Render function ' +
  72. 'should return a single root node.',
  73. vm
  74. )
  75. }
  76. vnode = emptyVNode()
  77. }
  78. // set parent
  79. vnode.parent = _parentVnode
  80. return vnode
  81. }
  82. // shorthands used in render functions
  83. Vue.prototype._h = createElement
  84. // toString for mustaches
  85. Vue.prototype._s = _toString
  86. // number conversion
  87. Vue.prototype._n = toNumber
  88. // empty vnode
  89. Vue.prototype._e = emptyVNode
  90. // loose equal
  91. Vue.prototype._q = looseEqual
  92. // loose indexOf
  93. Vue.prototype._i = looseIndexOf
  94. // render static tree by index
  95. Vue.prototype._m = function renderStatic (
  96. index: number,
  97. isInFor?: boolean
  98. ): VNode | Array<VNode> {
  99. let tree = this._staticTrees[index]
  100. // if has already-rendered static tree and not inside v-for,
  101. // we can reuse the same tree by doing a shallow clone.
  102. if (tree && !isInFor) {
  103. return Array.isArray(tree)
  104. ? cloneVNodes(tree)
  105. : cloneVNode(tree)
  106. }
  107. // otherwise, render a fresh tree.
  108. tree = this._staticTrees[index] = this.$options.staticRenderFns[index].call(this._renderProxy)
  109. markStatic(tree, `__static__${index}`, false)
  110. return tree
  111. }
  112. // mark node as static (v-once)
  113. Vue.prototype._o = function markOnce (
  114. tree: VNode | Array<VNode>,
  115. index: number,
  116. key: string
  117. ) {
  118. markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true)
  119. return tree
  120. }
  121. function markStatic (tree, key, isOnce) {
  122. if (Array.isArray(tree)) {
  123. for (let i = 0; i < tree.length; i++) {
  124. if (tree[i] && typeof tree[i] !== 'string') {
  125. markStaticNode(tree[i], `${key}_${i}`, isOnce)
  126. }
  127. }
  128. } else {
  129. markStaticNode(tree, key, isOnce)
  130. }
  131. }
  132. function markStaticNode (node, key, isOnce) {
  133. node.isStatic = true
  134. node.key = key
  135. node.isOnce = isOnce
  136. }
  137. // filter resolution helper
  138. const identity = _ => _
  139. Vue.prototype._f = function resolveFilter (id) {
  140. return resolveAsset(this.$options, 'filters', id, true) || identity
  141. }
  142. // render v-for
  143. Vue.prototype._l = function renderList (
  144. val: any,
  145. render: () => VNode
  146. ): ?Array<VNode> {
  147. let ret: ?Array<VNode>, i, l, keys, key
  148. if (Array.isArray(val)) {
  149. ret = new Array(val.length)
  150. for (i = 0, l = val.length; i < l; i++) {
  151. ret[i] = render(val[i], i)
  152. }
  153. } else if (typeof val === 'number') {
  154. ret = new Array(val)
  155. for (i = 0; i < val; i++) {
  156. ret[i] = render(i + 1, i)
  157. }
  158. } else if (isObject(val)) {
  159. keys = Object.keys(val)
  160. ret = new Array(keys.length)
  161. for (i = 0, l = keys.length; i < l; i++) {
  162. key = keys[i]
  163. ret[i] = render(val[key], key, i)
  164. }
  165. }
  166. return ret
  167. }
  168. // renderSlot
  169. Vue.prototype._t = function (
  170. name: string,
  171. fallback: ?Array<VNode>
  172. ): ?Array<VNode> {
  173. const slotNodes = this.$slots[name]
  174. // warn duplicate slot usage
  175. if (slotNodes && process.env.NODE_ENV !== 'production') {
  176. slotNodes._rendered && warn(
  177. `Duplicate presence of slot "${name}" found in the same render tree ` +
  178. `- this will likely cause render errors.`,
  179. this
  180. )
  181. slotNodes._rendered = true
  182. }
  183. return slotNodes || fallback
  184. }
  185. // apply v-bind object
  186. Vue.prototype._b = function bindProps (
  187. data: any,
  188. tag: string,
  189. value: any,
  190. asProp?: boolean
  191. ): VNodeData {
  192. if (value) {
  193. if (!isObject(value)) {
  194. process.env.NODE_ENV !== 'production' && warn(
  195. 'v-bind without argument expects an Object or Array value',
  196. this
  197. )
  198. } else {
  199. if (Array.isArray(value)) {
  200. value = toObject(value)
  201. }
  202. for (const key in value) {
  203. if (key === 'class' || key === 'style') {
  204. data[key] = value[key]
  205. } else {
  206. const hash = asProp || config.mustUseProp(tag, key)
  207. ? data.domProps || (data.domProps = {})
  208. : data.attrs || (data.attrs = {})
  209. hash[key] = value[key]
  210. }
  211. }
  212. }
  213. }
  214. return data
  215. }
  216. // expose v-on keyCodes
  217. Vue.prototype._k = function getKeyCodes (key: string): any {
  218. return config.keyCodes[key]
  219. }
  220. }
  221. export function resolveSlots (
  222. renderChildren: ?VNodeChildren,
  223. context: ?Component
  224. ): { [key: string]: Array<VNode> } {
  225. const slots = {}
  226. if (!renderChildren) {
  227. return slots
  228. }
  229. const children = normalizeChildren(renderChildren) || []
  230. const defaultSlot = []
  231. let name, child
  232. for (let i = 0, l = children.length; i < l; i++) {
  233. child = children[i]
  234. // named slots should only be respected if the vnode was rendered in the
  235. // same context.
  236. if ((child.context === context || child.functionalContext === context) &&
  237. child.data && (name = child.data.slot)) {
  238. const slot = (slots[name] || (slots[name] = []))
  239. if (child.tag === 'template') {
  240. slot.push.apply(slot, child.children)
  241. } else {
  242. slot.push(child)
  243. }
  244. } else {
  245. defaultSlot.push(child)
  246. }
  247. }
  248. // ignore single whitespace
  249. if (defaultSlot.length && !(
  250. defaultSlot.length === 1 &&
  251. (defaultSlot[0].text === ' ' || defaultSlot[0].isComment)
  252. )) {
  253. slots.default = defaultSlot
  254. }
  255. return slots
  256. }