diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts new file mode 100644 index 00000000..ebc431cb --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts @@ -0,0 +1,55 @@ +import { compileTemplate } from '../src/compileTemplate' +import { compile } from '@vue/compiler-dom' +import { parse, SFCTemplateBlock } from '../src/parse' + +const compiler = { compile } + +test('should work', () => { + const source = `

{{ render }}

` + + const result = compileTemplate({ filename: 'example.vue', source, compiler }) + + expect(result.errors.length).toBe(0) + expect(result.source).toBe(source) + // should expose render fn + expect(result.code).toMatch(`export default function render()`) +}) + +test('preprocess pug', () => { + const template = parse( + ` + +`, + { filename: 'example.vue', needMap: true } + ).template as SFCTemplateBlock + + const result = compileTemplate({ + filename: 'example.vue', + source: template.content, + preprocessLang: template.lang, + compiler + }) + + expect(result.errors.length).toBe(0) +}) + +test('warn missing preprocessor', () => { + const template = parse(`\n`, { + filename: 'example.vue', + needMap: true + }).template as SFCTemplateBlock + + const result = compileTemplate({ + filename: 'example.vue', + compiler, + source: template.content, + preprocessLang: template.lang + }) + + expect(result.errors.length).toBe(1) +}) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 013fc7e2..0e129b81 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -37,6 +37,7 @@ "source-map": "^0.7.3" }, "devDependencies": { - "@types/lru-cache": "^5.1.0" + "@types/lru-cache": "^5.1.0", + "pug": "^2.0.4" } } diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts index b970d915..52bb6d32 100644 --- a/packages/compiler-sfc/src/compileTemplate.ts +++ b/packages/compiler-sfc/src/compileTemplate.ts @@ -1,3 +1,101 @@ -export function compileTemplate() { - // TODO +import { + CompilerOptions, + CodegenResult, + CompilerError +} from '@vue/compiler-core' +import { RawSourceMap } from 'source-map' +import { transformAssetUrl } from './templateTransformAssetUrl' +import { transformSrcset } from './templateTransformSrcset' + +const consolidate = require('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 +} + +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] + 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({ + source, + compiler, + compilerOptions = {}, + filename +}: TemplateCompileOptions): TemplateCompileResults { + const errors: CompilerError[] = [] + const { code, map } = compiler.compile(source, { + ...compilerOptions, + filename, + mode: 'module', + sourceMap: true, + nodeTransforms: [transformAssetUrl, transformSrcset], + onError: e => errors.push(e) + }) + return { code, source, errors, tips: [], map } }