compileTemplate.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import {
  2. VueTemplateCompiler,
  3. VueTemplateCompilerOptions,
  4. ErrorWithRange
  5. } from './types'
  6. import assetUrlsModule, {
  7. AssetURLOptions,
  8. TransformAssetUrlsOptions
  9. } from './templateCompilerModules/assetUrl'
  10. import srcsetModule from './templateCompilerModules/srcset'
  11. const consolidate = require('consolidate')
  12. const transpile = require('vue-template-es2015-compiler')
  13. export interface TemplateCompileOptions {
  14. source: string
  15. filename: string
  16. compiler: VueTemplateCompiler
  17. compilerOptions?: VueTemplateCompilerOptions
  18. transformAssetUrls?: AssetURLOptions | boolean
  19. transformAssetUrlsOptions?: TransformAssetUrlsOptions
  20. preprocessLang?: string
  21. preprocessOptions?: any
  22. transpileOptions?: any
  23. isProduction?: boolean
  24. isFunctional?: boolean
  25. optimizeSSR?: boolean
  26. prettify?: boolean
  27. }
  28. export interface TemplateCompileResult {
  29. ast: Object | undefined
  30. code: string
  31. source: string
  32. tips: (string | ErrorWithRange)[]
  33. errors: (string | ErrorWithRange)[]
  34. }
  35. export function compileTemplate(
  36. options: TemplateCompileOptions
  37. ): TemplateCompileResult {
  38. const { preprocessLang } = options
  39. const preprocessor = preprocessLang && consolidate[preprocessLang]
  40. if (preprocessor) {
  41. return actuallyCompile(
  42. Object.assign({}, options, {
  43. source: preprocess(options, preprocessor)
  44. })
  45. )
  46. } else if (preprocessLang) {
  47. return {
  48. ast: {},
  49. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  50. source: options.source,
  51. tips: [
  52. `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
  53. ],
  54. errors: [
  55. `Component ${options.filename} 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. transformAssetUrlsOptions,
  99. isProduction = process.env.NODE_ENV === 'production',
  100. isFunctional = false,
  101. optimizeSSR = false,
  102. prettify = true
  103. } = options
  104. const compile =
  105. optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
  106. let finalCompilerOptions = compilerOptions
  107. if (transformAssetUrls) {
  108. const builtInModules = [
  109. transformAssetUrls === true
  110. ? assetUrlsModule(undefined, transformAssetUrlsOptions)
  111. : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
  112. srcsetModule(transformAssetUrlsOptions)
  113. ]
  114. finalCompilerOptions = Object.assign({}, compilerOptions, {
  115. modules: [...builtInModules, ...(compilerOptions.modules || [])],
  116. filename: options.filename
  117. })
  118. }
  119. const { ast, render, staticRenderFns, tips, errors } = compile(
  120. source,
  121. finalCompilerOptions
  122. )
  123. if (errors && errors.length) {
  124. return {
  125. ast,
  126. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  127. source,
  128. tips,
  129. errors
  130. }
  131. } else {
  132. const finalTranspileOptions = Object.assign({}, transpileOptions, {
  133. transforms: Object.assign({}, transpileOptions.transforms, {
  134. stripWithFunctional: isFunctional
  135. })
  136. })
  137. const toFunction = (code: string): string => {
  138. return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
  139. }
  140. // transpile code with vue-template-es2015-compiler, which is a forked
  141. // version of Buble that applies ES2015 transforms + stripping `with` usage
  142. let code =
  143. transpile(
  144. `var __render__ = ${toFunction(render)}\n` +
  145. `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
  146. finalTranspileOptions
  147. ) + `\n`
  148. // #23 we use __render__ to avoid `render` not being prefixed by the
  149. // transpiler when stripping with, but revert it back to `render` to
  150. // maintain backwards compat
  151. code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
  152. if (!isProduction) {
  153. // mark with stripped (this enables Vue to use correct runtime proxy
  154. // detection)
  155. code += `render._withStripped = true`
  156. if (prettify) {
  157. try {
  158. code = require('prettier').format(code, {
  159. semi: false,
  160. parser: 'babel'
  161. })
  162. } catch (e) {
  163. if (e.code === 'MODULE_NOT_FOUND') {
  164. tips.push(
  165. 'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
  166. 'Please either turn off `prettify` or manually install `prettier`.'
  167. )
  168. }
  169. tips.push(
  170. `Failed to prettify component ${options.filename} template source after compilation.`
  171. )
  172. }
  173. }
  174. }
  175. return {
  176. ast,
  177. code,
  178. source,
  179. tips,
  180. errors
  181. }
  182. }
  183. }