ssrCompile.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import {
  2. type ComponentInternalInstance,
  3. type ComponentOptions,
  4. warn,
  5. } from 'vue'
  6. import { compile } from '@vue/compiler-ssr'
  7. import { NO, extend, generateCodeFrame, isFunction } from '@vue/shared'
  8. import type { CompilerError, CompilerOptions } from '@vue/compiler-core'
  9. import type { PushFn } from '../render'
  10. import * as Vue from 'vue'
  11. import * as helpers from '../internal'
  12. type SSRRenderFunction = (
  13. context: any,
  14. push: PushFn,
  15. parentInstance: ComponentInternalInstance,
  16. ) => void
  17. const compileCache: Record<string, SSRRenderFunction> = Object.create(null)
  18. export function ssrCompile(
  19. template: string,
  20. instance: ComponentInternalInstance,
  21. ): SSRRenderFunction {
  22. // TODO: this branch should now work in ESM builds, enable it in a minor
  23. if (!__CJS__) {
  24. throw new Error(
  25. `On-the-fly template compilation is not supported in the ESM build of ` +
  26. `@vue/server-renderer. All templates must be pre-compiled into ` +
  27. `render functions.`,
  28. )
  29. }
  30. // TODO: This is copied from runtime-core/src/component.ts and should probably be refactored
  31. const Component = instance.type as ComponentOptions
  32. const { isCustomElement, compilerOptions } = instance.appContext.config
  33. const { delimiters, compilerOptions: componentCompilerOptions } = Component
  34. const finalCompilerOptions: CompilerOptions = extend(
  35. extend(
  36. {
  37. isCustomElement,
  38. delimiters,
  39. },
  40. compilerOptions,
  41. ),
  42. componentCompilerOptions,
  43. )
  44. finalCompilerOptions.isCustomElement =
  45. finalCompilerOptions.isCustomElement || NO
  46. finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || NO
  47. const cacheKey = JSON.stringify(
  48. {
  49. template,
  50. compilerOptions: finalCompilerOptions,
  51. },
  52. (key, value) => {
  53. return isFunction(value) ? value.toString() : value
  54. },
  55. )
  56. const cached = compileCache[cacheKey]
  57. if (cached) {
  58. return cached
  59. }
  60. finalCompilerOptions.onError = (err: CompilerError) => {
  61. if (__DEV__) {
  62. const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
  63. const codeFrame =
  64. err.loc &&
  65. generateCodeFrame(
  66. template as string,
  67. err.loc.start.offset,
  68. err.loc.end.offset,
  69. )
  70. warn(codeFrame ? `${message}\n${codeFrame}` : message)
  71. } else {
  72. throw err
  73. }
  74. }
  75. const { code } = compile(template, finalCompilerOptions)
  76. const requireMap = {
  77. vue: Vue,
  78. 'vue/server-renderer': helpers,
  79. }
  80. const fakeRequire = (id: 'vue' | 'vue/server-renderer') => requireMap[id]
  81. return (compileCache[cacheKey] = Function('require', code)(fakeRequire))
  82. }