import { declare } from '@babel/helper-plugin-utils' ; import * as t from '@babel/types' ; import { NodePath } from '@babel/traverse' ; import Store from '@remax/build-store' ; import { TEMPLATE_ID , ENTRY , EXPRESSION_BLOCK , SKIP_ELEMENTS } from './constants' ; import { createTemplate } from './templates' ; import { JSXNode , RenderNode , Options } from './types' ; import * as helpers from './helpers' ; function isSlotElement ( node ) { if ( t . isJSXElement ( node ) && node . openingElement . attributes . some ( ( attr ) => attr . name && attr . name . name === 'slot' ) ) { return true ; } return false ; } function canExtract ( node ) { const a = t . isJSXElement ( node ) && ! helpers . hasSpreadAttribute ( node ) && ! helpers . hasChildrenAttribute ( node ) && ! isSlotElement ( node ) ; return a ; } /**
 * 判断 JSX 元素是否处于一段 JSX 片段的顶部
 * 用于标记 template id
 */ function isRootPath ( path , options ) { const { parent , parentPath , node } = path ; if ( t . isJSXElement ( node ) && ( ( node . openingElement . name as t . JSXIdentifier ) . name === 'block' || ! helpers . isHostComponentElement ( node , path , options ) ) ) { return false ; } if ( ! t . isJSXElement ( parent ) && ! t . isJSXFragment ( parent ) ) { return true ; } if ( ! canExtract ( parent ) || ! helpers . isHostComponentElement ( parent , parentPath , options ) ) { return true ; } return false ; } param { t . JSXElement } node returns function isEntry ( node ) { if ( t . isJSXFragment ( node ) ) { return false ; } return ! ! node . openingElement . attributes . find ( attr => t . isJSXAttribute ( attr ) && attr . name . name === ENTRY ) ; } param { t . JSXOpeningElement } element returns function markTemplateID ( filename , element ) { let templateID = helpers . getTemplateID ( element ) ; if ( ! templateID ) { templateID = Store . generateTemplateId ( filename ) ; element . attributes . push ( t . jsxAttribute ( t . jsxIdentifier ( TEMPLATE_ID ) , t . stringLiteral ( templateID ) ) ) ; } return templateID ; } param { JSXNode } node returns { RenderNode [ ] } function sortNodes ( node ) { if ( t . isJSXFragment ( node ) ) { return node . children . reduce < RenderNode [ ] > ( ( prev , current ) => prev . concat ( sortNodes ( current ) ) , [ ] ) ; } if ( t . isJSXElement ( node ) ) { return [ { node , children : node . children . reduce < RenderNode [ ] > ( ( prev , current ) => prev . concat ( sortNodes ( current ) ) , [ ] ) , } , ] ; } return [ { node , children : [ ] , } , ] ; } export returns export default function extractTemplate ( options ) { return declare ( ( ) => { param { ( NodePath < t . JSXElement | t . JSXFragment > ) } path param { * } state returns function renderTemplates ( path , state ) { if ( ! isRootPath ( path , options ) ) { return ; } const nodes = sortNodes ( path . node ) . sort ( ) ; nodes . forEach ( node => { if ( ! canExtract ( node . node ) ) { return ; } const name = ( node . node . openingElement . name as any ) . name ; if ( name === EXPRESSION_BLOCK || SKIP_ELEMENTS . includes ( name ) ) { return ; } const module = state . filename ; const templateID = markTemplateID ( module . replace ( state . cwd , '' ) , node . node . openingElement ) ; if ( Store . extractedTemplates . has ( templateID ) ) { return ; } Store . extractedTemplates . set ( templateID , { id : templateID , template : createTemplate ( node , path , [ 'node' ] , options ) , module , isEntry : isEntry ( path . node ) , } ) ; } ) ; } return { pre ( state ) { for ( const [ globalId , template ] of Store . extractedTemplates ) { if ( template . module === state . opts . filename ) { Store . extractedTemplates . delete ( globalId ) ; } } Store . resetTemplateId ( ) ; } , visitor : { JSXElement : renderTemplates , JSXFragment : renderTemplates , } , } ; } ) ; }