73be1add224c7319803973f6a2f9ce0adf503b6d82e2917e8ec280d4da7cd412632147691bdab22aec273c1b78fa383710723093c763bb262faa5f288f14e0 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /* @flow */
  2. import { escape } from 'web/server/util'
  3. import { SSR_ATTR } from 'shared/constants'
  4. import { RenderContext } from './render-context'
  5. import { resolveAsset } from 'core/util/options'
  6. import { generateComponentTrace } from 'core/util/debug'
  7. import { ssrCompileToFunctions } from 'web/server/compiler'
  8. import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
  9. import { isDef, isUndef, isTrue } from 'shared/util'
  10. import {
  11. createComponent,
  12. createComponentInstanceForVnode
  13. } from 'core/vdom/create-component'
  14. let warned = Object.create(null)
  15. const warnOnce = msg => {
  16. if (!warned[msg]) {
  17. warned[msg] = true
  18. // eslint-disable-next-line no-console
  19. console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
  20. }
  21. }
  22. const onCompilationError = (err, vm) => {
  23. const trace = vm ? generateComponentTrace(vm) : ''
  24. throw new Error(`\n\u001b[31m${err}${trace}\u001b[39m\n`)
  25. }
  26. const normalizeRender = vm => {
  27. const { render, template, _scopeId } = vm.$options
  28. if (isUndef(render)) {
  29. if (template) {
  30. const compiled = ssrCompileToFunctions(template, {
  31. scopeId: _scopeId,
  32. warn: onCompilationError
  33. }, vm)
  34. vm.$options.render = compiled.render
  35. vm.$options.staticRenderFns = compiled.staticRenderFns
  36. } else {
  37. throw new Error(
  38. `render function or template not defined in component: ${
  39. vm.$options.name || vm.$options._componentTag || 'anonymous'
  40. }`
  41. )
  42. }
  43. }
  44. }
  45. function waitForServerPrefetch (vm, resolve, reject) {
  46. let handlers = vm.$options.serverPrefetch
  47. if (isDef(handlers)) {
  48. if (!Array.isArray(handlers)) handlers = [handlers]
  49. try {
  50. const promises = []
  51. for (let i = 0, j = handlers.length; i < j; i++) {
  52. const result = handlers[i].call(vm, vm)
  53. if (result && typeof result.then === 'function') {
  54. promises.push(result)
  55. }
  56. }
  57. Promise.all(promises).then(resolve).catch(reject)
  58. return
  59. } catch (e) {
  60. reject(e)
  61. }
  62. }
  63. resolve()
  64. }
  65. function renderNode (node, isRoot, context) {
  66. if (node.isString) {
  67. renderStringNode(node, context)
  68. } else if (isDef(node.componentOptions)) {
  69. renderComponent(node, isRoot, context)
  70. } else if (isDef(node.tag)) {
  71. renderElement(node, isRoot, context)
  72. } else if (isTrue(node.isComment)) {
  73. if (isDef(node.asyncFactory)) {
  74. // async component
  75. renderAsyncComponent(node, isRoot, context)
  76. } else {
  77. context.write(`<!--${node.text}-->`, context.next)
  78. }
  79. } else {
  80. context.write(
  81. node.raw ? node.text : escape(String(node.text)),
  82. context.next
  83. )
  84. }
  85. }
  86. function registerComponentForCache (options, write) {
  87. // exposed by vue-loader, need to call this if cache hit because
  88. // component lifecycle hooks will not be called.
  89. const register = options._ssrRegister
  90. if (write.caching && isDef(register)) {
  91. write.componentBuffer[write.componentBuffer.length - 1].add(register)
  92. }
  93. return register
  94. }
  95. function renderComponent (node, isRoot, context) {
  96. const { write, next, userContext } = context
  97. // check cache hit
  98. const Ctor = node.componentOptions.Ctor
  99. const getKey = Ctor.options.serverCacheKey
  100. const name = Ctor.options.name
  101. const cache = context.cache
  102. const registerComponent = registerComponentForCache(Ctor.options, write)
  103. if (isDef(getKey) && isDef(cache) && isDef(name)) {
  104. const rawKey = getKey(node.componentOptions.propsData)
  105. if (rawKey === false) {
  106. renderComponentInner(node, isRoot, context)
  107. return
  108. }
  109. const key = name + '::' + rawKey
  110. const { has, get } = context
  111. if (isDef(has)) {
  112. has(key, hit => {
  113. if (hit === true && isDef(get)) {
  114. get(key, res => {
  115. if (isDef(registerComponent)) {
  116. registerComponent(userContext)
  117. }
  118. res.components.forEach(register => register(userContext))
  119. write(res.html, next)
  120. })
  121. } else {
  122. renderComponentWithCache(node, isRoot, key, context)
  123. }
  124. })
  125. } else if (isDef(get)) {
  126. get(key, res => {
  127. if (isDef(res)) {
  128. if (isDef(registerComponent)) {
  129. registerComponent(userContext)
  130. }
  131. res.components.forEach(register => register(userContext))
  132. write(res.html, next)
  133. } else {
  134. renderComponentWithCache(node, isRoot, key, context)
  135. }
  136. })
  137. }
  138. } else {
  139. if (isDef(getKey) && isUndef(cache)) {
  140. warnOnce(
  141. `[vue-server-renderer] Component ${
  142. Ctor.options.name || '(anonymous)'
  143. } implemented serverCacheKey, ` +
  144. 'but no cache was provided to the renderer.'
  145. )
  146. }
  147. if (isDef(getKey) && isUndef(name)) {
  148. warnOnce(
  149. `[vue-server-renderer] Components that implement "serverCacheKey" ` +
  150. `must also define a unique "name" option.`
  151. )
  152. }
  153. renderComponentInner(node, isRoot, context)
  154. }
  155. }
  156. function renderComponentWithCache (node, isRoot, key, context) {
  157. const write = context.write
  158. write.caching = true
  159. const buffer = write.cacheBuffer
  160. const bufferIndex = buffer.push('') - 1
  161. const componentBuffer = write.componentBuffer
  162. componentBuffer.push(new Set())
  163. context.renderStates.push({
  164. type: 'ComponentWithCache',
  165. key,
  166. buffer,
  167. bufferIndex,
  168. componentBuffer
  169. })
  170. renderComponentInner(node, isRoot, context)
  171. }
  172. function renderComponentInner (node, isRoot, context) {
  173. const prevActive = context.activeInstance
  174. // expose userContext on vnode
  175. node.ssrContext = context.userContext
  176. const child = context.activeInstance = createComponentInstanceForVnode(
  177. node,
  178. context.activeInstance
  179. )
  180. normalizeRender(child)
  181. const resolve = () => {
  182. const childNode = child._render()
  183. childNode.parent = node
  184. context.renderStates.push({
  185. type: 'Component',
  186. prevActive
  187. })
  188. renderNode(childNode, isRoot, context)
  189. }
  190. const reject = context.done
  191. waitForServerPrefetch(child, resolve, reject)
  192. }
  193. function renderAsyncComponent (node, isRoot, context) {
  194. const factory = node.asyncFactory
  195. const resolve = comp => {
  196. if (comp.__esModule && comp.default) {
  197. comp = comp.default
  198. }
  199. const { data, children, tag } = node.asyncMeta
  200. const nodeContext = node.asyncMeta.context
  201. const resolvedNode: any = createComponent(
  202. comp,
  203. data,
  204. nodeContext,
  205. children,
  206. tag
  207. )
  208. if (resolvedNode) {
  209. if (resolvedNode.componentOptions) {
  210. // normal component
  211. renderComponent(resolvedNode, isRoot, context)
  212. } else if (!Array.isArray(resolvedNode)) {
  213. // single return node from functional component
  214. renderNode(resolvedNode, isRoot, context)
  215. } else {
  216. // multiple return nodes from functional component
  217. context.renderStates.push({
  218. type: 'Fragment',
  219. children: resolvedNode,
  220. rendered: 0,
  221. total: resolvedNode.length
  222. })
  223. context.next()
  224. }
  225. } else {
  226. // invalid component, but this does not throw on the client
  227. // so render empty comment node
  228. context.write(`<!---->`, context.next)
  229. }
  230. }
  231. if (factory.resolved) {
  232. resolve(factory.resolved)
  233. return
  234. }
  235. const reject = context.done
  236. let res
  237. try {
  238. res = factory(resolve, reject)
  239. } catch (e) {
  240. reject(e)
  241. }
  242. if (res) {
  243. if (typeof res.then === 'function') {
  244. res.then(resolve, reject).catch(reject)
  245. } else {
  246. // new syntax in 2.3
  247. const comp = res.component
  248. if (comp && typeof comp.then === 'function') {
  249. comp.then(resolve, reject).catch(reject)
  250. }
  251. }
  252. }
  253. }
  254. function renderStringNode (el, context) {
  255. const { write, next } = context
  256. if (isUndef(el.children) || el.children.length === 0) {
  257. write(el.open + (el.close || ''), next)
  258. } else {
  259. const children: Array<VNode> = el.children
  260. context.renderStates.push({
  261. type: 'Element',
  262. children,
  263. rendered: 0,
  264. total: children.length,
  265. endTag: el.close
  266. })
  267. write(el.open, next)
  268. }
  269. }
  270. function renderElement (el, isRoot, context) {
  271. const { write, next } = context
  272. if (isTrue(isRoot)) {
  273. if (!el.data) el.data = {}
  274. if (!el.data.attrs) el.data.attrs = {}
  275. el.data.attrs[SSR_ATTR] = 'true'
  276. }
  277. if (el.fnOptions) {
  278. registerComponentForCache(el.fnOptions, write)
  279. }
  280. const startTag = renderStartingTag(el, context)
  281. const endTag = `</${el.tag}>`
  282. if (context.isUnaryTag(el.tag)) {
  283. write(startTag, next)
  284. } else if (isUndef(el.children) || el.children.length === 0) {
  285. write(startTag + endTag, next)
  286. } else {
  287. const children: Array<VNode> = el.children
  288. context.renderStates.push({
  289. type: 'Element',
  290. children,
  291. rendered: 0,
  292. total: children.length,
  293. endTag
  294. })
  295. write(startTag, next)
  296. }
  297. }
  298. function hasAncestorData (node: VNode) {
  299. const parentNode = node.parent
  300. return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
  301. }
  302. function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
  303. let dir: VNodeDirective
  304. let tmp
  305. while (isDef(node)) {
  306. if (node.data && node.data.directives) {
  307. tmp = node.data.directives.find(dir => dir.name === 'show')
  308. if (tmp) {
  309. dir = tmp
  310. }
  311. }
  312. node = node.parent
  313. }
  314. return dir
  315. }
  316. function renderStartingTag (node: VNode, context) {
  317. let markup = `<${node.tag}`
  318. const { directives, modules } = context
  319. // construct synthetic data for module processing
  320. // because modules like style also produce code by parent VNode data
  321. if (isUndef(node.data) && hasAncestorData(node)) {
  322. node.data = {}
  323. }
  324. if (isDef(node.data)) {
  325. // check directives
  326. const dirs = node.data.directives
  327. if (dirs) {
  328. for (let i = 0; i < dirs.length; i++) {
  329. const name = dirs[i].name
  330. if (name !== 'show') {
  331. const dirRenderer = resolveAsset(context, 'directives', name)
  332. if (dirRenderer) {
  333. // directives mutate the node's data
  334. // which then gets rendered by modules
  335. dirRenderer(node, dirs[i])
  336. }
  337. }
  338. }
  339. }
  340. // v-show directive needs to be merged from parent to child
  341. const vshowDirectiveInfo = getVShowDirectiveInfo(node)
  342. if (vshowDirectiveInfo) {
  343. directives.show(node, vshowDirectiveInfo)
  344. }
  345. // apply other modules
  346. for (let i = 0; i < modules.length; i++) {
  347. const res = modules[i](node)
  348. if (res) {
  349. markup += res
  350. }
  351. }
  352. }
  353. // attach scoped CSS ID
  354. let scopeId
  355. const activeInstance = context.activeInstance
  356. if (isDef(activeInstance) &&
  357. activeInstance !== node.context &&
  358. isDef(scopeId = activeInstance.$options._scopeId)
  359. ) {
  360. markup += ` ${(scopeId: any)}`
  361. }
  362. if (isDef(node.fnScopeId)) {
  363. markup += ` ${node.fnScopeId}`
  364. } else {
  365. while (isDef(node)) {
  366. if (isDef(scopeId = node.context.$options._scopeId)) {
  367. markup += ` ${scopeId}`
  368. }
  369. node = node.parent
  370. }
  371. }
  372. return markup + '>'
  373. }
  374. export function createRenderFunction (
  375. modules: Array<(node: VNode) => ?string>,
  376. directives: Object,
  377. isUnaryTag: Function,
  378. cache: any
  379. ) {
  380. return function render (
  381. component: Component,
  382. write: (text: string, next: Function) => void,
  383. userContext: ?Object,
  384. done: Function
  385. ) {
  386. warned = Object.create(null)
  387. const context = new RenderContext({
  388. activeInstance: component,
  389. userContext,
  390. write, done, renderNode,
  391. isUnaryTag, modules, directives,
  392. cache
  393. })
  394. installSSRHelpers(component)
  395. normalizeRender(component)
  396. const resolve = () => {
  397. renderNode(component._render(), true, context)
  398. }
  399. waitForServerPrefetch(component, resolve, done)
  400. }
  401. }