import { CompilerOptions, CodegenResult, CompilerError, NodeTransform } from '@vue/compiler-core' import { RawSourceMap } from 'source-map' import { transformAssetUrl, AssetURLOptions, createAssetUrlTransformWithOptions } from './templateTransformAssetUrl' import { transformSrcset } from './templateTransformSrcset' import { isObject } from '@vue/shared' import consolidate from 'consolidate' export interface TemplateCompileResults { code: string source: string tips: string[] errors: (string | CompilerError)[] map?: RawSourceMap } export interface TemplateCompiler { compile(template: string, options: CompilerOptions): CodegenResult } export interface TemplateCompileOptions { source: string filename: string compiler: TemplateCompiler compilerOptions?: CompilerOptions preprocessLang?: string preprocessOptions?: any transformAssetUrls?: AssetURLOptions | boolean } function preprocess( { source, filename, preprocessOptions }: TemplateCompileOptions, preprocessor: any ): string { // 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, { filename, ...preprocessOptions }, (_err: Error | null, _res: string) => { if (_err) err = _err res = _res } ) if (err) throw err return res } export function compileTemplate( options: TemplateCompileOptions ): TemplateCompileResults { const { preprocessLang } = options const preprocessor = preprocessLang && consolidate[preprocessLang as keyof typeof consolidate] if (preprocessor) { return doCompileTemplate({ ...options, source: preprocess(options, preprocessor) }) } else if (preprocessLang) { return { code: `export default function render() {}`, 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 doCompileTemplate(options) } } function doCompileTemplate({ filename, source, compiler, compilerOptions = {}, transformAssetUrls }: TemplateCompileOptions): TemplateCompileResults { const errors: CompilerError[] = [] const nodeTransforms: NodeTransform[] = [transformSrcset] if (isObject(transformAssetUrls)) { nodeTransforms.push(createAssetUrlTransformWithOptions(transformAssetUrls)) } else if (transformAssetUrls !== false) { nodeTransforms.push(transformAssetUrl) } const { code, map } = compiler.compile(source, { ...compilerOptions, filename, mode: 'module', // implies prefixIdentifiers: true hoistStatic: true, cacheHandlers: true, sourceMap: true, nodeTransforms, onError: e => errors.push(e) }) return { code, source, errors, tips: [], map } }