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 'consolidate' ; import { warnOnce } from './warn' ; import { genCssVarsFromList } from './cssVars' ; export interface TemplateCompiler { compile ( template , options ) : CodegenResult ; parse ( template , options ) : 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 , options , cb ) : void ; } function preprocess ( { source , filename , preprocessOptions } , preprocessor ) { let res = '' ; let err = null ; preprocessor . render ( source , { filename , ... preprocessOptions } , ( _err , _res ) => { if ( _err ) err = _err ; res = _res ; } ) ; if ( err ) throw err ; return res ; } export function compileTemplate ( options ) { 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 ) : require ( 'consolidate' ) [ preprocessLang as keyof typeof consolidate ] : false ; if ( preprocessor ) { try { return doCompileTemplate ( { ... options , source : preprocess ( options , preprocessor ) } ) ; } catch ( e ) { 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 } ) { const errors = [ ] ; const warnings = [ ] ; let nodeTransforms = [ ] ; 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 , ... compilerOptions , nodeTransforms : nodeTransforms . concat ( compilerOptions . nodeTransforms || [ ] ) , filename , sourceMap : true , onError : e => errors . push ( e ) , onWarn : w => warnings . push ( w ) } ) ; 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 , newMap ) { 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 , column : m . originalColumn } , source : origPosInOldMap . source , name : origPosInOldMap . name } ) ; } ) ; const generator = mergedMapGenerator as any ; ( oldMapConsumer as any ) . sources . forEach ( ( sourceFile ) => { 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 , source , inMap ) { 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 ; } } } ) ; }