create-bundle-renderer.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /* @flow */
  2. import { createBundleRunner } from './create-bundle-runner'
  3. import type { Renderer, RenderOptions } from '../create-renderer'
  4. import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
  5. const fs = require('fs')
  6. const path = require('path')
  7. const PassThrough = require('stream').PassThrough
  8. const INVALID_MSG =
  9. 'Invalid server-rendering bundle format. Should be a string ' +
  10. 'or a bundle Object of type:\n\n' +
  11. `{
  12. entry: string;
  13. files: { [filename: string]: string; };
  14. maps: { [filename: string]: string; };
  15. }\n`
  16. // The render bundle can either be a string (single bundled file)
  17. // or a bundle manifest object generated by vue-ssr-webpack-plugin.
  18. type RenderBundle = {
  19. basedir?: string;
  20. entry: string;
  21. files: { [filename: string]: string; };
  22. maps: { [filename: string]: string; };
  23. modules?: { [filename: string]: Array<string> };
  24. };
  25. export function createBundleRendererCreator (createRenderer: () => Renderer) {
  26. return function createBundleRenderer (
  27. bundle: string | RenderBundle,
  28. rendererOptions?: RenderOptions = {}
  29. ) {
  30. let files, entry, maps
  31. let basedir = rendererOptions.basedir
  32. // load bundle if given filepath
  33. if (
  34. typeof bundle === 'string' &&
  35. /\.js(on)?$/.test(bundle) &&
  36. path.isAbsolute(bundle)
  37. ) {
  38. if (fs.existsSync(bundle)) {
  39. const isJSON = /\.json$/.test(bundle)
  40. basedir = basedir || path.dirname(bundle)
  41. bundle = fs.readFileSync(bundle, 'utf-8')
  42. if (isJSON) {
  43. try {
  44. bundle = JSON.parse(bundle)
  45. } catch (e) {
  46. throw new Error(`Invalid JSON bundle file: ${bundle}`)
  47. }
  48. }
  49. } else {
  50. throw new Error(`Cannot locate bundle file: ${bundle}`)
  51. }
  52. }
  53. if (typeof bundle === 'object') {
  54. entry = bundle.entry
  55. files = bundle.files
  56. basedir = basedir || bundle.basedir
  57. maps = createSourceMapConsumers(bundle.maps)
  58. if (typeof entry !== 'string' || typeof files !== 'object') {
  59. throw new Error(INVALID_MSG)
  60. }
  61. } else if (typeof bundle === 'string') {
  62. entry = '__vue_ssr_bundle__'
  63. files = { '__vue_ssr_bundle__': bundle }
  64. maps = {}
  65. } else {
  66. throw new Error(INVALID_MSG)
  67. }
  68. const renderer = createRenderer(rendererOptions)
  69. const run = createBundleRunner(
  70. entry,
  71. files,
  72. basedir,
  73. rendererOptions.runInNewContext
  74. )
  75. return {
  76. renderToString: (context?: Object, cb: (err: ?Error, res: ?string) => void) => {
  77. if (typeof context === 'function') {
  78. cb = context
  79. context = {}
  80. }
  81. run(context).catch(err => {
  82. rewriteErrorTrace(err, maps)
  83. cb(err)
  84. }).then(app => {
  85. if (app) {
  86. renderer.renderToString(app, context, (err, res) => {
  87. rewriteErrorTrace(err, maps)
  88. cb(err, res)
  89. })
  90. }
  91. })
  92. },
  93. renderToStream: (context?: Object) => {
  94. const res = new PassThrough()
  95. run(context).catch(err => {
  96. rewriteErrorTrace(err, maps)
  97. // avoid emitting synchronously before user can
  98. // attach error listener
  99. process.nextTick(() => {
  100. res.emit('error', err)
  101. })
  102. }).then(app => {
  103. if (app) {
  104. const renderStream = renderer.renderToStream(app, context)
  105. renderStream.on('error', err => {
  106. rewriteErrorTrace(err, maps)
  107. res.emit('error', err)
  108. })
  109. // relay HTMLStream special events
  110. if (rendererOptions && rendererOptions.template) {
  111. renderStream.on('beforeStart', () => {
  112. res.emit('beforeStart')
  113. })
  114. renderStream.on('beforeEnd', () => {
  115. res.emit('beforeEnd')
  116. })
  117. }
  118. renderStream.pipe(res)
  119. }
  120. })
  121. return res
  122. }
  123. }
  124. }
  125. }