| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- import {
- type CodegenResult,
- type CompilerError,
- type CompilerOptions,
- type ElementNode,
- type NodeTransform,
- NodeTypes,
- type ParserOptions,
- type RawSourceMap,
- type RootNode,
- createRoot,
- } from '@vue/compiler-core'
- import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js'
- import {
- type AssetURLOptions,
- type AssetURLTagConfig,
- createAssetUrlTransformWithOptions,
- normalizeOptions,
- transformAssetUrl,
- } from './template/transformAssetUrl'
- import {
- createSrcsetTransformWithOptions,
- transformSrcset,
- } from './template/transformSrcset'
- 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 './style/cssVars'
- export interface TemplateCompiler {
- compile(source: string | RootNode, 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
- ast?: RootNode
- 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),
- ast: undefined, // invalidate AST if template goes through 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,
- ast: inAST,
- ssr = false,
- ssrCssVars,
- isProd = false,
- compiler,
- 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}`
- const defaultCompiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM
- compiler = compiler || defaultCompiler
- if (compiler !== defaultCompiler) {
- // user using custom compiler, this means we cannot reuse the AST from
- // the descriptor as they might be different.
- inAST = undefined
- }
- if (inAST?.transformed) {
- // If input AST has already been transformed, then it cannot be reused.
- // We need to parse a fresh one. Can't just use `source` here since we need
- // the AST location info to be relative to the entire SFC.
- const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, {
- prefixIdentifiers: true,
- ...compilerOptions,
- parseMode: 'sfc',
- onError: e => errors.push(e),
- })
- const template = newAST.children.find(
- node => node.type === NodeTypes.ELEMENT && node.tag === 'template',
- ) as ElementNode
- inAST = createRoot(template.children, inAST.source)
- }
- let { code, ast, preamble, map } = compiler.compile(inAST || source, {
- mode: 'module',
- prefixIdentifiers: true,
- hoistStatic: true,
- cacheHandlers: true,
- ssrCssVars:
- ssr && ssrCssVars && ssrCssVars.length
- ? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
- : '',
- scopeId: scoped ? longId : undefined,
- slotted,
- sourceMap: true,
- ...compilerOptions,
- hmr: !isProd,
- 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 && !inAST) {
- 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(
- inAST?.source || 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
- }
- }
- })
- }
|