compileTemplate.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. ast: Object | undefined
  28. code: string
  29. source: string
  30. tips: (string | ErrorWithRange)[]
  31. errors: (string | ErrorWithRange)[]
  32. }
  33. export function compileTemplate(
  34. options: TemplateCompileOptions
  35. ): TemplateCompileResult {
  36. const { preprocessLang } = options
  37. const preprocessor = preprocessLang && consolidate[preprocessLang]
  38. if (preprocessor) {
  39. return actuallyCompile(
  40. Object.assign({}, options, {
  41. source: preprocess(options, preprocessor)
  42. })
  43. )
  44. } else if (preprocessLang) {
  45. return {
  46. ast: {},
  47. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  48. source: options.source,
  49. tips: [
  50. `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
  51. ],
  52. errors: [
  53. `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
  54. ]
  55. }
  56. } else {
  57. return actuallyCompile(options)
  58. }
  59. }
  60. function preprocess(
  61. options: TemplateCompileOptions,
  62. preprocessor: any
  63. ): string {
  64. const { source, filename, preprocessOptions } = options
  65. const finalPreprocessOptions = Object.assign(
  66. {
  67. filename
  68. },
  69. preprocessOptions
  70. )
  71. // Consolidate exposes a callback based API, but the callback is in fact
  72. // called synchronously for most templating engines. In our case, we have to
  73. // expose a synchronous API so that it is usable in Jest transforms (which
  74. // have to be sync because they are applied via Node.js require hooks)
  75. let res: any, err
  76. preprocessor.render(
  77. source,
  78. finalPreprocessOptions,
  79. (_err: Error | null, _res: string) => {
  80. if (_err) err = _err
  81. res = _res
  82. }
  83. )
  84. if (err) throw err
  85. return res
  86. }
  87. function actuallyCompile(
  88. options: TemplateCompileOptions
  89. ): TemplateCompileResult {
  90. const {
  91. source,
  92. compiler,
  93. compilerOptions = {},
  94. transpileOptions = {},
  95. transformAssetUrls,
  96. isProduction = process.env.NODE_ENV === 'production',
  97. isFunctional = false,
  98. optimizeSSR = false,
  99. prettify = true
  100. } = options
  101. const compile =
  102. optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
  103. let finalCompilerOptions = compilerOptions
  104. if (transformAssetUrls) {
  105. const builtInModules = [
  106. transformAssetUrls === true
  107. ? assetUrlsModule()
  108. : assetUrlsModule(transformAssetUrls),
  109. srcsetModule()
  110. ]
  111. finalCompilerOptions = Object.assign({}, compilerOptions, {
  112. modules: [...builtInModules, ...(compilerOptions.modules || [])],
  113. filename: options.filename
  114. })
  115. }
  116. const { ast, render, staticRenderFns, tips, errors } = compile(
  117. source,
  118. finalCompilerOptions
  119. )
  120. if (errors && errors.length) {
  121. return {
  122. ast,
  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. try {
  155. code = require('prettier').format(code, {
  156. semi: false,
  157. parser: 'babel'
  158. })
  159. } catch (e) {
  160. if (e.code === 'MODULE_NOT_FOUND') {
  161. tips.push(
  162. 'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
  163. 'Please either turn off `prettify` or manually install `prettier`.'
  164. )
  165. }
  166. tips.push(
  167. `Failed to prettify component ${options.filename} template source after compilation.`
  168. )
  169. }
  170. }
  171. }
  172. return {
  173. ast,
  174. code,
  175. source,
  176. tips,
  177. errors
  178. }
  179. }
  180. }