| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- import {
- CompilerOptions,
- CodegenResult,
- CompilerError,
- NodeTransform,
- ParserOptions,
- RootNode
- } from '@vue/compiler-core'
- import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
- import {
- transformAssetUrl,
- AssetURLOptions,
- createAssetUrlTransformWithOptions,
- AssetURLTagConfig,
- normalizeOptions
- } from './templateTransformAssetUrl'
- import {
- transformSrcset,
- createSrcsetTransformWithOptions
- } from './templateTransformSrcset'
- import { generateCodeFrame, isObject } from '@vue/shared'
- import * as CompilerDOM from '@vue/compiler-dom'
- import * as CompilerSSR from '@vue/compiler-ssr'
- import consolidate from '@vue/consolidate'
- import { warnOnce } from './warn'
- import { genCssVarsFromList } from './cssVars'
- export interface TemplateCompiler {
- compile(template: string, options: CompilerOptions): CodegenResult
- parse(template: string, options: ParserOptions): RootNode
- }
- export interface SFCTemplateCompileResults {
- code: string
- ast?: RootNode
- preamble?: string
- source: string
- tips: string[]
- errors: (string | CompilerError)[]
- map?: RawSourceMap
- }
- export interface SFCTemplateCompileOptions {
- source: string
- filename: string
- id: string
- scoped?: boolean
- slotted?: boolean
- isProd?: boolean
- ssr?: boolean
- ssrCssVars?: string[]
- inMap?: RawSourceMap
- compiler?: TemplateCompiler
- compilerOptions?: CompilerOptions
- preprocessLang?: string
- preprocessOptions?: any
- /**
- * In some cases, compiler-sfc may not be inside the project root (e.g. when
- * linked or globally installed). In such cases a custom `require` can be
- * passed to correctly resolve the preprocessors.
- */
- preprocessCustomRequire?: (id: string) => any
- /**
- * Configure what tags/attributes to transform into asset url imports,
- * or disable the transform altogether with `false`.
- */
- transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
- }
- interface PreProcessor {
- render(
- source: string,
- options: any,
- cb: (err: Error | null, res: string) => void
- ): void
- }
- function preprocess(
- { source, filename, preprocessOptions }: SFCTemplateCompileOptions,
- preprocessor: PreProcessor
- ): 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: string = ''
- let err: Error | null = null
- preprocessor.render(
- source,
- { filename, ...preprocessOptions },
- (_err, _res) => {
- if (_err) err = _err
- res = _res
- }
- )
- if (err) throw err
- return res
- }
- export function compileTemplate(
- options: SFCTemplateCompileOptions
- ): SFCTemplateCompileResults {
- const { preprocessLang, preprocessCustomRequire } = options
- if (
- (__ESM_BROWSER__ || __GLOBAL__) &&
- preprocessLang &&
- !preprocessCustomRequire
- ) {
- throw new Error(
- `[@vue/compiler-sfc] Template preprocessing in the browser build must ` +
- `provide the \`preprocessCustomRequire\` option to return the in-browser ` +
- `version of the preprocessor in the shape of { render(): string }.`
- )
- }
- const preprocessor = preprocessLang
- ? preprocessCustomRequire
- ? preprocessCustomRequire(preprocessLang)
- : __ESM_BROWSER__
- ? undefined
- : consolidate[preprocessLang as keyof typeof consolidate]
- : false
- if (preprocessor) {
- try {
- return doCompileTemplate({
- ...options,
- source: preprocess(options, preprocessor)
- })
- } catch (e: any) {
- return {
- code: `export default function render() {}`,
- source: options.source,
- tips: [],
- errors: [e]
- }
- }
- } 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,
- id,
- scoped,
- slotted,
- inMap,
- source,
- ssr = false,
- ssrCssVars,
- isProd = false,
- compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
- compilerOptions = {},
- transformAssetUrls
- }: SFCTemplateCompileOptions): SFCTemplateCompileResults {
- const errors: CompilerError[] = []
- const warnings: CompilerError[] = []
- let nodeTransforms: NodeTransform[] = []
- if (isObject(transformAssetUrls)) {
- const assetOptions = normalizeOptions(transformAssetUrls)
- nodeTransforms = [
- createAssetUrlTransformWithOptions(assetOptions),
- createSrcsetTransformWithOptions(assetOptions)
- ]
- } else if (transformAssetUrls !== false) {
- nodeTransforms = [transformAssetUrl, transformSrcset]
- }
- if (ssr && !ssrCssVars) {
- warnOnce(
- `compileTemplate is called with \`ssr: true\` but no ` +
- `corresponding \`cssVars\` option.\`.`
- )
- }
- if (!id) {
- warnOnce(`compileTemplate now requires the \`id\` option.\`.`)
- id = ''
- }
- const shortId = id.replace(/^data-v-/, '')
- const longId = `data-v-${shortId}`
- let { code, ast, preamble, map } = compiler.compile(source, {
- mode: 'module',
- prefixIdentifiers: true,
- hoistStatic: true,
- cacheHandlers: true,
- ssrCssVars:
- ssr && ssrCssVars && ssrCssVars.length
- ? genCssVarsFromList(ssrCssVars, shortId, isProd)
- : '',
- scopeId: scoped ? longId : undefined,
- slotted,
- sourceMap: true,
- ...compilerOptions,
- nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
- filename,
- onError: e => errors.push(e),
- onWarn: w => warnings.push(w)
- })
- // inMap should be the map produced by ./parse.ts which is a simple line-only
- // mapping. If it is present, we need to adjust the final map and errors to
- // reflect the original line numbers.
- if (inMap) {
- if (map) {
- map = mapLines(inMap, map)
- }
- if (errors.length) {
- patchErrors(errors, source, inMap)
- }
- }
- const tips = warnings.map(w => {
- let msg = w.message
- if (w.loc) {
- msg += `\n${generateCodeFrame(
- source,
- w.loc.start.offset,
- w.loc.end.offset
- )}`
- }
- return msg
- })
- return { code, ast, preamble, source, errors, tips, map }
- }
- function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
- if (!oldMap) return newMap
- if (!newMap) return oldMap
- const oldMapConsumer = new SourceMapConsumer(oldMap)
- const newMapConsumer = new SourceMapConsumer(newMap)
- const mergedMapGenerator = new SourceMapGenerator()
- newMapConsumer.eachMapping(m => {
- if (m.originalLine == null) {
- return
- }
- const origPosInOldMap = oldMapConsumer.originalPositionFor({
- line: m.originalLine,
- column: m.originalColumn
- })
- if (origPosInOldMap.source == null) {
- return
- }
- mergedMapGenerator.addMapping({
- generated: {
- line: m.generatedLine,
- column: m.generatedColumn
- },
- original: {
- line: origPosInOldMap.line, // map line
- // use current column, since the oldMap produced by @vue/compiler-sfc
- // does not
- column: m.originalColumn
- },
- source: origPosInOldMap.source,
- name: origPosInOldMap.name
- })
- })
- // source-map's type definition is incomplete
- const generator = mergedMapGenerator as any
- ;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
- generator._sources.add(sourceFile)
- const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
- if (sourceContent != null) {
- mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
- }
- })
- generator._sourceRoot = oldMap.sourceRoot
- generator._file = oldMap.file
- return generator.toJSON()
- }
- function patchErrors(
- errors: CompilerError[],
- source: string,
- inMap: RawSourceMap
- ) {
- const originalSource = inMap.sourcesContent![0]
- const offset = originalSource.indexOf(source)
- const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1
- errors.forEach(err => {
- if (err.loc) {
- err.loc.start.line += lineOffset
- err.loc.start.offset += offset
- if (err.loc.end !== err.loc.start) {
- err.loc.end.line += lineOffset
- err.loc.end.offset += offset
- }
- }
- })
- }
|