render.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /* @flow */
  2. import { escape } from 'he'
  3. import { RenderContext } from './render-context'
  4. import { compileToFunctions } from 'web/compiler/index'
  5. import { createComponentInstanceForVnode } from 'core/vdom/create-component'
  6. let warned = Object.create(null)
  7. const warnOnce = msg => {
  8. if (!warned[msg]) {
  9. warned[msg] = true
  10. console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
  11. }
  12. }
  13. const compilationCache = Object.create(null)
  14. const normalizeRender = vm => {
  15. const { render, template } = vm.$options
  16. if (!render) {
  17. if (template) {
  18. const renderFns = (
  19. compilationCache[template] ||
  20. (compilationCache[template] = compileToFunctions(template))
  21. )
  22. Object.assign(vm.$options, renderFns)
  23. } else {
  24. throw new Error(
  25. `render function or template not defined in component: ${
  26. vm.$options.name || vm.$options._componentTag || 'anonymous'
  27. }`
  28. )
  29. }
  30. }
  31. }
  32. function renderNode (node, isRoot, context) {
  33. const { write, next } = context
  34. if (node.componentOptions) {
  35. // check cache hit
  36. const Ctor = node.componentOptions.Ctor
  37. const getKey = Ctor.options.serverCacheKey
  38. const name = Ctor.options.name
  39. const cache = context.cache
  40. if (getKey && cache && name) {
  41. const key = name + '::' + getKey(node.componentOptions.propsData)
  42. const { has, get } = context
  43. if (has) {
  44. has(key, hit => {
  45. if (hit && get) {
  46. get(key, res => write(res, next))
  47. } else {
  48. renderComponentWithCache(node, isRoot, key, context)
  49. }
  50. })
  51. } else if (get) {
  52. get(key, res => {
  53. if (res) {
  54. write(res, next)
  55. } else {
  56. renderComponentWithCache(node, isRoot, key, context)
  57. }
  58. })
  59. }
  60. } else {
  61. if (getKey && !cache) {
  62. warnOnce(
  63. `[vue-server-renderer] Component ${
  64. Ctor.options.name || '(anonymous)'
  65. } implemented serverCacheKey, ` +
  66. 'but no cache was provided to the renderer.'
  67. )
  68. }
  69. if (getKey && !name) {
  70. warnOnce(
  71. `[vue-server-renderer] Components that implement "serverCacheKey" ` +
  72. `must also define a unique "name" option.`
  73. )
  74. }
  75. renderComponent(node, isRoot, context)
  76. }
  77. } else {
  78. if (node.tag) {
  79. renderElement(node, isRoot, context)
  80. } else if (node.isComment) {
  81. write(`<!--${node.text}-->`, next)
  82. } else {
  83. write(node.raw ? node.text : escape(String(node.text)), next)
  84. }
  85. }
  86. }
  87. function renderComponent (node, isRoot, context) {
  88. const prevActive = context.activeInstance
  89. const child = context.activeInstance = createComponentInstanceForVnode(node, context.activeInstance)
  90. normalizeRender(child)
  91. const childNode = child._render()
  92. childNode.parent = node
  93. context.renderStates.push({
  94. type: 'Component',
  95. prevActive
  96. })
  97. renderNode(childNode, isRoot, context)
  98. }
  99. function renderComponentWithCache (node, isRoot, key, context) {
  100. const write = context.write
  101. write.caching = true
  102. const buffer = write.cacheBuffer
  103. const bufferIndex = buffer.push('') - 1
  104. context.renderStates.push({
  105. type: 'ComponentWithCache',
  106. buffer, bufferIndex, key
  107. })
  108. renderComponent(node, isRoot, context)
  109. }
  110. function renderElement (el, isRoot, context) {
  111. if (isRoot) {
  112. if (!el.data) el.data = {}
  113. if (!el.data.attrs) el.data.attrs = {}
  114. el.data.attrs['server-rendered'] = 'true'
  115. }
  116. const startTag = renderStartingTag(el, context)
  117. const endTag = `</${el.tag}>`
  118. const { write, next } = context
  119. if (context.isUnaryTag(el.tag)) {
  120. write(startTag, next)
  121. } else if (!el.children || !el.children.length) {
  122. write(startTag + endTag, next)
  123. } else {
  124. const children: Array<VNode> = el.children
  125. context.renderStates.push({
  126. type: 'Element',
  127. rendered: 0,
  128. total: children.length,
  129. endTag, children
  130. })
  131. write(startTag, next)
  132. }
  133. }
  134. function hasAncestorData (node: VNode) {
  135. const parentNode = node.parent
  136. return parentNode && (parentNode.data || hasAncestorData(parentNode))
  137. }
  138. function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
  139. let dir: VNodeDirective
  140. let tmp
  141. while (node) {
  142. if (node.data && node.data.directives) {
  143. tmp = node.data.directives.find(dir => dir.name === 'show')
  144. if (tmp) {
  145. dir = tmp
  146. }
  147. }
  148. node = node.parent
  149. }
  150. return dir
  151. }
  152. function renderStartingTag (node: VNode, context) {
  153. let markup = `<${node.tag}`
  154. const { directives, modules } = context
  155. // construct synthetic data for module processing
  156. // because modules like style also produce code by parent VNode data
  157. if (!node.data && hasAncestorData(node)) {
  158. node.data = {}
  159. }
  160. if (node.data) {
  161. // check directives
  162. const dirs = node.data.directives
  163. if (dirs) {
  164. for (let i = 0; i < dirs.length; i++) {
  165. const name = dirs[i].name
  166. const dirRenderer = directives[name]
  167. if (dirRenderer && name !== 'show') {
  168. // directives mutate the node's data
  169. // which then gets rendered by modules
  170. dirRenderer(node, dirs[i])
  171. }
  172. }
  173. }
  174. // v-show directive needs to be merged from parent to child
  175. const vshowDirectiveInfo = getVShowDirectiveInfo(node)
  176. if (vshowDirectiveInfo) {
  177. directives.show(node, vshowDirectiveInfo)
  178. }
  179. // apply other modules
  180. for (let i = 0; i < modules.length; i++) {
  181. const res = modules[i](node)
  182. if (res) {
  183. markup += res
  184. }
  185. }
  186. }
  187. // attach scoped CSS ID
  188. let scopeId
  189. const activeInstance = context.activeInstance
  190. if (activeInstance &&
  191. activeInstance !== node.context &&
  192. (scopeId = activeInstance.$options._scopeId)) {
  193. markup += ` ${scopeId}`
  194. }
  195. while (node) {
  196. if ((scopeId = node.context.$options._scopeId)) {
  197. markup += ` ${scopeId}`
  198. }
  199. node = node.parent
  200. }
  201. return markup + '>'
  202. }
  203. export function createRenderFunction (
  204. modules: Array<Function>,
  205. directives: Object,
  206. isUnaryTag: Function,
  207. cache: any
  208. ) {
  209. return function render (
  210. component: Component,
  211. write: (text: string, next: Function) => void,
  212. done: Function
  213. ) {
  214. warned = Object.create(null)
  215. const context = new RenderContext({
  216. activeInstance: component,
  217. write, done, renderNode,
  218. isUnaryTag, modules, directives,
  219. cache
  220. })
  221. normalizeRender(component)
  222. renderNode(component._render(), true, context)
  223. }
  224. }