Просмотр исходного кода

feat: support multi-chunk bundles in ssr bundle renderer

Evan You 9 лет назад
Родитель
Сommit
561447d278

+ 37 - 6
src/server/create-bundle-renderer.js

@@ -1,22 +1,53 @@
+/* @flow */
+
 import runInVm from './run-in-vm'
 import { PassThrough } from 'stream'
+import type { Renderer, RenderOptions } from './create-renderer'
+
+const INVALID_MSG =
+  'Invalid server-rendering bundle format. Should be a string of bundled code ' +
+  'or an Object of type { entry: string; chunks: { [filename: string]: string }}.'
+
+// The render bundle can either be a string (single bundled file)
+// or an object containing a chunks hash of filename:code pairs with the
+// name of the entry file. The object format is used in conjunction with
+// Webpack's compilation output so that code-split chunks can also be loaded.
+type RenderBundle = string | {
+  entry: string;
+  chunks: {
+    [filename: string]: string;
+  };
+};
 
-export function createBundleRendererCreator (createRenderer) {
-  return (code, rendererOptions) => {
+export function createBundleRendererCreator (createRenderer: () => Renderer) {
+  return (bundle: RenderBundle, rendererOptions?: RenderOptions) => {
     const renderer = createRenderer(rendererOptions)
+    let chunks, entry
+    if (typeof bundle === 'object') {
+      entry = bundle.entry
+      chunks = bundle.chunks
+      if (typeof entry !== 'string' || typeof chunks !== 'object') {
+        throw new Error(INVALID_MSG)
+      }
+    } else if (typeof bundle === 'string') {
+      entry = '__vue_ssr_bundle__'
+      chunks = { '__vue_ssr_bundle__': bundle }
+    } else {
+      throw new Error(INVALID_MSG)
+    }
     return {
-      renderToString: (context, cb) => {
+      renderToString: (context?: Object, cb: (err: ?Error, res: ?string) => void) => {
         if (typeof context === 'function') {
           cb = context
           context = {}
         }
-        runInVm(code, context).then(app => {
+        runInVm(entry, chunks, context).then(app => {
           renderer.renderToString(app, cb)
         }).catch(cb)
       },
-      renderToStream: (context) => {
+      renderToStream: (context?: Object) => {
         const res = new PassThrough()
-        runInVm(code, context).then(app => {
+        runInVm(entry, chunks, context).then(app => {
           const renderStream = renderer.renderToStream(app)
           renderStream.on('error', err => {
             res.emit('error', err)

+ 13 - 9
src/server/create-renderer.js

@@ -4,20 +4,24 @@ import RenderStream from './render-stream'
 import { createWriteFunction } from './write'
 import { createRenderFunction } from './render'
 
+export type Renderer = {
+  renderToString: (component: Component, cb: (err: ?Error, res: ?string) => void) => void;
+  renderToStream: (component: Component) => RenderStream;
+};
+
+export type RenderOptions = {
+  modules: Array<Function>,
+  directives: Object,
+  isUnaryTag: Function,
+  cache: ?Object
+};
+
 export function createRenderer ({
   modules = [],
   directives = {},
   isUnaryTag = (() => false),
   cache
-}: {
-  modules: Array<Function>,
-  directives: Object,
-  isUnaryTag: Function,
-  cache: ?Object
-} = {}): {
-  renderToString: Function,
-  renderToStream: Function
-} {
+}: RenderOptions = {}): Renderer {
   const render = createRenderFunction(modules, directives, isUnaryTag, cache)
 
   return {

+ 2 - 1
src/server/render-stream.js

@@ -8,7 +8,8 @@
  * Modified by Evan You (@yyx990803)
  */
 
-import stream from 'stream'
+const stream = require('stream')
+
 import { createWriteFunction } from './write'
 
 export default class RenderStream extends stream.Readable {

+ 34 - 13
src/server/run-in-vm.js

@@ -1,5 +1,6 @@
-import NativeModule from 'module'
-import vm from 'vm'
+const NativeModule = require('module')
+const vm = require('vm')
+const path = require('path')
 
 function createContext (context) {
   const sandbox = {
@@ -18,19 +19,39 @@ function createContext (context) {
   return sandbox
 }
 
-export default function runInVm (code, _context = {}) {
+function evaluateModule (filename, chunks, context, evaluatedModules) {
+  if (evaluatedModules[filename]) {
+    return evaluatedModules[filename]
+  }
+
+  const code = chunks[filename]
+  const wrapper = NativeModule.wrap(code)
+  const compiledWrapper = vm.runInNewContext(wrapper, context, {
+    filename,
+    displayErrors: true
+  })
+  const m = { exports: {}}
+  const r = file => {
+    file = path.join('.', file)
+    if (chunks[file]) {
+      return evaluateModule(file, chunks, context, evaluatedModules)
+    } else {
+      return require(file)
+    }
+  }
+  compiledWrapper.call(m.exports, m.exports, r, m)
+
+  const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')
+    ? m.exports.default
+    : m.exports
+  evaluatedModules[filename] = res
+  return res
+}
+
+export default function runInVm (entry, chunks, _context = {}) {
   return new Promise((resolve, reject) => {
-    const wrapper = NativeModule.wrap(code)
     const context = createContext(_context)
-    const compiledWrapper = vm.runInNewContext(wrapper, context, {
-      filename: '__vue_ssr_bundle__',
-      displayErrors: true
-    })
-    const m = { exports: {}}
-    compiledWrapper.call(m.exports, m.exports, require, m)
-    const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')
-      ? m.exports.default
-      : m.exports
+    const res = evaluateModule(entry, chunks, context, {})
     resolve(typeof res === 'function' ? res(_context) : res)
   })
 }