create-bundle-runner.js 4.0 KB

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