compileTemplate.ts 5.7 KB

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