当前位置: 首页 > Web前端 > HTML

Vue3企业级优雅实战-组件库框架-9实现组件库cli-

时间:2023-03-28 18:41:23 HTML

上面搭建了组件库cli的基础架子,实现了创建组件时的用户交互,但是留下了cli/src/command/create-createNewComponentcomponent.ts中的函数,这个函数要实现的功能就是上面开头提到的——创建一个组件的完整步骤。在本文中,我们将依次实施这些步骤。(友情提示:本文内容很多,如果能耐心阅读和编写,一定会有所提高)1、创建工具类的过程会涉及组件名称的转换和cmd命令的执行实现cli的过程。所以在开始创建组件之前,先准备一些工具类。上一篇已经在cli/src/util/目录下创建了一个log-utils.ts文件,现在继续创建以下四个文件:cmd-utils.ts、loading-utils.ts、name-utils。ts,template-utils.ts1.1name-utils.ts这个文件提供了一些名字组成部分的转换功能,比如转换成大小写驼峰名字,转换成破折号分隔的名字等:/***将首字母转换为大写*/exportconstconvertFirstUpper=(str:string):string=>{return`${str.substring(0,1).toUpperCase()}${str.substring(1)}`}/***将第一个字母转换为小写*/exportconstconvertFirstLower=(str:string):string=>{return`${str.substring(0,1).toLowerCase()}${str.substring(1)}`}/***转换为破折号名称*/exportconstconvertToLine=(str:string):string=>{returnconvertFirstLower(str).replace(/([A-Z])/g,'-$1').toLowerCase()}/***转换为驼峰式大小写(首字母大写)*/exportconstconvertToUpCamelName=(str:string):string=>{letret=''constlist=str.split('-')list.forEach(item=>{ret+=convertFirstUpper(item)})returnconvertFirstUpper(ret)}/***转换为驼峰式大小写(首字母小写)*/exportconstconvertToLowCamelName=(componentName:string):string=>{返回convertFirstLower(convertToUpCamelName(componentName))}1.2loading-utils.ts在命令行创建组件时需要有加载效果。此文件使用ora库提供显示加载和关闭加载的函数:importorafrom'ora'letspinner:ora.Ora|null=nullexportconstshowLoading=(msg:string)=>{spinner=ora(msg).start()}exportconstcloseLoading=()=>{if(spinner!=null){spinner.stop()}}1.3cmd-utils.ts该文件封装了shelljs库的execCmd函数,用于执行cmd命令:=>newPromise((resolve,reject)=>{shelljs.exec(cmd,(err,stdout,stderr)=>{if(err){closeLoading()reject(newError(stderr))}returnresolve('')})})1.4template-utils.ts由于自动创建组件需要生成一些文件,template-utils.ts提供了这些文件获取模板的函数。由于内容较多,这些功能等到用到的时候再说。2参数实体类在执行gen命令的时候,会提示开发者输入组件名称,中文名称,类型,以及一些组件名称的转换,这样就可以将新建组件的信息封装到一个实体类中,即后面各种操作用到,只传对象即可,避免传递很多参数。2.1component-info.ts在src目录下创建domain目录,并在该目录下创建component-info.ts。该类封装了组件的基本信息:import*aspathfrom'path'import{convertToLine,convertToLowCamelName,convertToUpCamelName}from'../util/name-utils'import{Config}from'../config'exportclassComponentInfo{/**名称以破折号分隔,例如:nav-bar*/lineName:string/**名称以破折号分隔(带组件前缀)例如:yyg-nav-bar*/lineNameWithPrefix:string/**CamelCase首字母小写的名称如:navBar*/lowCamelName:string/**首字母大写的驼峰命名如:NavBar*/upCamelName:string/**组件中文名称如:leftnavigation*/zhName:string/**组件类型如:tsx*/type:'tsx'|'vue'/**packages目录所在的路径*/parentPath:string/**组件所在的路径*/fullPath:string/**组件的前缀如:yyg*/prefix:string/**组件全名如:@yyg-demo-ui/xxx*/nameWithLib:stringconstructor(componentName:string,description:string,componentType:string){this.prefix=Config.COMPONENT_PREFIXthis.lineName=convertToLine(componentName)this.lineNameWithPrefix=`${this.prefix}-${this.lineName}`this.upCamelName=convertToUpCamelName(this.lineName)this.lowCamelName=convertToLowCamelName(this.upCamelName)this.zhName=descriptionthis.type=componentType==='vue'?'vue':'tsx'this.parentPath=路径。resolve(__dirname,'../../../packages')this.fullPath=path.resolve(this.parentPath,this.lineName)this.nameWithLib=`@${Config.COMPONENT_LIB_NAME}/${this.lineName}`}}2.2config.ts上面的实体指的是config.ts文件,用来设置组件的前缀和组件库的名称。在src目录下创建config.ts:exportconstConfig={/**组件名前缀*/COMPONENT_PREFIX:'yyg',/**组件库名*/COMPONENT_LIB_NAME:'yyg-demo-ui'}3创建一个新建组件模块3.1概述上一篇文章开头提到,新建cli组件需要做四件事:新建组件模块;创建样式scss文件并导入;在组件库入口模块中安装新的组件模块作为依赖,并引入新的组件;创建组件库文档和演示。本文剩余部分分享第一点,剩下的三点将在下一篇分享。在src下创建service目录,以上4个内容拆分成不同的service文件,由cli/src/command/create-component.ts统一调用,层次结构清晰,便于维护。首先在src/service目录下创建init-component.ts文件。该文件用于创建新的组件模块。在这个文件中,需要做以下事情:为新组件创建一个目录;使用pnpminit初始化package.json文件;修改package.json的name属性;安装通用工具包@yyg-demo-ui/utils到依赖;创建src目录;在src目录下创建组件体文件xxx.tsx或xxx.vue;在src目录中创建类型.ts文件;创建组件入口文件index.ts。3.2init-component.ts中的以上8个东西需要在src/service/init-component.ts中实现,函数initComponent导出在这个文件中供外部调用:/***创建组件目录和文件*/exportconstinitComponent=(componentInfo:ComponentInfo)=>newPromise((resolve,reject)=>{if(fs.existsSync(componentInfo.fullPath)){returnreject(newError('Componentalreadyexists'))}//1.创建组件根目录fs.mkdirSync(componentInfo.fullPath)//2.初始化package.jsonexecCmd(`cd${componentInfo.fullPath}&&pnpminit`).then(r=>{//3.修改包.jsonupdatePackageJson(componentInfo)//4.安装utils依赖execCmd(`cd${componentInfo.fullPath}&&pnpminstall@${Config.COMPONENT_LIB_NAME}/utils`)//5.创建组件src目录fs.mkdirSync(path.resolve(componentInfo.fullPath,'src'))//6.创建src/xxx.vue或src/xxx.tsxcreateSrcIndex(componentInfo)//7.创建src/types.ts文件createSrcTypes(componentInfo)//8.创建index.tscreateIndex(componentInfo)g('componentinitsuccess')returnresolve(componentInfo)}).catch(e=>{returnreject(e)})})上面方法的逻辑比较清晰。3、6、7、8提取为函数,相信大家也能理解。修改package.json:读取package.json文件,由于默认生成的name属性是xxx-xx的形式,所以只需将字段字符串替换成@yyg-demo-ui/xxx-xx的形式即可,最后将替换后的结果重写为package.json。代码实现如下:constupdatePackageJson=(componentInfo:ComponentInfo)=>{const{lineName,fullPath,nameWithLib}=componentInfoconstpackageJsonPath=`${fullPath}/package.json`if(fs.existsSync(packageJsonPath)){letcontent=fs.readFileSync(packageJsonPath).toString()content=content.replace(lineName,nameWithLib)fs.writeFileSync(packageJsonPath,content)}}创建组件体xxx.vue/xxx.tsx:根据组件类型(.tsx或.vue)读取对应的模板,然后写入文件。代码实现:constcreateSrcIndex=(componentInfo:ComponentInfo)=>{letcontent=''if(componentInfo.type==='vue'){content=sfcTemplate(componentInfo.lineNameWithPrefix,componentInfo.lowCamelName)}else{content=tsxTemplate(componentInfo.lineNameWithPrefix,componentInfo.lowCamelName)}constfileFullName=`${componentInfo.fullPath}/src/${componentInfo.lineName}.${componentInfo.type}`fs.writeFileSync(fileFullName,content)}在这里导入src二/util/template-utils.ts中的模板生成函数:稍后将提供sfcTemplate和tsxTemplate。创建src/types.ts文件:调用template-utils.ts中的typesTemplate函数获取模板,然后写入文件。代码实现:constcreateSrcTypes=(componentInfo:ComponentInfo)=>{constcontent=typesTemplate(componentInfo.lowCamelName,componentInfo.upCamelName)constfileFullName=`${componentInfo.fullPath}/src/types.ts`fs.writeFileSync(fileFullName,content)}创建index.ts:同上,调用template-utils.ts中的indexTemplate函数获取模板,然后写入文件。代码实现:constcreateIndex=(componentInfo:ComponentInfo)=>{fs.writeFileSync(`${componentInfo.fullPath}/index.ts`,indexTemplate(componentInfo))}init-component.ts引入如下内容:import{ComponentInfo}from'../domain/component-info'importfsfrom'fs'import*aspathfrom'path'import{indexTemplate,sfcTemplate,tsxTemplate,typesTemplate}from'../util/template-utils'import{g}from'../util/log-utils'import{execCmd}from'../util/cmd-utils'import{Config}from'../config'3.3template-utils.tsinit-component.ts介绍四template-utils.ts:indexTemplate,sfcTemplate,tsxTemplate,typesTemplate的功能实现如下:import{ComponentInfo}from'../domain/component-info'/***.vuefiletemplate*/exportconstsfcTemplate=(lineNameWithPrefix:string,lowCamelName:string):string=>{return`.${lineNameWithPrefix}{}/style>`}/***.tsx文件模板*/exportconsttsxTemplate=(lineNameWithPrefix:string,lowCamelName:string):string=>{return`import{defineComponent}from'vue'import{${lowCamelName}Props}from'./types'constNAME='${lineNameWithPrefix}'exportdefaultdefineComponent({name:NAME,props:${lowCamelName}Props,setup(props,context){console.log(props,context)return()=>(

${lineNameWithPrefix}
)}})`}/***types.ts文件模板*/exportconsttypesTemplate=(lowCamelName:string,upCamelName:string):string=>{return`import{ExtractPropTypes}from'vue'exportconst${lowCamelName}Props={}asconstexport类型${upCamelName}Props=ExtractPropTypes`}/***组件入口index.ts文件模板*/exportconstindexTemplate=(componentInfo:ComponentInfo):string=>{const{upCamelName,lineName,lineNameWithPrefix,type}=componentInforeturn`import${upCamelName}from'./src/${type==='tsx'?线路名称:线路名称+'.'+type}'import{App}from'vue'${type==='vue'?`\n${upCamelName}.name='${lineNameWithPrefix}'\n`:''}${upCamelName}.install=(app:App):void=>{//注册组件app.component(${upCamelName}.name,${upCamelName})}exportdefault${upCamelName}`}这样就实现了新建一个组件模块,下一篇会分享剩下的三个步骤,并在createNewComponent函数中调用