import * as t from "@babel/types" ; import { ComponentState } from "../plugin" ; import { KEY_ELEMENT , KEY_PROP_UPDATER , ELEMENT_UPDATER_SUFFIX , PROP_VAR_TRANSACTION_VAR , } from "../constants" ; import { createStatementUpdater } from "./createStatementUpdater" ; export interface NodeElementDefinition { type : "node" ; isNative ? : boolean ; tag : string ; identifier : t . Identifier ; children : t . Identifier [ ] ; attributes ? : t . JSXOpeningElement [ "attributes" ] ; } export interface TextElementDefenition { type : "text" ; value : string ; identifier : t . Identifier ; } export interface ExpressionElementDefenition { type : "expr" ; expression : t . Expression | t . JSXEmptyExpression ; identifier : t . Identifier ; } export type ElementDefenition = NodeElementDefinition | TextElementDefenition | ExpressionElementDefenition ; export const transformerMap = { text : transformText , node : transformNode , expr : transformExpression , } ; export function createComponentElement ( elementName , propUpdater ) { return t . objectExpression ( [ t . objectProperty ( t . identifier ( KEY_ELEMENT ) , elementName ) , t . objectProperty ( t . identifier ( KEY_PROP_UPDATER ) , propUpdater ) , ] ) ; } export function transformNode ( definition , state ) { state . moduleDependencies . add ( "createElement" ) ; const result = createCreateElement ( definition , state ) ; if ( definition . children . length > 0 ) { state . moduleDependencies . add ( "append" ) ; result . push ( createAppend ( definition . identifier , definition . children ) ) ; } return result ; } export function transformText ( definition , state ) { state . moduleDependencies . add ( "createText" ) ; const variableDeclarator = t . variableDeclarator ( definition . identifier , createCreateTextNode ( definition . value ) ) ; const elementDeclarator = t . variableDeclaration ( "let" , [ variableDeclarator ] ) ; return elementDeclarator ; } export function transformExpression ( definition , state ) { state . moduleDependencies . add ( "createText" ) ; state . moduleDependencies . add ( "setContent" ) ; const updateFunctionIdentifier = t . identifier ( definition . identifier . name + ELEMENT_UPDATER_SUFFIX ) ; const elementDeclarator = t . variableDeclaration ( "let" , [ t . variableDeclarator ( definition . identifier ) , ] ) ; const expression = definition . expression as t . Expression ; t . traverse ( expression , ( node ) => { if ( t . isArrowFunctionExpression ( node ) && ! t . isBlockStatement ( node . body ) ) { node . body = t . blockStatement ( [ t . returnStatement ( node . body ) ] ) ; } } ) ; const block = t . blockStatement ( [ t . ifStatement ( t . unaryExpression ( "!" , definition . identifier ) , t . expressionStatement ( t . assignmentExpression ( "=" , definition . identifier , createCreateTextNode ( ) ) ) ) , t . expressionStatement ( t . assignmentExpression ( "=" , t . memberExpression ( definition . identifier , t . identifier ( KEY_ELEMENT ) ) , t . callExpression ( t . identifier ( "setContent" ) , [ t . memberExpression ( definition . identifier , t . identifier ( KEY_ELEMENT ) ) , expression , ] ) ) ) , ] ) ; const [ updater , callUpdater ] = createStatementUpdater ( block , updateFunctionIdentifier ) ; return [ elementDeclarator , updater , callUpdater ] ; } function createCreateElement ( definition , state ) { return definition . isNative ? createCreateNativeElement ( definition , state ) : createCreateComponentElement ( definition , state ) ; } function createCreateNativeElement ( definition , state ) { const [ updateAttrStatements , init ] = createSetAttribute ( definition , definition . identifier , state ) ; return [ t . variableDeclaration ( "let" , [ t . variableDeclarator ( definition . identifier , t . callExpression ( t . identifier ( "createElement" ) , [ t . stringLiteral ( definition . tag ) , init ] . filter ( Boolean ) ) ) , ] ) , ... updateAttrStatements , ] ; } function createCreateComponentElement ( definition , state ) { const elInstanceId = t . identifier ( definition . identifier . name + "_instance" ) ; const [ updateAttrStatements , init ] = createSetAttribute ( definition , elInstanceId , state ) ; const callExpression = t . callExpression ( t . identifier ( definition . tag ) , init ? [ init ] : [ t . objectExpression ( [ ] ) ] ) ; const instanceDeclarator = t . variableDeclarator ( elInstanceId , callExpression ) ; const elementDeclarator = t . variableDeclarator ( definition . identifier , t . memberExpression ( elInstanceId , t . identifier ( KEY_ELEMENT ) ) ) ; const elementDeclaration = t . variableDeclaration ( "let" , [ instanceDeclarator , elementDeclarator , ] ) ; return [ elementDeclaration , ... updateAttrStatements ] ; } function createSetAttribute ( definition , instanceId , state ) { if ( ! definition . attributes || definition . attributes . length === 0 ) { return [ [ ] , undefined ] ; } const attributeSet = [ ] ; const objProperties = [ ] ; for ( const attr of definition . attributes ) { if ( ! t . isJSXAttribute ( attr ) ) { continue ; } const propName = attr . name . name ; const propId = t . identifier ( propName ) ; const literalValue = t . isStringLiteral ( attr . value ) && attr . value ; const value = literalValue || ( t . isJSXExpressionContainer ( attr . value ) && ! t . isJSXEmptyExpression ( attr . value . expression ) && attr . value . expression ) || t . booleanLiteral ( true ) ; objProperties . push ( t . objectProperty ( propId , value ) ) ; if ( literalValue ) { continue ; } state . moduleDependencies . add ( "addPropTransaction" ) ; state . needsPropTransaction = true ; attributeSet . push ( t . expressionStatement ( t . callExpression ( t . identifier ( "addPropTransaction" ) , [ t . identifier ( PROP_VAR_TRANSACTION_VAR ) , instanceId , t . stringLiteral ( propId . name ) , value , ] ) ) ) ; } return [ attributeSet , t . objectExpression ( objProperties ) ] ; } function createCreateTextNode ( value ? ) { return t . callExpression ( t . identifier ( "createText" ) , value ? [ t . stringLiteral ( value ) ] : [ ] ) ; } function createAppend ( parent , children ) { return t . expressionStatement ( t . callExpression ( t . identifier ( "append" ) , [ parent , ... children ] ) ) ; }