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.
203 lines
5.6 KiB
203 lines
5.6 KiB
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 |
|
} |
|
} |
|
}
|
|
|