ssr-bundle-render.spec.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import LRU from 'lru-cache'
  2. import { compileWithWebpack } from './compile-with-webpack'
  3. import { createBundleRenderer } from '../../packages/vue-server-renderer'
  4. import VueSSRServerPlugin from '../../packages/vue-server-renderer/server-plugin'
  5. export function createRenderer (file, options, cb) {
  6. if (typeof options === 'function') {
  7. cb = options
  8. options = undefined
  9. }
  10. const asBundle = !!(options && options.asBundle)
  11. if (options) delete options.asBundle
  12. compileWithWebpack(file, {
  13. target: 'node',
  14. devtool: asBundle ? '#source-map' : false,
  15. output: {
  16. path: '/',
  17. filename: 'bundle.js',
  18. libraryTarget: 'commonjs2'
  19. },
  20. externals: [require.resolve('../../dist/vue.runtime.common.js')],
  21. plugins: asBundle
  22. ? [new VueSSRServerPlugin()]
  23. : []
  24. }, fs => {
  25. const bundle = asBundle
  26. ? JSON.parse(fs.readFileSync('/vue-ssr-server-bundle.json', 'utf-8'))
  27. : fs.readFileSync('/bundle.js', 'utf-8')
  28. const renderer = createBundleRenderer(bundle, options)
  29. cb(renderer)
  30. })
  31. }
  32. describe('SSR: bundle renderer', () => {
  33. createAssertions(true)
  34. createAssertions(false)
  35. })
  36. function createAssertions (runInNewContext) {
  37. it('renderToString', done => {
  38. createRenderer('app.js', { runInNewContext }, renderer => {
  39. const context = { url: '/test' }
  40. renderer.renderToString(context, (err, res) => {
  41. expect(err).toBeNull()
  42. expect(res).toBe('<div data-server-rendered="true">/test</div>')
  43. expect(context.msg).toBe('hello')
  44. done()
  45. })
  46. })
  47. })
  48. it('renderToStream', done => {
  49. createRenderer('app.js', { runInNewContext }, renderer => {
  50. const context = { url: '/test' }
  51. const stream = renderer.renderToStream(context)
  52. let res = ''
  53. stream.on('data', chunk => {
  54. res += chunk.toString()
  55. })
  56. stream.on('end', () => {
  57. expect(res).toBe('<div data-server-rendered="true">/test</div>')
  58. expect(context.msg).toBe('hello')
  59. done()
  60. })
  61. })
  62. })
  63. it('renderToString catch error', done => {
  64. createRenderer('error.js', { runInNewContext }, renderer => {
  65. renderer.renderToString(err => {
  66. expect(err.message).toBe('foo')
  67. done()
  68. })
  69. })
  70. })
  71. it('renderToStream catch error', done => {
  72. createRenderer('error.js', { runInNewContext }, renderer => {
  73. const stream = renderer.renderToStream()
  74. stream.on('error', err => {
  75. expect(err.message).toBe('foo')
  76. done()
  77. })
  78. })
  79. })
  80. it('render with cache (get/set)', done => {
  81. const cache = {}
  82. const get = jasmine.createSpy('get')
  83. const set = jasmine.createSpy('set')
  84. const options = {
  85. runInNewContext,
  86. cache: {
  87. // async
  88. get: (key, cb) => {
  89. setTimeout(() => {
  90. get(key)
  91. cb(cache[key])
  92. }, 0)
  93. },
  94. set: (key, val) => {
  95. set(key, val)
  96. cache[key] = val
  97. }
  98. }
  99. }
  100. createRenderer('cache.js', options, renderer => {
  101. const expected = '<div data-server-rendered="true">/test</div>'
  102. const key = 'app::1'
  103. renderer.renderToString((err, res) => {
  104. expect(err).toBeNull()
  105. expect(res).toBe(expected)
  106. expect(get).toHaveBeenCalledWith(key)
  107. const setArgs = set.calls.argsFor(0)
  108. expect(setArgs[0]).toBe(key)
  109. expect(setArgs[1].html).toBe(expected)
  110. expect(cache[key].html).toBe(expected)
  111. renderer.renderToString((err, res) => {
  112. expect(err).toBeNull()
  113. expect(res).toBe(expected)
  114. expect(get.calls.count()).toBe(2)
  115. expect(set.calls.count()).toBe(1)
  116. done()
  117. })
  118. })
  119. })
  120. })
  121. it('render with cache (get/set/has)', done => {
  122. const cache = {}
  123. const has = jasmine.createSpy('has')
  124. const get = jasmine.createSpy('get')
  125. const set = jasmine.createSpy('set')
  126. const options = {
  127. runInNewContext,
  128. cache: {
  129. // async
  130. has: (key, cb) => {
  131. has(key)
  132. cb(!!cache[key])
  133. },
  134. // sync
  135. get: key => {
  136. get(key)
  137. return cache[key]
  138. },
  139. set: (key, val) => {
  140. set(key, val)
  141. cache[key] = val
  142. }
  143. }
  144. }
  145. createRenderer('cache.js', options, renderer => {
  146. const expected = '<div data-server-rendered="true">/test</div>'
  147. const key = 'app::1'
  148. renderer.renderToString((err, res) => {
  149. expect(err).toBeNull()
  150. expect(res).toBe(expected)
  151. expect(has).toHaveBeenCalledWith(key)
  152. expect(get).not.toHaveBeenCalled()
  153. const setArgs = set.calls.argsFor(0)
  154. expect(setArgs[0]).toBe(key)
  155. expect(setArgs[1].html).toBe(expected)
  156. expect(cache[key].html).toBe(expected)
  157. renderer.renderToString((err, res) => {
  158. expect(err).toBeNull()
  159. expect(res).toBe(expected)
  160. expect(has.calls.count()).toBe(2)
  161. expect(get.calls.count()).toBe(1)
  162. expect(set.calls.count()).toBe(1)
  163. done()
  164. })
  165. })
  166. })
  167. })
  168. it('render with cache (nested)', done => {
  169. const cache = LRU({ maxAge: Infinity })
  170. spyOn(cache, 'get').and.callThrough()
  171. spyOn(cache, 'set').and.callThrough()
  172. const options = {
  173. cache,
  174. runInNewContext
  175. }
  176. createRenderer('nested-cache.js', options, renderer => {
  177. const expected = '<div data-server-rendered="true">/test</div>'
  178. const key = 'app::1'
  179. const context1 = { registered: [] }
  180. const context2 = { registered: [] }
  181. renderer.renderToString(context1, (err, res) => {
  182. expect(err).toBeNull()
  183. expect(res).toBe(expected)
  184. expect(cache.set.calls.count()).toBe(3) // 3 nested components cached
  185. const cached = cache.get(key)
  186. expect(cached.html).toBe(expected)
  187. expect(cache.get.calls.count()).toBe(1)
  188. // assert component usage registration for nested children
  189. expect(context1.registered).toEqual(['app', 'child', 'grandchild'])
  190. renderer.renderToString(context2, (err, res) => {
  191. expect(err).toBeNull()
  192. expect(res).toBe(expected)
  193. expect(cache.set.calls.count()).toBe(3) // no new cache sets
  194. expect(cache.get.calls.count()).toBe(2) // 1 get for root
  195. expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
  196. done()
  197. })
  198. })
  199. })
  200. })
  201. it('renderToString (bundle format with code split)', done => {
  202. createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
  203. const context = { url: '/test' }
  204. renderer.renderToString(context, (err, res) => {
  205. expect(err).toBeNull()
  206. expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
  207. done()
  208. })
  209. })
  210. })
  211. it('renderToStream (bundle format with code split)', done => {
  212. createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
  213. const context = { url: '/test' }
  214. const stream = renderer.renderToStream(context)
  215. let res = ''
  216. stream.on('data', chunk => {
  217. res += chunk.toString()
  218. })
  219. stream.on('end', () => {
  220. expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
  221. done()
  222. })
  223. })
  224. })
  225. it('renderToString catch error (bundle format with source map)', done => {
  226. createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
  227. renderer.renderToString(err => {
  228. expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
  229. expect(err.message).toBe('foo')
  230. done()
  231. })
  232. })
  233. })
  234. it('renderToString catch error (bundle format with source map)', done => {
  235. createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
  236. const stream = renderer.renderToStream()
  237. stream.on('error', err => {
  238. expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
  239. expect(err.message).toBe('foo')
  240. done()
  241. })
  242. })
  243. })
  244. }