You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
5.0 KiB

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