compileTemplate.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {
  2. VueTemplateCompiler,
  3. VueTemplateCompilerOptions,
  4. ErrorWithRange
  5. } from './types'
  6. import assetUrlsModule, {
  7. AssetURLOptions
  8. } from './templateCompilerModules/assetUrl'
  9. import srcsetModule from './templateCompilerModules/srcset'
  10. const consolidate = require('consolidate')
  11. const transpile = require('vue-template-es2015-compiler')
  12. export interface TemplateCompileOptions {
  13. source: string
  14. filename: string
  15. compiler: VueTemplateCompiler
  16. compilerOptions?: VueTemplateCompilerOptions
  17. transformAssetUrls?: AssetURLOptions | boolean
  18. preprocessLang?: string
  19. preprocessOptions?: any
  20. transpileOptions?: any
  21. isProduction?: boolean
  22. isFunctional?: boolean
  23. optimizeSSR?: boolean
  24. prettify?: boolean
  25. }
  26. export interface TemplateCompileResult {
  27. code: string
  28. source: string
  29. tips: (string | ErrorWithRange)[]
  30. errors: (string | ErrorWithRange)[]
  31. }
  32. export function compileTemplate(
  33. options: TemplateCompileOptions
  34. ): TemplateCompileResult {
  35. const { preprocessLang } = options
  36. const preprocessor = preprocessLang && consolidate[preprocessLang]
  37. if (preprocessor) {
  38. return actuallyCompile(
  39. Object.assign({}, options, {
  40. source: preprocess(options, preprocessor)
  41. })
  42. )
  43. } else if (preprocessLang) {
  44. return {
  45. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  46. source: options.source,
  47. tips: [
  48. `Component ${
  49. options.filename
  50. } uses lang ${preprocessLang} for template. Please install the language preprocessor.`
  51. ],
  52. errors: [
  53. `Component ${
  54. options.filename
  55. } uses lang ${preprocessLang} for template, however it is not installed.`
  56. ]
  57. }
  58. } else {
  59. return actuallyCompile(options)
  60. }
  61. }
  62. function preprocess(
  63. options: TemplateCompileOptions,
  64. preprocessor: any
  65. ): string {
  66. const { source, filename, preprocessOptions } = options
  67. const finalPreprocessOptions = Object.assign(
  68. {
  69. filename
  70. },
  71. preprocessOptions
  72. )
  73. // Consolidate exposes a callback based API, but the callback is in fact
  74. // called synchronously for most templating engines. In our case, we have to
  75. // expose a synchronous API so that it is usable in Jest transforms (which
  76. // have to be sync because they are applied via Node.js require hooks)
  77. let res: any, err
  78. preprocessor.render(
  79. source,
  80. finalPreprocessOptions,
  81. (_err: Error | null, _res: string) => {
  82. if (_err) err = _err
  83. res = _res
  84. }
  85. )
  86. if (err) throw err
  87. return res
  88. }
  89. function actuallyCompile(
  90. options: TemplateCompileOptions
  91. ): TemplateCompileResult {
  92. const {
  93. source,
  94. compiler,
  95. compilerOptions = {},
  96. transpileOptions = {},
  97. transformAssetUrls,
  98. isProduction = process.env.NODE_ENV === 'production',
  99. isFunctional = false,
  100. optimizeSSR = false,
  101. prettify = true
  102. } = options
  103. const compile =
  104. optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
  105. let finalCompilerOptions = compilerOptions
  106. if (transformAssetUrls) {
  107. const builtInModules = [
  108. transformAssetUrls === true
  109. ? assetUrlsModule()
  110. : assetUrlsModule(transformAssetUrls),
  111. srcsetModule()
  112. ]
  113. finalCompilerOptions = Object.assign({}, compilerOptions, {
  114. modules: [...builtInModules, ...(compilerOptions.modules || [])]
  115. })
  116. }
  117. const { render, staticRenderFns, tips, errors } = compile(
  118. source,
  119. finalCompilerOptions
  120. )
  121. if (errors && errors.length) {
  122. return {
  123. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  124. source,
  125. tips,
  126. errors
  127. }
  128. } else {
  129. const finalTranspileOptions = Object.assign({}, transpileOptions, {
  130. transforms: Object.assign({}, transpileOptions.transforms, {
  131. stripWithFunctional: isFunctional
  132. })
  133. })
  134. const toFunction = (code: string): string => {
  135. return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
  136. }
  137. // transpile code with vue-template-es2015-compiler, which is a forked
  138. // version of Buble that applies ES2015 transforms + stripping `with` usage
  139. let code =
  140. transpile(
  141. `var __render__ = ${toFunction(render)}\n` +
  142. `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
  143. finalTranspileOptions
  144. ) + `\n`
  145. // #23 we use __render__ to avoid `render` not being prefixed by the
  146. // transpiler when stripping with, but revert it back to `render` to
  147. // maintain backwards compat
  148. code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
  149. if (!isProduction) {
  150. // mark with stripped (this enables Vue to use correct runtime proxy
  151. // detection)
  152. code += `render._withStripped = true`
  153. if (prettify) {
  154. code = require('prettier').format(code, {
  155. semi: false,
  156. parser: 'babel'
  157. })
  158. }
  159. }
  160. return {
  161. code,
  162. source,
  163. tips,
  164. errors
  165. }
  166. }
  167. }