/* @flow */
|
|
import { createPromiseCallback } from '../util'
|
import { createBundleRunner } from './create-bundle-runner'
|
import type { Renderer, RenderOptions } from '../create-renderer'
|
import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
|
|
const fs = require('fs')
|
const path = require('path')
|
const PassThrough = require('stream').PassThrough
|
|
const INVALID_MSG =
|
'Invalid server-rendering bundle format. Should be a string ' +
|
'or a bundle Object of type:\n\n' +
|
`{
|
entry: string;
|
files: { [filename: string]: string; };
|
maps: { [filename: string]: string; };
|
}\n`
|
|
// The render bundle can either be a string (single bundled file)
|
// or a bundle manifest object generated by vue-ssr-webpack-plugin.
|
type RenderBundle = {
|
basedir?: string;
|
entry: string;
|
files: { [filename: string]: string; };
|
maps: { [filename: string]: string; };
|
modules?: { [filename: string]: Array<string> };
|
};
|
|
export function createBundleRendererCreator (
|
createRenderer: (options?: RenderOptions) => Renderer
|
) {
|
return function createBundleRenderer (
|
bundle: string | RenderBundle,
|
rendererOptions?: RenderOptions = {}
|
) {
|
let files, entry, maps
|
let basedir = rendererOptions.basedir
|
|
// load bundle if given filepath
|
if (
|
typeof bundle === 'string' &&
|
/\.js(on)?$/.test(bundle) &&
|
path.isAbsolute(bundle)
|
) {
|
if (fs.existsSync(bundle)) {
|
const isJSON = /\.json$/.test(bundle)
|
basedir = basedir || path.dirname(bundle)
|
bundle = fs.readFileSync(bundle, 'utf-8')
|
if (isJSON) {
|
try {
|
bundle = JSON.parse(bundle)
|
} catch (e) {
|
throw new Error(`Invalid JSON bundle file: ${bundle}`)
|
}
|
}
|
} else {
|
throw new Error(`Cannot locate bundle file: ${bundle}`)
|
}
|
}
|
|
if (typeof bundle === 'object') {
|
entry = bundle.entry
|
files = bundle.files
|
basedir = basedir || bundle.basedir
|
maps = createSourceMapConsumers(bundle.maps)
|
if (typeof entry !== 'string' || typeof files !== 'object') {
|
throw new Error(INVALID_MSG)
|
}
|
} else if (typeof bundle === 'string') {
|
entry = '__vue_ssr_bundle__'
|
files = { '__vue_ssr_bundle__': bundle }
|
maps = {}
|
} else {
|
throw new Error(INVALID_MSG)
|
}
|
|
const renderer = createRenderer(rendererOptions)
|
|
const run = createBundleRunner(
|
entry,
|
files,
|
basedir,
|
rendererOptions.runInNewContext
|
)
|
|
return {
|
renderToString: (context?: Object, cb: any) => {
|
if (typeof context === 'function') {
|
cb = context
|
context = {}
|
}
|
|
let promise
|
if (!cb) {
|
({ promise, cb } = createPromiseCallback())
|
}
|
|
run(context).catch(err => {
|
rewriteErrorTrace(err, maps)
|
cb(err)
|
}).then(app => {
|
if (app) {
|
renderer.renderToString(app, context, (err, res) => {
|
rewriteErrorTrace(err, maps)
|
cb(err, res)
|
})
|
}
|
})
|
|
return promise
|
},
|
|
renderToStream: (context?: Object) => {
|
const res = new PassThrough()
|
run(context).catch(err => {
|
rewriteErrorTrace(err, maps)
|
// avoid emitting synchronously before user can
|
// attach error listener
|
process.nextTick(() => {
|
res.emit('error', err)
|
})
|
}).then(app => {
|
if (app) {
|
const renderStream = renderer.renderToStream(app, context)
|
|
renderStream.on('error', err => {
|
rewriteErrorTrace(err, maps)
|
res.emit('error', err)
|
})
|
|
// relay HTMLStream special events
|
if (rendererOptions && rendererOptions.template) {
|
renderStream.on('beforeStart', () => {
|
res.emit('beforeStart')
|
})
|
renderStream.on('beforeEnd', () => {
|
res.emit('beforeEnd')
|
})
|
}
|
|
renderStream.pipe(res)
|
}
|
})
|
|
return res
|
}
|
}
|
}
|
}
|