import {
|
|
VueTemplateCompiler,
|
|
VueTemplateCompilerOptions,
|
|
ErrorWithRange
|
|
} from './types'
|
|
|
|
import assetUrlsModule, {
|
|
AssetURLOptions
|
|
} 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
|
|
preprocessLang?: string
|
|
preprocessOptions?: any
|
|
transpileOptions?: any
|
|
isProduction?: boolean
|
|
isFunctional?: boolean
|
|
optimizeSSR?: boolean
|
|
prettify?: boolean
|
|
}
|
|
|
|
export interface TemplateCompileResult {
|
|
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 {
|
|
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,
|
|
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()
|
|
: assetUrlsModule(transformAssetUrls),
|
|
srcsetModule()
|
|
]
|
|
finalCompilerOptions = Object.assign({}, compilerOptions, {
|
|
modules: [...builtInModules, ...(compilerOptions.modules || [])]
|
|
})
|
|
}
|
|
|
|
const { render, staticRenderFns, tips, errors } = compile(
|
|
source,
|
|
finalCompilerOptions
|
|
)
|
|
|
|
if (errors && errors.length) {
|
|
return {
|
|
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) {
|
|
tips.push(
|
|
`Failed to prettify component ${options.filename} template source after compilation.`
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
code,
|
|
source,
|
|
tips,
|
|
errors
|
|
}
|
|
}
|
|
}
|