import { CodegenOptions } from './options' ; import { RootNode , TemplateChildNode , TextNode , CommentNode , ExpressionNode , NodeTypes , JSChildNode , CallExpression , ArrayExpression , ObjectExpression , Position , InterpolationNode , CompoundExpressionNode , SimpleExpressionNode , FunctionExpression , ConditionalExpression , CacheExpression , locStub , SSRCodegenNode , TemplateLiteral , IfStatement , AssignmentExpression , ReturnStatement , VNodeCall , SequenceExpression } from './ast' ; import { SourceMapGenerator , RawSourceMap } from 'source-map' ; import { advancePositionWithMutation , assert , getVNodeBlockHelper , getVNodeHelper , isSimpleIdentifier , toValidAssetId } from './utils' ; import { isString , isArray , isSymbol } from '@vue/shared' ; import { helperNameMap , TO_DISPLAY_STRING , CREATE_VNODE , RESOLVE_COMPONENT , RESOLVE_DIRECTIVE , SET_BLOCK_TRACKING , CREATE_COMMENT , CREATE_TEXT , PUSH_SCOPE_ID , POP_SCOPE_ID , WITH_SCOPE_ID , WITH_DIRECTIVES , CREATE_ELEMENT_VNODE , OPEN_BLOCK , CREATE_STATIC , WITH_CTX , RESOLVE_FILTER } from './runtimeHelpers' ; import { ImportItem } from './transform' ; const PURE_ANNOTATION = `/*#__PURE__*/` ; type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode ; export interface CodegenResult { code : string ; preamble : string ; ast : RootNode ; map ? : RawSourceMap ; } export interface CodegenContext extends Omit < Required < CodegenOptions > , 'bindingMetadata' | 'inline' > { source : string ; code : string ; line : number ; column : number ; offset : number ; indentLevel : number ; pure : boolean ; map ? : SourceMapGenerator ; helper ( key ) : string ; push ( code , node ? ) : void ; indent ( ) : void ; deindent ( withoutNewLine ? ) : void ; newline ( ) : void ; } function createCodegenContext ( ast , { mode = 'function' , prefixIdentifiers = mode === 'module' , sourceMap = false , filename = `template.vue.html` , scopeId = null , optimizeImports = false , runtimeGlobalName = `Vue` , runtimeModuleName = `vue` , ssr = false , isTS = false , inSSR = false } ) { const context = { mode , prefixIdentifiers , sourceMap , filename , scopeId , optimizeImports , runtimeGlobalName , runtimeModuleName , ssr , isTS , inSSR , source : ast . loc . source , code : `` , column : 1 , line : 1 , offset : 0 , indentLevel : 0 , pure : false , map : undefined , helper ( key ) { return `_${ helperNameMap [ key ] }` ; } , push ( code , node ) { context . code += code ; if ( ! __BROWSER__ && context . map ) { if ( node ) { let name ; if ( node . type === NodeTypes . SIMPLE_EXPRESSION && ! node . isStatic ) { const content = node . content . replace ( /^_ctx\./ , '' ) ; if ( content !== node . content && isSimpleIdentifier ( content ) ) { name = content ; } } addMapping ( node . loc . start , name ) ; } advancePositionWithMutation ( context , code ) ; if ( node && node . loc !== locStub ) { addMapping ( node . loc . end ) ; } } } , indent ( ) { newline ( ++ context . indentLevel ) ; } , deindent ( withoutNewLine = false ) { if ( withoutNewLine ) { -- context . indentLevel ; } else { newline ( -- context . indentLevel ) ; } } , newline ( ) { newline ( context . indentLevel ) ; } } ; function newline ( n ) { context . push ( '\n' + `  ` . repeat ( n ) ) ; } function addMapping ( loc , name ? ) { context . map ! . addMapping ( { name , source : context . filename , original : { line : loc . line , column : loc . column - 1 } , generated : { line : context . line , column : context . column - 1 } } ) ; } if ( ! __BROWSER__ && sourceMap ) { context . map = new SourceMapGenerator ( ) ; context . map ! . setSourceContent ( filename , context . source ) ; } return context ; } export function generate ( ast , options = { } ) { const context = createCodegenContext ( ast , options ) ; if ( options . onContextCreated ) options . onContextCreated ( context ) ; const { mode , push , prefixIdentifiers , indent , deindent , newline , scopeId , ssr } = context ; const hasHelpers = ast . helpers . length > 0 ; const useWithBlock = ! prefixIdentifiers && mode !== 'module' ; const genScopeId = ! __BROWSER__ && scopeId != null && mode === 'module' ; const isSetupInlined = ! __BROWSER__ && ! ! options . inline ; const preambleContext = isSetupInlined ? createCodegenContext ( ast , options ) : context ; if ( ! __BROWSER__ && mode === 'module' ) { genModulePreamble ( ast , preambleContext , genScopeId , isSetupInlined ) ; } else { genFunctionPreamble ( ast , preambleContext ) ; } const functionName = ssr ? `ssrRender` : `render` ; const args = ssr ? [ '_ctx' , '_push' , '_parent' , '_attrs' ] : [ '_ctx' , '_cache' ] ; if ( ! __BROWSER__ && options . bindingMetadata && ! options . inline ) { args . push ( '$props' , '$setup' , '$data' , '$options' ) ; } const signature = ! __BROWSER__ && options . isTS ? args . map ( arg => `${ arg }: any` ) . join ( ',' ) : args . join ( ', ' ) ; if ( isSetupInlined ) { push ( `(${ signature }) => {` ) ; } else { push ( `function ${ functionName }(${ signature }) {` ) ; } indent ( ) ; if ( useWithBlock ) { push ( `with (_ctx) {` ) ; indent ( ) ; if ( hasHelpers ) { push ( `const { ${ ast . helpers . map ( s => `${ helperNameMap [ s ] }: _${ helperNameMap [ s ] }` ) . join ( ', ' ) } } = _Vue` ) ; push ( `\n` ) ; newline ( ) ; } } if ( ast . components . length ) { genAssets ( ast . components , 'component' , context ) ; if ( ast . directives . length || ast . temps > 0 ) { newline ( ) ; } } if ( ast . directives . length ) { genAssets ( ast . directives , 'directive' , context ) ; if ( ast . temps > 0 ) { newline ( ) ; } } if ( __COMPAT__ && ast . filters && ast . filters . length ) { newline ( ) ; genAssets ( ast . filters , 'filter' , context ) ; newline ( ) ; } if ( ast . temps > 0 ) { push ( `let ` ) ; for ( let i = 0 ; i < ast . temps ; i ++ ) { push ( `${ i > 0 ? `, ` : `` }_temp${ i }` ) ; } } if ( ast . components . length || ast . directives . length || ast . temps ) { push ( `\n` ) ; newline ( ) ; } if ( ! ssr ) { push ( `return ` ) ; } if ( ast . codegenNode ) { genNode ( ast . codegenNode , context ) ; } else { push ( `null` ) ; } if ( useWithBlock ) { deindent ( ) ; push ( `}` ) ; } deindent ( ) ; push ( `}` ) ; return { ast , code : context . code , preamble : isSetupInlined ? preambleContext . code : `` , map : context . map ? ( context . map as any ) . toJSON ( ) : undefined } ; } function genFunctionPreamble ( ast , context ) { const { ssr , prefixIdentifiers , push , newline , runtimeModuleName , runtimeGlobalName } = context ; const VueBinding = ! __BROWSER__ && ssr ? `require(${ JSON . stringify ( runtimeModuleName ) })` : runtimeGlobalName ; const aliasHelper = ( s ) => `${ helperNameMap [ s ] }: _${ helperNameMap [ s ] }` ; if ( ast . helpers . length > 0 ) { if ( ! __BROWSER__ && prefixIdentifiers ) { push ( `const { ${ ast . helpers . map ( aliasHelper ) . join ( ', ' ) } } = ${ VueBinding }\n` ) ; } else { push ( `const _Vue = ${ VueBinding }\n` ) ; if ( ast . hoists . length ) { const staticHelpers = [ CREATE_VNODE , CREATE_ELEMENT_VNODE , CREATE_COMMENT , CREATE_TEXT , CREATE_STATIC ] . filter ( helper => ast . helpers . includes ( helper ) ) . map ( aliasHelper ) . join ( ', ' ) ; push ( `const { ${ staticHelpers } } = _Vue\n` ) ; } } } if ( ! __BROWSER__ && ast . ssrHelpers && ast . ssrHelpers . length ) { push ( `const { ${ ast . ssrHelpers . map ( aliasHelper ) . join ( ', ' ) } } = require("@vue/server-renderer")\n` ) ; } genHoists ( ast . hoists , context ) ; newline ( ) ; push ( `return ` ) ; } function genModulePreamble ( ast , context , genScopeId , inline ? ) { const { push , newline , optimizeImports , runtimeModuleName } = context ; if ( genScopeId ) { ast . helpers . push ( WITH_SCOPE_ID ) ; if ( ast . hoists . length ) { ast . helpers . push ( PUSH_SCOPE_ID , POP_SCOPE_ID ) ; } } if ( ast . helpers . length ) { if ( optimizeImports ) { push ( `import { ${ ast . helpers . map ( s => helperNameMap [ s ] ) . join ( ', ' ) } } from ${ JSON . stringify ( runtimeModuleName ) }\n` ) ; push ( `\n// Binding optimization for webpack code-split\nconst ${ ast . helpers . map ( s => `_${ helperNameMap [ s ] } = ${ helperNameMap [ s ] }` ) . join ( ', ' ) }\n` ) ; } else { push ( `import { ${ ast . helpers . map ( s => `${ helperNameMap [ s ] } as _${ helperNameMap [ s ] }` ) . join ( ', ' ) } } from ${ JSON . stringify ( runtimeModuleName ) }\n` ) ; } } if ( ast . ssrHelpers && ast . ssrHelpers . length ) { push ( `import { ${ ast . ssrHelpers . map ( s => `${ helperNameMap [ s ] } as _${ helperNameMap [ s ] }` ) . join ( ', ' ) } } from "@vue/server-renderer"\n` ) ; } if ( ast . imports . length ) { genImports ( ast . imports , context ) ; newline ( ) ; } genHoists ( ast . hoists , context ) ; newline ( ) ; if ( ! inline ) { push ( `export ` ) ; } } function genAssets ( assets , type , { helper , push , newline , isTS } ) { const resolver = helper ( __COMPAT__ && type === 'filter' ? RESOLVE_FILTER : type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE ) ; for ( let i = 0 ; i < assets . length ; i ++ ) { let id = assets [ i ] ; const maybeSelfReference = id . endsWith ( '__self' ) ; if ( maybeSelfReference ) { id = id . slice ( 0 , - 6 ) ; } push ( `const ${ toValidAssetId ( id , type ) } = ${ resolver }(${ JSON . stringify ( id ) }${ maybeSelfReference ? `, true` : `` })${ isTS ? `!` : `` }` ) ; if ( i < assets . length - 1 ) { newline ( ) ; } } } function genHoists ( hoists , context ) { if ( ! hoists . length ) { return ; } context . pure = true ; const { push , newline , helper , scopeId , mode } = context ; const genScopeId = ! __BROWSER__ && scopeId != null && mode !== 'function' ; newline ( ) ; if ( genScopeId ) { push ( `${ helper ( PUSH_SCOPE_ID ) }("${ scopeId }")` ) ; newline ( ) ; } hoists . forEach ( ( exp , i ) => { if ( exp ) { push ( `const _hoisted_${ i + 1 } = ` ) ; genNode ( exp , context ) ; newline ( ) ; } } ) ; if ( genScopeId ) { push ( `${ helper ( POP_SCOPE_ID ) }()` ) ; newline ( ) ; } context . pure = false ; } function genImports ( importsOptions , context ) { if ( ! importsOptions . length ) { return ; } importsOptions . forEach ( imports => { context . push ( `import ` ) ; genNode ( imports . exp , context ) ; context . push ( ` from '${ imports . path }'` ) ; context . newline ( ) ; } ) ; } function isText ( n ) { return ( isString ( n ) || n . type === NodeTypes . SIMPLE_EXPRESSION || n . type === NodeTypes . TEXT || n . type === NodeTypes . INTERPOLATION || n . type === NodeTypes . COMPOUND_EXPRESSION ) ; } function genNodeListAsArray ( nodes , context ) { const multilines = nodes . length > 3 || ( ( ! __BROWSER__ || __DEV__ ) && nodes . some ( n => isArray ( n ) || ! isText ( n ) ) ) ; context . push ( `[` ) ; multilines && context . indent ( ) ; genNodeList ( nodes , context , multilines ) ; multilines && context . deindent ( ) ; context . push ( `]` ) ; } function genNodeList ( nodes , context , multilines = false , comma = true ) { const { push , newline } = context ; for ( let i = 0 ; i < nodes . length ; i ++ ) { const node = nodes [ i ] ; if ( isString ( node ) ) { push ( node ) ; } else if ( isArray ( node ) ) { genNodeListAsArray ( node , context ) ; } else { genNode ( node , context ) ; } if ( i < nodes . length - 1 ) { if ( multilines ) { comma && push ( ',' ) ; newline ( ) ; } else { comma && push ( ', ' ) ; } } } } function genNode ( node , context ) { if ( isString ( node ) ) { context . push ( node ) ; return ; } if ( isSymbol ( node ) ) { context . push ( context . helper ( node ) ) ; return ; } switch ( node . type ) { case NodeTypes . ELEMENT : case NodeTypes . IF : case NodeTypes . FOR : __DEV__ && assert ( node . codegenNode != null , `Codegen node is missing for element/if/for node. ` + `Apply appropriate transforms first.` ) ; genNode ( node . codegenNode ! , context ) ; break ; case NodeTypes . TEXT : genText ( node , context ) ; break ; case NodeTypes . SIMPLE_EXPRESSION : genExpression ( node , context ) ; break ; case NodeTypes . INTERPOLATION : genInterpolation ( node , context ) ; break ; case NodeTypes . TEXT_CALL : genNode ( node . codegenNode , context ) ; break ; case NodeTypes . COMPOUND_EXPRESSION : genCompoundExpression ( node , context ) ; break ; case NodeTypes . COMMENT : genComment ( node , context ) ; break ; case NodeTypes . VNODE_CALL : genVNodeCall ( node , context ) ; break ; case NodeTypes . JS_CALL_EXPRESSION : genCallExpression ( node , context ) ; break ; case NodeTypes . JS_OBJECT_EXPRESSION : genObjectExpression ( node , context ) ; break ; case NodeTypes . JS_ARRAY_EXPRESSION : genArrayExpression ( node , context ) ; break ; case NodeTypes . JS_FUNCTION_EXPRESSION : genFunctionExpression ( node , context ) ; break ; case NodeTypes . JS_CONDITIONAL_EXPRESSION : genConditionalExpression ( node , context ) ; break ; case NodeTypes . JS_CACHE_EXPRESSION : genCacheExpression ( node , context ) ; break ; case NodeTypes . JS_BLOCK_STATEMENT : genNodeList ( node . body , context , true , false ) ; break ; case NodeTypes . JS_TEMPLATE_LITERAL : ! __BROWSER__ && genTemplateLiteral ( node , context ) ; break ; case NodeTypes . JS_IF_STATEMENT : ! __BROWSER__ && genIfStatement ( node , context ) ; break ; case NodeTypes . JS_ASSIGNMENT_EXPRESSION : ! __BROWSER__ && genAssignmentExpression ( node , context ) ; break ; case NodeTypes . JS_SEQUENCE_EXPRESSION : ! __BROWSER__ && genSequenceExpression ( node , context ) ; break ; case NodeTypes . JS_RETURN_STATEMENT : ! __BROWSER__ && genReturnStatement ( node , context ) ; break ; case NodeTypes . IF_BRANCH : break ; default : if ( __DEV__ ) { assert ( false , `unhandled codegen node type: ${ ( node as any ) . type }` ) ; const exhaustiveCheck = node ; return exhaustiveCheck ; } } } function genText ( node , context ) { context . push ( JSON . stringify ( node . content ) , node ) ; } function genExpression ( node , context ) { const { content , isStatic } = node ; context . push ( isStatic ? JSON . stringify ( content ) : content , node ) ; } function genInterpolation ( node , context ) { const { push , helper , pure } = context ; if ( pure ) push ( PURE_ANNOTATION ) ; push ( `${ helper ( TO_DISPLAY_STRING ) }(` ) ; genNode ( node . content , context ) ; push ( `)` ) ; } function genCompoundExpression ( node , context ) { for ( let i = 0 ; i < node . children ! . length ; i ++ ) { const child = node . children ! [ i ] ; if ( isString ( child ) ) { context . push ( child ) ; } else { genNode ( child , context ) ; } } } function genExpressionAsPropertyKey ( node , context ) { const { push } = context ; if ( node . type === NodeTypes . COMPOUND_EXPRESSION ) { push ( `[` ) ; genCompoundExpression ( node , context ) ; push ( `]` ) ; } else if ( node . isStatic ) { const text = isSimpleIdentifier ( node . content ) ? node . content : JSON . stringify ( node . content ) ; push ( text , node ) ; } else { push ( `[${ node . content }]` , node ) ; } } function genComment ( node , context ) { const { push , helper , pure } = context ; if ( pure ) { push ( PURE_ANNOTATION ) ; } push ( `${ helper ( CREATE_COMMENT ) }(${ JSON . stringify ( node . content ) })` , node ) ; } function genVNodeCall ( node , context ) { const { push , helper , pure } = context ; const { tag , props , children , patchFlag , dynamicProps , directives , isBlock , disableTracking , isComponent } = node ; if ( directives ) { push ( helper ( WITH_DIRECTIVES ) + `(` ) ; } if ( isBlock ) { push ( `(${ helper ( OPEN_BLOCK ) }(${ disableTracking ? `true` : `` }), ` ) ; } if ( pure ) { push ( PURE_ANNOTATION ) ; } const callHelper = isBlock ? getVNodeBlockHelper ( context . inSSR , isComponent ) : getVNodeHelper ( context . inSSR , isComponent ) ; push ( helper ( callHelper ) + `(` , node ) ; genNodeList ( genNullableArgs ( [ tag , props , children , patchFlag , dynamicProps ] ) , context ) ; push ( `)` ) ; if ( isBlock ) { push ( `)` ) ; } if ( directives ) { push ( `, ` ) ; genNode ( directives , context ) ; push ( `)` ) ; } } function genNullableArgs ( args ) { let i = args . length ; while ( i -- ) { if ( args [ i ] != null ) break ; } return args . slice ( 0 , i + 1 ) . map ( arg => arg || `null` ) ; } function genCallExpression ( node , context ) { const { push , helper , pure } = context ; const callee = isString ( node . callee ) ? node . callee : helper ( node . callee ) ; if ( pure ) { push ( PURE_ANNOTATION ) ; } push ( callee + `(` , node ) ; genNodeList ( node . arguments , context ) ; push ( `)` ) ; } function genObjectExpression ( node , context ) { const { push , indent , deindent , newline } = context ; const { properties } = node ; if ( ! properties . length ) { push ( `{}` , node ) ; return ; } const multilines = properties . length > 1 || ( ( ! __BROWSER__ || __DEV__ ) && properties . some ( p => p . value . type !== NodeTypes . SIMPLE_EXPRESSION ) ) ; push ( multilines ? `{` : `{ ` ) ; multilines && indent ( ) ; for ( let i = 0 ; i < properties . length ; i ++ ) { const { key , value } = properties [ i ] ; genExpressionAsPropertyKey ( key , context ) ; push ( `: ` ) ; genNode ( value , context ) ; if ( i < properties . length - 1 ) { push ( `,` ) ; newline ( ) ; } } multilines && deindent ( ) ; push ( multilines ? `}` : ` }` ) ; } function genArrayExpression ( node , context ) { genNodeListAsArray ( node . elements as CodegenNode [ ] , context ) ; } function genFunctionExpression ( node , context ) { const { push , indent , deindent } = context ; const { params , returns , body , newline , isSlot } = node ; if ( isSlot ) { push ( `_${ helperNameMap [ WITH_CTX ] }(` ) ; } push ( `(` , node ) ; if ( isArray ( params ) ) { genNodeList ( params , context ) ; } else if ( params ) { genNode ( params , context ) ; } push ( `) => ` ) ; if ( newline || body ) { push ( `{` ) ; indent ( ) ; } if ( returns ) { if ( newline ) { push ( `return ` ) ; } if ( isArray ( returns ) ) { genNodeListAsArray ( returns , context ) ; } else { genNode ( returns , context ) ; } } else if ( body ) { genNode ( body , context ) ; } if ( newline || body ) { deindent ( ) ; push ( `}` ) ; } if ( isSlot ) { if ( __COMPAT__ && node . isNonScopedSlot ) { push ( `, undefined, true` ) ; } push ( `)` ) ; } } function genConditionalExpression ( node , context ) { const { test , consequent , alternate , newline : needNewline } = node ; const { push , indent , deindent , newline } = context ; if ( test . type === NodeTypes . SIMPLE_EXPRESSION ) { const needsParens = ! isSimpleIdentifier ( test . content ) ; needsParens && push ( `(` ) ; genExpression ( test , context ) ; needsParens && push ( `)` ) ; } else { push ( `(` ) ; genNode ( test , context ) ; push ( `)` ) ; } needNewline && indent ( ) ; context . indentLevel ++ ; needNewline || push ( ` ` ) ; push ( `? ` ) ; genNode ( consequent , context ) ; context . indentLevel -- ; needNewline && newline ( ) ; needNewline || push ( ` ` ) ; push ( `: ` ) ; const isNested = alternate . type === NodeTypes . JS_CONDITIONAL_EXPRESSION ; if ( ! isNested ) { context . indentLevel ++ ; } genNode ( alternate , context ) ; if ( ! isNested ) { context . indentLevel -- ; } needNewline && deindent ( true ) ; } function genCacheExpression ( node , context ) { const { push , helper , indent , deindent , newline } = context ; push ( `_cache[${ node . index }] || (` ) ; if ( node . isVNode ) { indent ( ) ; push ( `${ helper ( SET_BLOCK_TRACKING ) }(-1),` ) ; newline ( ) ; } push ( `_cache[${ node . index }] = ` ) ; genNode ( node . value , context ) ; if ( node . isVNode ) { push ( `,` ) ; newline ( ) ; push ( `${ helper ( SET_BLOCK_TRACKING ) }(1),` ) ; newline ( ) ; push ( `_cache[${ node . index }]` ) ; deindent ( ) ; } push ( `)` ) ; } function genTemplateLiteral ( node , context ) { const { push , indent , deindent } = context ; push ( '`' ) ; const l = node . elements . length ; const multilines = l > 3 ; for ( let i = 0 ; i < l ; i ++ ) { const e = node . elements [ i ] ; if ( isString ( e ) ) { push ( e . replace ( /(`|\$|\\)/g , '\\$1' ) ) ; } else { push ( '${' ) ; if ( multilines ) indent ( ) ; genNode ( e , context ) ; if ( multilines ) deindent ( ) ; push ( '}' ) ; } } push ( '`' ) ; } function genIfStatement ( node , context ) { const { push , indent , deindent } = context ; const { test , consequent , alternate } = node ; push ( `if (` ) ; genNode ( test , context ) ; push ( `) {` ) ; indent ( ) ; genNode ( consequent , context ) ; deindent ( ) ; push ( `}` ) ; if ( alternate ) { push ( ` else ` ) ; if ( alternate . type === NodeTypes . JS_IF_STATEMENT ) { genIfStatement ( alternate , context ) ; } else { push ( `{` ) ; indent ( ) ; genNode ( alternate , context ) ; deindent ( ) ; push ( `}` ) ; } } } function genAssignmentExpression ( node , context ) { genNode ( node . left , context ) ; context . push ( ` = ` ) ; genNode ( node . right , context ) ; } function genSequenceExpression ( node , context ) { context . push ( `(` ) ; genNodeList ( node . expressions , context ) ; context . push ( `)` ) ; } function genReturnStatement ( { returns } , context ) { context . push ( `return ` ) ; if ( isArray ( returns ) ) { genNodeListAsArray ( returns , context ) ; } else { genNode ( returns , context ) ; } }