import { relative } from 'path' ; import { types as t , BabelFile , ConfigAPI , PluginObj } from '@babel/core' ; import { declare } from '@babel/helper-plugin-utils' ; import { assignVarExpr , callExpr , declareVar , generateUids , importNamespace , variableExpr } from './node' ; import { compileExclude } from './exclude' ; const TARGET_MODULE_NAME = '@kirei/element' ; const HMR_MODULE_PATH = '@kirei/hmr-api' ; const DEFINE_ELEMENT_FN = 'defineComponent' ; interface PluginOptions { opts ? : { target ? : string ; harmony ? : boolean ; include ? : string | string [ ] ; exclude ? : string | string [ ] ; extension ? : string | string [ ] ; guard ? : boolean ; } ; file ? : BabelFile ; } param fn returns export default declare ( ( api ) => { api . assertVersion ( 7 ) ; let definitions = [ ] ; let exportNodes = [ ] ; let filepath ; let skip = false ; let exclude ; if ( process . env . NODE_ENV === 'production' || process . env . NODE_ENV === 'test' ) { return { visitor : { } } ; } return { name : 'kirei' , visitor : { Program : { enter ( _ , { file , opts } ) { const { cwd , filenameRelative , filename } = file . opts ; definitions = [ ] ; exportNodes = [ ] ; filepath = filenameRelative ?? relative ( cwd , filename ) ; if ( opts . guard !== false ) { if ( ! exclude ) { exclude = compileExclude ( opts ) ; } skip = exclude ( filepath ) ; } } , exit ( path , { opts } ) { if ( skip || ! definitions . length ) { return ; } const meta = opts . harmony ? 'import.meta' : 'module' ; const hmrAPI = path . scope . generateUid ( 'hmr' ) ; const hmrID = path . scope . generateUid ( 'hmrId' ) ; path . node . body . push ( importNamespace ( hmrAPI , HMR_MODULE_PATH ) , declareVar ( 'const' , hmrID , t . stringLiteral ( filepath ) ) , ... definitions . map ( ( { oid , expr } ) => declareVar ( 'let' , oid , expr ) ) , t . ifStatement ( variableExpr ( `${ meta }.hot` ) , t . blockStatement ( [ t . expressionStatement ( callExpr ( `${ meta }.hot.accept` , [ ] ) ) , ... definitions . map ( ( { oid , eid } ) => { path . scope . generateUid ( ) ; return assignVarExpr ( '=' , eid , callExpr ( `${ hmrAPI }.createOrUpdate` , [ t . identifier ( hmrID ) , t . identifier ( oid ) , ] ) ) ; } ) , ] ) , t . blockStatement ( [ ... definitions . map ( ( { oid , eid } ) => { return assignVarExpr ( '=' , eid , callExpr ( `${ hmrAPI }.create` , [ t . identifier ( hmrID ) , t . identifier ( oid ) , ] ) ) ; } ) , ] ) ) , ... exportNodes ) ; } , } , ImportDeclaration ( path , { opts } ) { const target = opts ?. target ?? TARGET_MODULE_NAME ; if ( skip || path . node . source . value !== target ) { return ; } const { specifiers } = path . node ; for ( let idx = 0 ; idx < specifiers . length ; idx ++ ) { const s = specifiers [ idx ] ; if ( t . isImportSpecifier ( s ) && t . isIdentifier ( s . imported ) && s . imported . name === DEFINE_ELEMENT_FN ) { specifiers . splice ( idx , 1 ) ; } } } , CallExpression ( path ) { if ( skip || ! t . isIdentifier ( path . node . callee , { name : DEFINE_ELEMENT_FN } ) ) { return ; } const arg = path . node . arguments [ 0 ] as t . Expression ; if ( t . isExportDefaultDeclaration ( path . parent ) ) { const [ eid , oid ] = generateUids ( path ) ; definitions . push ( { eid , oid , expr : arg } ) ; path . replaceWith ( t . identifier ( eid ) ) ; path . parentPath . replaceWith ( declareVar ( 'let' , eid ) ) ; exportNodes . push ( path . parent ) ; return ; } else if ( t . isVariableDeclarator ( path . parent ) ) { let parent = path . findParent ( n => t . isExportDeclaration ( n ) ) ; if ( parent ) { const [ eid , oid ] = generateUids ( path ) ; definitions . push ( { eid , oid , expr : arg } ) ; path . replaceWith ( t . identifier ( eid ) ) ; exportNodes . push ( parent . node as t . ExportDeclaration ) ; parent . replaceWith ( declareVar ( 'let' , eid ) ) ; return ; } parent = path . findParent ( n => t . isVariableDeclaration ( n ) ) ; if ( parent ) { const [ eid , oid ] = generateUids ( path ) ; definitions . push ( { eid , oid , expr : arg } ) ; parent . insertBefore ( declareVar ( 'let' , eid ) ) ; path . replaceWith ( t . identifier ( eid ) ) ; return ; } } path . remove ( ) ; } , } , } as PluginObj < PluginOptions > ; } ) ;