UNPKG

4.7 kBJavaScriptView Raw
1import { isPlainObject } from 'shared/util'
2
3const vm = require('vm')
4const path = require('path')
5const resolve = require('resolve')
6const NativeModule = require('module')
7
8function createSandbox (context) {
9 const sandbox = {
10 Buffer,
11 console,
12 process,
13 setTimeout,
14 setInterval,
15 setImmediate,
16 clearTimeout,
17 clearInterval,
18 clearImmediate,
19 __VUE_SSR_CONTEXT__: context
20 }
21 sandbox.global = sandbox
22 return sandbox
23}
24
25function compileModule (files, basedir, runInNewContext) {
26 const compiledScripts = {}
27 const resolvedModules = {}
28
29 function getCompiledScript (filename) {
30 if (compiledScripts[filename]) {
31 return compiledScripts[filename]
32 }
33 const code = files[filename]
34 const wrapper = NativeModule.wrap(code)
35 const script = new vm.Script(wrapper, {
36 filename,
37 displayErrors: true
38 })
39 compiledScripts[filename] = script
40 return script
41 }
42
43 function evaluateModule (filename, sandbox, evaluatedFiles = {}) {
44 if (evaluatedFiles[filename]) {
45 return evaluatedFiles[filename]
46 }
47
48 const script = getCompiledScript(filename)
49 const compiledWrapper = runInNewContext === false
50 ? script.runInThisContext()
51 : script.runInNewContext(sandbox)
52 const m = { exports: {}}
53 const r = file => {
54 file = path.posix.join('.', file)
55 if (files[file]) {
56 return evaluateModule(file, sandbox, evaluatedFiles)
57 } else if (basedir) {
58 return require(
59 resolvedModules[file] ||
60 (resolvedModules[file] = resolve.sync(file, { basedir }))
61 )
62 } else {
63 return require(file)
64 }
65 }
66 compiledWrapper.call(m.exports, m.exports, r, m)
67
68 const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')
69 ? m.exports.default
70 : m.exports
71 evaluatedFiles[filename] = res
72 return res
73 }
74 return evaluateModule
75}
76
77function deepClone (val) {
78 if (isPlainObject(val)) {
79 const res = {}
80 for (const key in val) {
81 res[key] = deepClone(val[key])
82 }
83 return res
84 } else if (Array.isArray(val)) {
85 return val.slice()
86 } else {
87 return val
88 }
89}
90
91export function createBundleRunner (entry, files, basedir, runInNewContext) {
92 const evaluate = compileModule(files, basedir, runInNewContext)
93 if (runInNewContext !== false && runInNewContext !== 'once') {
94 // new context mode: creates a fresh context and re-evaluate the bundle
95 // on each render. Ensures entire application state is fresh for each
96 // render, but incurs extra evaluation cost.
97 return (userContext = {}) => new Promise(resolve => {
98 userContext._registeredComponents = new Set()
99 const res = evaluate(entry, createSandbox(userContext))
100 resolve(typeof res === 'function' ? res(userContext) : res)
101 })
102 } else {
103 // direct mode: instead of re-evaluating the whole bundle on
104 // each render, it simply calls the exported function. This avoids the
105 // module evaluation costs but requires the source code to be structured
106 // slightly differently.
107 let runner // lazy creation so that errors can be caught by user
108 let initialContext
109 return (userContext = {}) => new Promise(resolve => {
110 if (!runner) {
111 const sandbox = runInNewContext === 'once'
112 ? createSandbox()
113 : global
114 // the initial context is only used for collecting possible non-component
115 // styles injected by vue-style-loader.
116 initialContext = sandbox.__VUE_SSR_CONTEXT__ = {}
117 runner = evaluate(entry, sandbox)
118 // On subsequent renders, __VUE_SSR_CONTEXT__ will not be available
119 // to prevent cross-request pollution.
120 delete sandbox.__VUE_SSR_CONTEXT__
121 if (typeof runner !== 'function') {
122 throw new Error(
123 'bundle export should be a function when using ' +
124 '{ runInNewContext: false }.'
125 )
126 }
127 }
128 userContext._registeredComponents = new Set()
129
130 // vue-style-loader styles imported outside of component lifecycle hooks
131 if (initialContext._styles) {
132 userContext._styles = deepClone(initialContext._styles)
133 // #6353 ensure "styles" is exposed even if no styles are injected
134 // in component lifecycles.
135 // the renderStyles fn is exposed by vue-style-loader >= 3.0.3
136 const renderStyles = initialContext._renderStyles
137 if (renderStyles) {
138 Object.defineProperty(userContext, 'styles', {
139 enumerable: true,
140 get () {
141 return renderStyles(userContext._styles)
142 }
143 })
144 }
145 }
146
147 resolve(runner(userContext))
148 })
149 }
150}