ssr-bundle-render.spec.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import path from 'path'
  2. import webpack from 'webpack'
  3. import MemoeryFS from 'memory-fs'
  4. import VueSSRPlugin from 'vue-ssr-webpack-plugin'
  5. import { createBundleRenderer } from '../../packages/vue-server-renderer'
  6. function createRenderer (file, cb, options) {
  7. const asBundle = !!(options && options.asBundle)
  8. if (options) delete options.asBundle
  9. const config = {
  10. target: 'node',
  11. entry: path.resolve(__dirname, 'fixtures', file),
  12. devtool: asBundle ? '#source-map' : false,
  13. output: {
  14. path: '/',
  15. filename: 'bundle.js',
  16. libraryTarget: 'commonjs2'
  17. },
  18. module: {
  19. rules: [{ test: /\.js$/, loader: 'babel-loader' }]
  20. },
  21. externals: [require.resolve('../../dist/vue.runtime.common.js')],
  22. plugins: asBundle
  23. ? [new VueSSRPlugin()]
  24. : []
  25. }
  26. const compiler = webpack(config)
  27. const fs = new MemoeryFS()
  28. compiler.outputFileSystem = fs
  29. compiler.run((err, stats) => {
  30. expect(err).toBeFalsy()
  31. expect(stats.errors).toBeFalsy()
  32. const bundle = asBundle
  33. ? JSON.parse(fs.readFileSync('/vue-ssr-bundle.json', 'utf-8'))
  34. : fs.readFileSync('/bundle.js', 'utf-8')
  35. const renderer = createBundleRenderer(bundle, options)
  36. cb(renderer)
  37. })
  38. }
  39. describe('SSR: bundle renderer', () => {
  40. it('renderToString', done => {
  41. createRenderer('app.js', renderer => {
  42. const context = { url: '/test' }
  43. renderer.renderToString(context, (err, res) => {
  44. expect(err).toBeNull()
  45. expect(res).toBe('<div server-rendered="true">/test</div>')
  46. expect(context.msg).toBe('hello')
  47. done()
  48. })
  49. })
  50. })
  51. it('renderToStream', done => {
  52. createRenderer('app.js', renderer => {
  53. const context = { url: '/test' }
  54. const stream = renderer.renderToStream(context)
  55. let res = ''
  56. stream.on('data', chunk => {
  57. res += chunk.toString()
  58. })
  59. stream.on('end', () => {
  60. expect(res).toBe('<div server-rendered="true">/test</div>')
  61. expect(context.msg).toBe('hello')
  62. done()
  63. })
  64. })
  65. })
  66. it('renderToString catch error', done => {
  67. createRenderer('error.js', renderer => {
  68. renderer.renderToString(err => {
  69. expect(err.message).toBe('foo')
  70. done()
  71. })
  72. })
  73. })
  74. it('renderToStream catch error', done => {
  75. createRenderer('error.js', renderer => {
  76. const stream = renderer.renderToStream()
  77. stream.on('error', err => {
  78. expect(err.message).toBe('foo')
  79. done()
  80. })
  81. })
  82. })
  83. it('render with cache (get/set)', done => {
  84. const cache = {}
  85. const get = jasmine.createSpy('get')
  86. const set = jasmine.createSpy('set')
  87. const options = {
  88. cache: {
  89. // async
  90. get: (key, cb) => {
  91. setTimeout(() => {
  92. get(key)
  93. cb(cache[key])
  94. }, 0)
  95. },
  96. set: (key, val) => {
  97. set(key, val)
  98. cache[key] = val
  99. }
  100. }
  101. }
  102. createRenderer('cache.js', renderer => {
  103. const expected = '<div server-rendered="true">/test</div>'
  104. const key = 'app::1'
  105. renderer.renderToString((err, res) => {
  106. expect(err).toBeNull()
  107. expect(res).toBe(expected)
  108. expect(get).toHaveBeenCalledWith(key)
  109. expect(set).toHaveBeenCalledWith(key, expected)
  110. expect(cache[key]).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. }, options)
  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. cache: {
  128. // async
  129. has: (key, cb) => {
  130. has(key)
  131. cb(!!cache[key])
  132. },
  133. // sync
  134. get: key => {
  135. get(key)
  136. return cache[key]
  137. },
  138. set: (key, val) => {
  139. set(key, val)
  140. cache[key] = val
  141. }
  142. }
  143. }
  144. createRenderer('cache.js', renderer => {
  145. const expected = '<div server-rendered="true">/test</div>'
  146. const key = 'app::1'
  147. renderer.renderToString((err, res) => {
  148. expect(err).toBeNull()
  149. expect(res).toBe(expected)
  150. expect(has).toHaveBeenCalledWith(key)
  151. expect(get).not.toHaveBeenCalled()
  152. expect(set).toHaveBeenCalledWith(key, expected)
  153. expect(cache[key]).toBe(expected)
  154. renderer.renderToString((err, res) => {
  155. expect(err).toBeNull()
  156. expect(res).toBe(expected)
  157. expect(has.calls.count()).toBe(2)
  158. expect(get.calls.count()).toBe(1)
  159. expect(set.calls.count()).toBe(1)
  160. done()
  161. })
  162. })
  163. }, options)
  164. })
  165. it('renderToString (bundle format with code split)', done => {
  166. createRenderer('split.js', renderer => {
  167. const context = { url: '/test' }
  168. renderer.renderToString(context, (err, res) => {
  169. expect(err).toBeNull()
  170. expect(res).toBe('<div server-rendered="true">/test<div>async</div></div>')
  171. done()
  172. })
  173. }, { asBundle: true })
  174. })
  175. it('renderToStream (bundle format with code split)', done => {
  176. createRenderer('split.js', renderer => {
  177. const context = { url: '/test' }
  178. const stream = renderer.renderToStream(context)
  179. let res = ''
  180. stream.on('data', chunk => {
  181. res += chunk.toString()
  182. })
  183. stream.on('end', () => {
  184. expect(res).toBe('<div server-rendered="true">/test<div>async</div></div>')
  185. done()
  186. })
  187. }, { asBundle: true })
  188. })
  189. it('renderToString catch error (bundle format with source map)', done => {
  190. createRenderer('error.js', renderer => {
  191. renderer.renderToString(err => {
  192. expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
  193. expect(err.message).toBe('foo')
  194. done()
  195. })
  196. }, { asBundle: true })
  197. })
  198. it('renderToString catch error (bundle format with source map)', done => {
  199. createRenderer('error.js', renderer => {
  200. const stream = renderer.renderToStream()
  201. stream.on('error', err => {
  202. expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
  203. expect(err.message).toBe('foo')
  204. done()
  205. })
  206. }, { asBundle: true })
  207. })
  208. it('renderToString with template', done => {
  209. createRenderer('app.js', renderer => {
  210. const context = {
  211. head: '<meta name="viewport" content="width=device-width">',
  212. styles: '<style>h1 { color: red }</style>',
  213. state: { a: 1 },
  214. url: '/test'
  215. }
  216. renderer.renderToString(context, (err, res) => {
  217. expect(err).toBeNull()
  218. expect(res).toContain(
  219. `<html><head>${context.head}${context.styles}</head><body>` +
  220. `<div server-rendered="true">/test</div>` +
  221. `<script>window.__INITIAL_STATE__={"a":1}</script>` +
  222. `</body></html>`
  223. )
  224. expect(context.msg).toBe('hello')
  225. done()
  226. })
  227. }, {
  228. template: `<html><head></head><body><!--vue-ssr-outlet--></body></html>`
  229. })
  230. })
  231. it('renderToStream with template', done => {
  232. createRenderer('app.js', renderer => {
  233. const context = {
  234. head: '<meta name="viewport" content="width=device-width">',
  235. styles: '<style>h1 { color: red }</style>',
  236. state: { a: 1 },
  237. url: '/test'
  238. }
  239. const stream = renderer.renderToStream(context)
  240. let res = ''
  241. stream.on('data', chunk => {
  242. res += chunk.toString()
  243. })
  244. stream.on('end', () => {
  245. expect(res).toContain(
  246. `<html><head>${context.head}${context.styles}</head><body>` +
  247. `<div server-rendered="true">/test</div>` +
  248. `<script>window.__INITIAL_STATE__={"a":1}</script>` +
  249. `</body></html>`
  250. )
  251. expect(context.msg).toBe('hello')
  252. done()
  253. })
  254. }, {
  255. template: `<html><head></head><body><!--vue-ssr-outlet--></body></html>`
  256. })
  257. })
  258. })