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))
|
})
|
}
|
}
|