Skip to content

Commit

Permalink
feat: support multi-chunk bundles in ssr bundle renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 3, 2017
1 parent d57f942 commit 561447d
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 29 deletions.
43 changes: 37 additions & 6 deletions src/server/create-bundle-renderer.js
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
22 changes: 13 additions & 9 deletions src/server/create-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion src/server/render-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 34 additions & 13 deletions src/server/run-in-vm.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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)
})
}

0 comments on commit 561447d

Please sign in to comment.