Vue3.3 + TS4 ,自主打造媲美 ElementPlus 的组件库(MKW分享)

2401_84100907 2024-04-02 21:40:46

Vue3.3 + TS4 ,自主打造媲美 ElementPlus 的组件库

//xia仔k>>:百度网盘

1 创立东西类

在完结 cli 的进程中会涉及到组件称号命名方法的转化、履行cmd指令等操作,所以在开端完结创立组件前,先预备一些东西类。

在 cli/src/util/ 目录上一篇文章中已经创立了一个 log-utils.ts 文件,现继续创立下列四个文件:cmd-utils.tsloading-utils.tsname-utils.tstemplate-utils.ts

1.1 name-utils.ts

该文件供给一些称号组件转化的函数,如转化为首字母大写或小写的驼峰命名、转化为中划线分隔的命名等:

/**
 * 将首字母转为大写
 */ export const convertFirstUpper = (str: string): string => { return `${str.substring(0, 1).toUpperCase()}${str.substring(1)}` } /**
 * 将首字母转为小写
 */ export const convertFirstLower = (str: string): string => { return `${str.substring(0, 1).toLowerCase()}${str.substring(1)}` } /**
 * 转为中划线命名
 */ export const convertToLine = (str: string): string => { return convertFirstLower(str).replace(/([A-Z])/g, '-$1').toLowerCase()
} /**
 * 转为驼峰命名(首字母大写)
 */ export const convertToUpCamelName = (str: string): string => { let ret = '' const list = str.split('-')
  list.forEach(item => {
    ret += convertFirstUpper(item)
  }) return convertFirstUpper(ret)
} /**
 * 转为驼峰命名(首字母小写)
 */ export const convertToLowCamelName = (componentName: string): string => { return convertFirstLower(convertToUpCamelName(componentName))
}

1.2 loading-utils.ts

在指令行中创立组件时需求有 loading 效果,该文件运用 ora 库,供给显示 loading 和封闭 loading 的函数:

import ora from 'ora' let spinner: ora.Ora | null = null export const showLoading = (msg: string) => {
  spinner = ora(msg).start()
} export const closeLoading = () => { if (spinner != null) {
    spinner.stop()
  }
}

1.3 cmd-utils.ts

该文件封装 shelljs 库的 execCmd 函数,用于履行 cmd 指令:

import shelljs from 'shelljs' import { closeLoading } from './loading-utils' export const execCmd = (cmd: string) => new Promise((resolve, reject) => {
  shelljs.exec(cmd, (err, stdout, stderr) => { if (err) { closeLoading() reject(new Error(stderr))
    } return resolve('')
  })
})

1.4 template-utils.ts

因为主动创立组件需求生成一些文件,template-utils.ts 为这些文件供给函数获取模板。因为内容较多,这些函数在运用到的时候再讨论。

2 参数实体类

履行 gen 指令时,会提示开发人员输入组件名、中文名、类型,此外还有一些组件名的转化,故可以将新组件的这些信息封装为一个实体类,后边在各种操作中,传递该对象即可,然后避免传递一大堆参数。

2.1 component-info.ts

在 src 目录下创立 domain 目录,并在该目录中创立 component-info.ts ,该类封装了组件的这些基础信息:

import * as path from 'path' import { convertToLine, convertToLowCamelName, convertToUpCamelName } from '../util/name-utils' import { Config } from '../config' export class ComponentInfo { /** 中划线分隔的称号,如:nav-bar */ lineName: string /** 中划线分隔的称号(带组件前缀) 如:yyg-nav-bar */ lineNameWithPrefix: string /** 首字母小写的驼峰名 如:navBar */ lowCamelName: string /** 首字母大写的驼峰名 如:NavBar */ upCamelName: string /** 组件中文名 如:左侧导航 */ zhName: string /** 组件类型 如:tsx */ type: 'tsx' | 'vue' /** packages 目录地点的途径 */ parentPath: string /** 组件地点的途径 */ fullPath: string /** 组件的前缀 如:yyg */ prefix: string /** 组件全名 如:@yyg-demo-ui/xxx */ nameWithLib: string constructor (componentName: string, description: string, componentType: string) { this.prefix = Config.COMPONENT_PREFIX this.lineName = convertToLine(componentName) this.lineNameWithPrefix = `${this.prefix}-${this.lineName}` this.upCamelName = convertToUpCamelName(this.lineName) this.lowCamelName = convertToLowCamelName(this.upCamelName) this.zhName = description this.type = componentType === 'vue' ? 'vue' : 'tsx' this.parentPath = path.resolve(__dirname, '../../../packages') this.fullPath = path.resolve(this.parentPath, this.lineName) this.nameWithLib = `@${Config.COMPONENT_LIB_NAME}/${this.lineName}` }
}

2.2 config.ts

上面的实体中引用了 config.ts 文件,该文件用于设置组件的前缀和组件库的称号。在 src 目录下创立 config.ts

export const Config = { /** 组件名的前缀 */ COMPONENT_PREFIX: 'yyg', /** 组件库称号 */ COMPONENT_LIB_NAME: 'yyg-demo-ui' }

3 创立新组件模块

3.1 概述

上一篇开篇讲了,cli 组件新组件要做四件事:

  1. 创立新组件模块;
  2. 创立款式 scss 文件并导入;
  3. 在组件库进口模块装置新组件模块为依靠,并引进新组件;
  4. 创立组件库文档和 demo。

本文剩下的部分共享第一点,其他三点下一篇文章共享。

在 src 下创立 service 目录,上面四个内容拆分在不同的 service 文件中,并统一由 cli/src/command/create-component.ts 调用,这样层次结构明晰,也便于维护。

首先在 src/service 目录下创立 init-component.ts 文件,该文件用于创立新组件模块,在该文件中要完结如下几件事:

  1. 创立新组件的目录;
  2. 运用 pnpm init 初始化 package.json 文件;
  3. 修正 package.json 的 name 属性;
  4. 装置通用东西包 @yyg-demo-ui/utils 到依靠中;
  5. 创立 src 目录;
  6. 在 src 目录中创立组件本体文件 xxx.tsx 或 xxx.vue;
  7. 在 src 目录中创立 types.ts 文件;
  8. 创立组件进口文件 index.ts。

3.2 init-component.ts

上面的 8 件事需求在 src/service/init-component.ts 中完结,在该文件中导出函数 initComponent 给外部调用:

/**
 * 创立组件目录及文件
 */ export const initComponent = (componentInfo: ComponentInfo) => new Promise((resolve, reject) => { if (fs.existsSync(componentInfo.fullPath)) { return reject(new Error('组件已存在'))
  } // 1. 创立组件根目录 fs.mkdirSync(componentInfo.fullPath) // 2. 初始化 package.json execCmd(`cd ${componentInfo.fullPath} && pnpm init`).then(r => { // 3. 修正 package.json updatePackageJson(componentInfo) // 4. 装置 utils 依靠 execCmd(`cd ${componentInfo.fullPath} && pnpm install @${Config.COMPONENT_LIB_NAME}/utils`) // 5. 创立组件 src 目录 fs.mkdirSync(path.resolve(componentInfo.fullPath, 'src')) // 6. 创立 src/xxx.vue 或s src/xxx.tsx createSrcIndex(componentInfo) // 7. 创立 src/types.ts 文件 createSrcTypes(componentInfo) // 8. 创立 index.ts createIndex(componentInfo) g('component init success') return resolve(componentInfo)
  }).catch(e => { return reject(e)
  })
})

上面的方法逻辑比较明晰,相信大家可以看懂。其间 3、6、7、8抽取为函数。

修正 package.json :读取 package.json 文件,因为默认生成的 name 属性为 xxx-xx 的方式,故只需将该字段串替换为 @yyg-demo-ui/xxx-xx 的方式即可,最后将替换后的结果重新写入到 package.json。代码完结如下:

const updatePackageJson = (componentInfo: ComponentInfo) => { const { lineName, fullPath, nameWithLib } = componentInfo const packageJsonPath = `${fullPath}/package.json` if (fs.existsSync(packageJsonPath)) { let content = fs.readFileSync(packageJsonPath).toString()
    content = content.replace(lineName, nameWithLib)
    fs.writeFileSync(packageJsonPath, content)
  }
}

创立组件的本体 xxx.vue / xxx.tsx:根据组件类型(.tsx 或 .vue)读取对应的模板,然后写入到文件中即可。代码完结:

const createSrcIndex = (componentInfo: ComponentInfo) => { let content = '' if (componentInfo.type === 'vue') {
    content = sfcTemplate(componentInfo.lineNameWithPrefix, componentInfo.lowCamelName)
  } else {
    content = tsxTemplate(componentInfo.lineNameWithPrefix, componentInfo.lowCamelName)
  } const fileFullName = `${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 得到模板,再写入文件。代码完结:

const createSrcTypes = (componentInfo: ComponentInfo) => { const content = typesTemplate(componentInfo.lowCamelName, componentInfo.upCamelName) const fileFullName = `${componentInfo.fullPath}/src/types.ts` fs.writeFileSync(fileFullName, content)
}

创立 index.ts:同上,调用 template-utils.ts 中的函数 indexTemplate 得到模板再写入文件。代码完结:

const createIndex = (componentInfo: ComponentInfo) => {
  fs.writeFileSync(`${componentInfo.fullPath}/index.ts`, indexTemplate(componentInfo))
}

init-component.ts 引进的内容如下:

import { ComponentInfo } from '../domain/component-info' import fs from 'fs' import * as path from '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.3 template-utils.ts

init-component.ts 中引进了 template-utils.ts 的四个函数:indexTemplatesfcTemplatetsxTemplatetypesTemplate,完结如下:

import { ComponentInfo } from '../domain/component-info' /**
 * .vue 文件模板
 */ export const sfcTemplate = (lineNameWithPrefix: string, lowCamelName: string): string => { return `

${lineNameWithPrefix}

` } /** * .tsx 文件模板 */ export const tsxTemplate = (lineNameWithPrefix: string, lowCamelName: string): string => { return `import { defineComponent } from 'vue' import { ${lowCamelName}Props } from './types' const NAME = '${lineNameWithPrefix}' export default defineComponent({ name: NAME, props: ${lowCamelName}Props, setup (props, context) { console.log(props, context) return () => (

${lineNameWithPrefix}

) } }) ` } /** * types.ts 文件模板 */ export const typesTemplate = (lowCamelName: string, upCamelName: string): string => { return `import { ExtractPropTypes } from 'vue' export const ${lowCamelName}Props = { } as const export type ${upCamelName}Props = ExtractPropTypes${lowCamelName}Props> ` } /** * 组件进口 index.ts 文件模板 */ export const indexTemplate = (componentInfo: ComponentInfo): string => { const { upCamelName, lineName, lineNameWithPrefix, type } = componentInfo return `import ${upCamelName} from './src/${type === 'tsx' ? lineName : lineName + '.' + type}' import { App } from 'vue' ${type === 'vue' ? `\n${upCamelName}.name = '${lineNameWithPrefix}'\n` : ''} ${upCamelName}.install = (app: App): void => { // 注册组件 app.component(${upCamelName}.name, ${upCamelName}) } export default ${upCamelName} ` }

这样便完结了新组件模块的创立,下一篇文章将共享其他的三个步骤,并在 createNewComponent 函数中调用。

...全文
430 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

16,203

社区成员

发帖
与我相关
我的任务
社区描述
Qt 是一个跨平台应用程序框架。通过使用 Qt,您可以一次性开发应用程序和用户界面,然后将其部署到多个桌面和嵌入式操作系统,而无需重复编写源代码。
社区管理员
  • Qt
  • 亭台六七座
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧