aboutsummaryrefslogtreecommitdiff
path: root/node_modules/vue/src/server/bundle-renderer
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/vue/src/server/bundle-renderer')
-rw-r--r--node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js151
-rw-r--r--node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js150
-rw-r--r--node_modules/vue/src/server/bundle-renderer/source-map-support.js45
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
+ }
+}