render.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /* @flow */
  2. const { escape } = require('he')
  3. import { SSR_ATTR } from 'shared/constants'
  4. import { RenderContext } from './render-context'
  5. import { ssrCompileToFunctions } from 'web/server/compiler'
  6. import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
  7. import { createComponentInstanceForVnode } from 'core/vdom/create-component'
  8. import { isDef, isUndef, isTrue } from 'shared/util'
  9. let warned = Object.create(null)
  10. const warnOnce = msg => {
  11. if (!warned[msg]) {
  12. warned[msg] = true
  13. console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
  14. }
  15. }
  16. const normalizeRender = vm => {
  17. const { render, template } = vm.$options
  18. if (isUndef(render)) {
  19. if (template) {
  20. Object.assign(vm.$options, ssrCompileToFunctions(template))
  21. } else {
  22. throw new Error(
  23. `render function or template not defined in component: ${
  24. vm.$options.name || vm.$options._componentTag || 'anonymous'
  25. }`
  26. )
  27. }
  28. }
  29. }
  30. function renderNode (node, isRoot, context) {
  31. if (node.isString) {
  32. renderStringNode(node, context)
  33. } else if (isDef(node.componentOptions)) {
  34. renderComponent(node, isRoot, context)
  35. } else {
  36. if (isDef(node.tag)) {
  37. renderElement(node, isRoot, context)
  38. } else if (isTrue(node.isComment)) {
  39. context.write(
  40. `<!--${node.text}-->`,
  41. context.next
  42. )
  43. } else {
  44. context.write(
  45. node.raw ? node.text : escape(String(node.text)),
  46. context.next
  47. )
  48. }
  49. }
  50. }
  51. function registerComponentForCache (options, write) {
  52. // exposed by vue-loader, need to call this if cache hit because
  53. // component lifecycle hooks will not be called.
  54. const register = options._ssrRegister
  55. if (write.caching && isDef(register)) {
  56. write.componentBuffer[write.componentBuffer.length - 1].add(register)
  57. }
  58. return register
  59. }
  60. function renderComponent (node, isRoot, context) {
  61. const { write, next, userContext } = context
  62. // check cache hit
  63. const Ctor = node.componentOptions.Ctor
  64. const getKey = Ctor.options.serverCacheKey
  65. const name = Ctor.options.name
  66. const cache = context.cache
  67. const registerComponent = registerComponentForCache(Ctor.options, write)
  68. if (isDef(getKey) && isDef(cache) && isDef(name)) {
  69. const key = name + '::' + getKey(node.componentOptions.propsData)
  70. const { has, get } = context
  71. if (isDef(has)) {
  72. has(key, hit => {
  73. if (hit === true && isDef(get)) {
  74. get(key, res => {
  75. if (isDef(registerComponent)) {
  76. registerComponent(userContext)
  77. }
  78. res.components.forEach(register => register(userContext))
  79. write(res.html, next)
  80. })
  81. } else {
  82. renderComponentWithCache(node, isRoot, key, context)
  83. }
  84. })
  85. } else if (isDef(get)) {
  86. get(key, res => {
  87. if (isDef(res)) {
  88. if (isDef(registerComponent)) {
  89. registerComponent(userContext)
  90. }
  91. res.components.forEach(register => register(userContext))
  92. write(res.html, next)
  93. } else {
  94. renderComponentWithCache(node, isRoot, key, context)
  95. }
  96. })
  97. }
  98. } else {
  99. if (isDef(getKey) && isUndef(cache)) {
  100. warnOnce(
  101. `[vue-server-renderer] Component ${
  102. Ctor.options.name || '(anonymous)'
  103. } implemented serverCacheKey, ` +
  104. 'but no cache was provided to the renderer.'
  105. )
  106. }
  107. if (isDef(getKey) && isUndef(name)) {
  108. warnOnce(
  109. `[vue-server-renderer] Components that implement "serverCacheKey" ` +
  110. `must also define a unique "name" option.`
  111. )
  112. }
  113. renderComponentInner(node, isRoot, context)
  114. }
  115. }
  116. function renderComponentWithCache (node, isRoot, key, context) {
  117. const write = context.write
  118. write.caching = true
  119. const buffer = write.cacheBuffer
  120. const bufferIndex = buffer.push('') - 1
  121. const componentBuffer = write.componentBuffer
  122. componentBuffer.push(new Set())
  123. context.renderStates.push({
  124. type: 'ComponentWithCache',
  125. key,
  126. buffer,
  127. bufferIndex,
  128. componentBuffer
  129. })
  130. renderComponentInner(node, isRoot, context)
  131. }
  132. function renderComponentInner (node, isRoot, context) {
  133. const prevActive = context.activeInstance
  134. // expose userContext on vnode
  135. node.ssrContext = context.userContext
  136. const child = context.activeInstance = createComponentInstanceForVnode(
  137. node,
  138. context.activeInstance
  139. )
  140. normalizeRender(child)
  141. const childNode = child._render()
  142. childNode.parent = node
  143. context.renderStates.push({
  144. type: 'Component',
  145. prevActive
  146. })
  147. renderNode(childNode, isRoot, context)
  148. }
  149. function renderStringNode (el, context) {
  150. const { write, next } = context
  151. if (isUndef(el.children) || el.children.length === 0) {
  152. write(el.open + (el.close || ''), next)
  153. } else {
  154. const children: Array<VNode> = el.children
  155. context.renderStates.push({
  156. type: 'Element',
  157. rendered: 0,
  158. total: children.length,
  159. endTag: el.close, children
  160. })
  161. write(el.open, next)
  162. }
  163. }
  164. function renderElement (el, isRoot, context) {
  165. const { write, next } = context
  166. if (isTrue(isRoot)) {
  167. if (!el.data) el.data = {}
  168. if (!el.data.attrs) el.data.attrs = {}
  169. el.data.attrs[SSR_ATTR] = 'true'
  170. }
  171. if (el.functionalOptions) {
  172. registerComponentForCache(el.functionalOptions, write)
  173. }
  174. const startTag = renderStartingTag(el, context)
  175. const endTag = `</${el.tag}>`
  176. if (context.isUnaryTag(el.tag)) {
  177. write(startTag, next)
  178. } else if (isUndef(el.children) || el.children.length === 0) {
  179. write(startTag + endTag, next)
  180. } else {
  181. const children: Array<VNode> = el.children
  182. context.renderStates.push({
  183. type: 'Element',
  184. rendered: 0,
  185. total: children.length,
  186. endTag, children
  187. })
  188. write(startTag, next)
  189. }
  190. }
  191. function hasAncestorData (node: VNode) {
  192. const parentNode = node.parent
  193. return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
  194. }
  195. function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
  196. let dir: VNodeDirective
  197. let tmp
  198. while (isDef(node)) {
  199. if (node.data && node.data.directives) {
  200. tmp = node.data.directives.find(dir => dir.name === 'show')
  201. if (tmp) {
  202. dir = tmp
  203. }
  204. }
  205. node = node.parent
  206. }
  207. return dir
  208. }
  209. function renderStartingTag (node: VNode, context) {
  210. let markup = `<${node.tag}`
  211. const { directives, modules } = context
  212. // construct synthetic data for module processing
  213. // because modules like style also produce code by parent VNode data
  214. if (isUndef(node.data) && hasAncestorData(node)) {
  215. node.data = {}
  216. }
  217. if (isDef(node.data)) {
  218. // check directives
  219. const dirs = node.data.directives
  220. if (dirs) {
  221. for (let i = 0; i < dirs.length; i++) {
  222. const name = dirs[i].name
  223. const dirRenderer = directives[name]
  224. if (dirRenderer && name !== 'show') {
  225. // directives mutate the node's data
  226. // which then gets rendered by modules
  227. dirRenderer(node, dirs[i])
  228. }
  229. }
  230. }
  231. // v-show directive needs to be merged from parent to child
  232. const vshowDirectiveInfo = getVShowDirectiveInfo(node)
  233. if (vshowDirectiveInfo) {
  234. directives.show(node, vshowDirectiveInfo)
  235. }
  236. // apply other modules
  237. for (let i = 0; i < modules.length; i++) {
  238. const res = modules[i](node)
  239. if (res) {
  240. markup += res
  241. }
  242. }
  243. }
  244. // attach scoped CSS ID
  245. let scopeId
  246. const activeInstance = context.activeInstance
  247. if (isDef(activeInstance) &&
  248. activeInstance !== node.context &&
  249. isDef(scopeId = activeInstance.$options._scopeId)
  250. ) {
  251. markup += ` ${(scopeId: any)}`
  252. }
  253. while (isDef(node)) {
  254. if (isDef(scopeId = node.context.$options._scopeId)) {
  255. markup += ` ${scopeId}`
  256. }
  257. node = node.parent
  258. }
  259. return markup + '>'
  260. }
  261. export function createRenderFunction (
  262. modules: Array<Function>,
  263. directives: Object,
  264. isUnaryTag: Function,
  265. cache: any
  266. ) {
  267. return function render (
  268. component: Component,
  269. write: (text: string, next: Function) => void,
  270. userContext: ?Object,
  271. done: Function
  272. ) {
  273. warned = Object.create(null)
  274. const context = new RenderContext({
  275. activeInstance: component,
  276. userContext,
  277. write, done, renderNode,
  278. isUnaryTag, modules, directives,
  279. cache
  280. })
  281. installSSRHelpers(component)
  282. normalizeRender(component)
  283. renderNode(component._render(), true, context)
  284. }
  285. }