diff options
Diffstat (limited to 'node_modules/vue/src/server/bundle-renderer')
3 files changed, 346 insertions, 0 deletions
diff --git a/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js b/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js new file mode 100644 index 00000000..05c60c35 --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js @@ -0,0 +1,151 @@ +/* @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 + } + } + } +} diff --git a/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js b/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js new file mode 100644 index 00000000..16c33c41 --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js @@ -0,0 +1,150 @@ +import { isPlainObject } from 'shared/util' + +const vm = require('vm') +const path = require('path') +const resolve = require('resolve') +const NativeModule = require('module') + +function createSandbox (context) { + const sandbox = { + Buffer, + console, + process, + setTimeout, + setInterval, + setImmediate, + clearTimeout, + clearInterval, + clearImmediate, + __VUE_SSR_CONTEXT__: context + } + sandbox.global = sandbox + return sandbox +} + +function compileModule (files, basedir, runInNewContext) { + const compiledScripts = {} + const resolvedModules = {} + + function getCompiledScript (filename) { + if (compiledScripts[filename]) { + return compiledScripts[filename] + } + const code = files[filename] + const wrapper = NativeModule.wrap(code) + const script = new vm.Script(wrapper, { + filename, + displayErrors: true + }) + compiledScripts[filename] = script + return script + } + + function evaluateModule (filename, sandbox, evaluatedFiles = {}) { + if (evaluatedFiles[filename]) { + return evaluatedFiles[filename] + } + + const script = getCompiledScript(filename) + const compiledWrapper = runInNewContext === false + ? script.runInThisContext() + : script.runInNewContext(sandbox) + const m = { exports: {}} + const r = file => { + file = path.posix.join('.', file) + if (files[file]) { + return evaluateModule(file, sandbox, evaluatedFiles) + } else if (basedir) { + return require( + resolvedModules[file] || + (resolvedModules[file] = resolve.sync(file, { basedir })) + ) + } 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 + evaluatedFiles[filename] = res + return res + } + return evaluateModule +} + +function deepClone (val) { + if (isPlainObject(val)) { + const res = {} + for (const key in val) { + res[key] = deepClone(val[key]) + } + return res + } else if (Array.isArray(val)) { + return val.slice() + } else { + return val + } +} + +export function createBundleRunner (entry, files, basedir, runInNewContext) { + const evaluate = compileModule(files, basedir, runInNewContext) + if (runInNewContext !== false && runInNewContext !== 'once') { + // new context mode: creates a fresh context and re-evaluate the bundle + // on each render. Ensures entire application state is fresh for each + // render, but incurs extra evaluation cost. + return (userContext = {}) => new Promise(resolve => { + userContext._registeredComponents = new Set() + const res = evaluate(entry, createSandbox(userContext)) + resolve(typeof res === 'function' ? res(userContext) : res) + }) + } else { + // direct mode: instead of re-evaluating the whole bundle on + // each render, it simply calls the exported function. This avoids the + // module evaluation costs but requires the source code to be structured + // slightly differently. + let runner // lazy creation so that errors can be caught by user + let initialContext + return (userContext = {}) => new Promise(resolve => { + if (!runner) { + const sandbox = runInNewContext === 'once' + ? createSandbox() + : global + // the initial context is only used for collecting possible non-component + // styles injected by vue-style-loader. + initialContext = sandbox.__VUE_SSR_CONTEXT__ = {} + runner = evaluate(entry, sandbox) + // On subsequent renders, __VUE_SSR_CONTEXT__ will not be available + // to prevent cross-request pollution. + delete sandbox.__VUE_SSR_CONTEXT__ + if (typeof runner !== 'function') { + throw new Error( + 'bundle export should be a function when using ' + + '{ runInNewContext: false }.' + ) + } + } + userContext._registeredComponents = new Set() + + // vue-style-loader styles imported outside of component lifecycle hooks + if (initialContext._styles) { + userContext._styles = deepClone(initialContext._styles) + // #6353 ensure "styles" is exposed even if no styles are injected + // in component lifecycles. + // the renderStyles fn is exposed by vue-style-loader >= 3.0.3 + const renderStyles = initialContext._renderStyles + if (renderStyles) { + Object.defineProperty(userContext, 'styles', { + enumerable: true, + get () { + return renderStyles(userContext._styles) + } + }) + } + } + + resolve(runner(userContext)) + }) + } +} diff --git a/node_modules/vue/src/server/bundle-renderer/source-map-support.js b/node_modules/vue/src/server/bundle-renderer/source-map-support.js new file mode 100644 index 00000000..1010532e --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/source-map-support.js @@ -0,0 +1,45 @@ +/* @flow */ + +const SourceMapConsumer = require('source-map').SourceMapConsumer + +const filenameRE = /\(([^)]+\.js):(\d+):(\d+)\)$/ + +export function createSourceMapConsumers (rawMaps: Object) { + const maps = {} + Object.keys(rawMaps).forEach(file => { + maps[file] = new SourceMapConsumer(rawMaps[file]) + }) + return maps +} + +export function rewriteErrorTrace (e: any, mapConsumers: { + [key: string]: SourceMapConsumer +}) { + if (e && typeof e.stack === 'string') { + e.stack = e.stack.split('\n').map(line => { + return rewriteTraceLine(line, mapConsumers) + }).join('\n') + } +} + +function rewriteTraceLine (trace: string, mapConsumers: { + [key: string]: SourceMapConsumer +}) { + const m = trace.match(filenameRE) + const map = m && mapConsumers[m[1]] + if (m != null && map) { + const originalPosition = map.originalPositionFor({ + line: Number(m[2]), + column: Number(m[3]) + }) + if (originalPosition.source != null) { + const { source, line, column } = originalPosition + const mappedPosition = `(${source.replace(/^webpack:\/\/\//, '')}:${String(line)}:${String(column)})` + return trace.replace(filenameRE, mappedPosition) + } else { + return trace + } + } else { + return trace + } +} |
