compileTemplate.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { BindingMetadata, TemplateCompiler } from './types'
  2. import assetUrlsModule, {
  3. AssetURLOptions,
  4. TransformAssetUrlsOptions
  5. } from './templateCompilerModules/assetUrl'
  6. import srcsetModule from './templateCompilerModules/srcset'
  7. import consolidate from '@vue/consolidate'
  8. import * as _compiler from 'web/entry-compiler'
  9. import { prefixIdentifiers } from './prefixIdentifiers'
  10. import { CompilerOptions, WarningMessage } from 'types/compiler'
  11. export interface SFCTemplateCompileOptions {
  12. source: string
  13. filename: string
  14. compiler?: TemplateCompiler
  15. compilerOptions?: CompilerOptions
  16. transformAssetUrls?: AssetURLOptions | boolean
  17. transformAssetUrlsOptions?: TransformAssetUrlsOptions
  18. preprocessLang?: string
  19. preprocessOptions?: any
  20. transpileOptions?: any
  21. isProduction?: boolean
  22. isFunctional?: boolean
  23. optimizeSSR?: boolean
  24. prettify?: boolean
  25. isTS?: boolean
  26. bindings?: BindingMetadata
  27. }
  28. export interface SFCTemplateCompileResults {
  29. ast: Object | undefined
  30. code: string
  31. source: string
  32. tips: (string | WarningMessage)[]
  33. errors: (string | WarningMessage)[]
  34. }
  35. export function compileTemplate(
  36. options: SFCTemplateCompileOptions
  37. ): SFCTemplateCompileResults {
  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: SFCTemplateCompileOptions,
  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: SFCTemplateCompileOptions
  91. ): SFCTemplateCompileResults {
  92. const {
  93. source,
  94. compiler = _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. isTS = false,
  104. bindings
  105. } = options
  106. const compile =
  107. optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
  108. let finalCompilerOptions = compilerOptions
  109. if (transformAssetUrls) {
  110. const builtInModules = [
  111. transformAssetUrls === true
  112. ? assetUrlsModule(undefined, transformAssetUrlsOptions)
  113. : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
  114. srcsetModule(transformAssetUrlsOptions)
  115. ]
  116. finalCompilerOptions = Object.assign({}, compilerOptions, {
  117. modules: [...builtInModules, ...(compilerOptions.modules || [])],
  118. filename: options.filename
  119. })
  120. }
  121. finalCompilerOptions.bindings = bindings
  122. const { ast, render, staticRenderFns, tips, errors } = compile(
  123. source,
  124. finalCompilerOptions
  125. )
  126. if (errors && errors.length) {
  127. return {
  128. ast,
  129. code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
  130. source,
  131. tips,
  132. errors
  133. }
  134. } else {
  135. // stripping `with` usage
  136. let code =
  137. `var __render__ = ${prefixIdentifiers(
  138. `function render(${isFunctional ? `_c,_vm` : ``}){${render}\n}`,
  139. isFunctional,
  140. isTS,
  141. transpileOptions,
  142. bindings
  143. )}\n` +
  144. `var __staticRenderFns__ = [${staticRenderFns.map(code =>
  145. prefixIdentifiers(
  146. `function (${isFunctional ? `_c,_vm` : ``}){${code}\n}`,
  147. isFunctional,
  148. isTS,
  149. transpileOptions,
  150. bindings
  151. )
  152. )}]` +
  153. `\n`
  154. // #23 we use __render__ to avoid `render` not being prefixed by the
  155. // transpiler when stripping with, but revert it back to `render` to
  156. // maintain backwards compat
  157. code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
  158. if (!isProduction) {
  159. // mark with stripped (this enables Vue to use correct runtime proxy
  160. // detection)
  161. code += `render._withStripped = true`
  162. if (prettify) {
  163. try {
  164. code = require('prettier').format(code, {
  165. semi: false,
  166. parser: 'babel'
  167. })
  168. } catch (e: any) {
  169. if (e.code === 'MODULE_NOT_FOUND') {
  170. tips.push(
  171. 'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
  172. 'Please either turn off `prettify` or manually install `prettier`.'
  173. )
  174. }
  175. tips.push(
  176. `Failed to prettify component ${options.filename} template source after compilation.`
  177. )
  178. }
  179. }
  180. }
  181. return {
  182. ast,
  183. code,
  184. source,
  185. tips,
  186. errors
  187. }
  188. }
  189. }