204 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   VueTemplateCompiler,
 | |
|   VueTemplateCompilerOptions,
 | |
|   ErrorWithRange
 | |
| } from './types'
 | |
| 
 | |
| import assetUrlsModule, {
 | |
|   AssetURLOptions,
 | |
|   TransformAssetUrlsOptions
 | |
| } from './templateCompilerModules/assetUrl'
 | |
| import srcsetModule from './templateCompilerModules/srcset'
 | |
| 
 | |
| const consolidate = require('consolidate')
 | |
| const transpile = require('vue-template-es2015-compiler')
 | |
| 
 | |
| export interface TemplateCompileOptions {
 | |
|   source: string
 | |
|   filename: string
 | |
|   compiler: VueTemplateCompiler
 | |
|   compilerOptions?: VueTemplateCompilerOptions
 | |
|   transformAssetUrls?: AssetURLOptions | boolean
 | |
|   transformAssetUrlsOptions?: TransformAssetUrlsOptions
 | |
|   preprocessLang?: string
 | |
|   preprocessOptions?: any
 | |
|   transpileOptions?: any
 | |
|   isProduction?: boolean
 | |
|   isFunctional?: boolean
 | |
|   optimizeSSR?: boolean
 | |
|   prettify?: boolean
 | |
| }
 | |
| 
 | |
| export interface TemplateCompileResult {
 | |
|   ast: Object | undefined
 | |
|   code: string
 | |
|   source: string
 | |
|   tips: (string | ErrorWithRange)[]
 | |
|   errors: (string | ErrorWithRange)[]
 | |
| }
 | |
| 
 | |
| export function compileTemplate(
 | |
|   options: TemplateCompileOptions
 | |
| ): TemplateCompileResult {
 | |
|   const { preprocessLang } = options
 | |
|   const preprocessor = preprocessLang && consolidate[preprocessLang]
 | |
|   if (preprocessor) {
 | |
|     return actuallyCompile(
 | |
|       Object.assign({}, options, {
 | |
|         source: preprocess(options, preprocessor)
 | |
|       })
 | |
|     )
 | |
|   } else if (preprocessLang) {
 | |
|     return {
 | |
|       ast: {},
 | |
|       code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
 | |
|       source: options.source,
 | |
|       tips: [
 | |
|         `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
 | |
|       ],
 | |
|       errors: [
 | |
|         `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
 | |
|       ]
 | |
|     }
 | |
|   } else {
 | |
|     return actuallyCompile(options)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function preprocess(
 | |
|   options: TemplateCompileOptions,
 | |
|   preprocessor: any
 | |
| ): string {
 | |
|   const { source, filename, preprocessOptions } = options
 | |
| 
 | |
|   const finalPreprocessOptions = Object.assign(
 | |
|     {
 | |
|       filename
 | |
|     },
 | |
|     preprocessOptions
 | |
|   )
 | |
| 
 | |
|   // Consolidate exposes a callback based API, but the callback is in fact
 | |
|   // called synchronously for most templating engines. In our case, we have to
 | |
|   // expose a synchronous API so that it is usable in Jest transforms (which
 | |
|   // have to be sync because they are applied via Node.js require hooks)
 | |
|   let res: any, err
 | |
|   preprocessor.render(
 | |
|     source,
 | |
|     finalPreprocessOptions,
 | |
|     (_err: Error | null, _res: string) => {
 | |
|       if (_err) err = _err
 | |
|       res = _res
 | |
|     }
 | |
|   )
 | |
| 
 | |
|   if (err) throw err
 | |
|   return res
 | |
| }
 | |
| 
 | |
| function actuallyCompile(
 | |
|   options: TemplateCompileOptions
 | |
| ): TemplateCompileResult {
 | |
|   const {
 | |
|     source,
 | |
|     compiler,
 | |
|     compilerOptions = {},
 | |
|     transpileOptions = {},
 | |
|     transformAssetUrls,
 | |
|     transformAssetUrlsOptions,
 | |
|     isProduction = process.env.NODE_ENV === 'production',
 | |
|     isFunctional = false,
 | |
|     optimizeSSR = false,
 | |
|     prettify = true
 | |
|   } = options
 | |
| 
 | |
|   const compile =
 | |
|     optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
 | |
| 
 | |
|   let finalCompilerOptions = compilerOptions
 | |
|   if (transformAssetUrls) {
 | |
|     const builtInModules = [
 | |
|       transformAssetUrls === true
 | |
|         ? assetUrlsModule(undefined, transformAssetUrlsOptions)
 | |
|         : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
 | |
|       srcsetModule(transformAssetUrlsOptions)
 | |
|     ]
 | |
|     finalCompilerOptions = Object.assign({}, compilerOptions, {
 | |
|       modules: [...builtInModules, ...(compilerOptions.modules || [])],
 | |
|       filename: options.filename
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   const { ast, render, staticRenderFns, tips, errors } = compile(
 | |
|     source,
 | |
|     finalCompilerOptions
 | |
|   )
 | |
| 
 | |
|   if (errors && errors.length) {
 | |
|     return {
 | |
|       ast,
 | |
|       code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
 | |
|       source,
 | |
|       tips,
 | |
|       errors
 | |
|     }
 | |
|   } else {
 | |
|     const finalTranspileOptions = Object.assign({}, transpileOptions, {
 | |
|       transforms: Object.assign({}, transpileOptions.transforms, {
 | |
|         stripWithFunctional: isFunctional
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     const toFunction = (code: string): string => {
 | |
|       return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
 | |
|     }
 | |
| 
 | |
|     // transpile code with vue-template-es2015-compiler, which is a forked
 | |
|     // version of Buble that applies ES2015 transforms + stripping `with` usage
 | |
|     let code =
 | |
|       transpile(
 | |
|         `var __render__ = ${toFunction(render)}\n` +
 | |
|           `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
 | |
|         finalTranspileOptions
 | |
|       ) + `\n`
 | |
| 
 | |
|     // #23 we use __render__ to avoid `render` not being prefixed by the
 | |
|     // transpiler when stripping with, but revert it back to `render` to
 | |
|     // maintain backwards compat
 | |
|     code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
 | |
| 
 | |
|     if (!isProduction) {
 | |
|       // mark with stripped (this enables Vue to use correct runtime proxy
 | |
|       // detection)
 | |
|       code += `render._withStripped = true`
 | |
| 
 | |
|       if (prettify) {
 | |
|         try {
 | |
|           code = require('prettier').format(code, {
 | |
|             semi: false,
 | |
|             parser: 'babel'
 | |
|           })
 | |
|         } catch (e) {
 | |
|           if (e.code === 'MODULE_NOT_FOUND') {
 | |
|             tips.push(
 | |
|               'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
 | |
|                 'Please either turn off `prettify` or manually install `prettier`.'
 | |
|             )
 | |
|           }
 | |
|           tips.push(
 | |
|             `Failed to prettify component ${options.filename} template source after compilation.`
 | |
|           )
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|       ast,
 | |
|       code,
 | |
|       source,
 | |
|       tips,
 | |
|       errors
 | |
|     }
 | |
|   }
 | |
| }
 |