- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
上文搭建了组件库 cli 的基础架子,实现了创建组件时的用户交互,但遗留了 cli/src/command/create-component.ts 中的 createNewComponent 函数,该函数要实现的功能就是上文开篇提到的 —— 创建一个组件的完整步骤。本文咱们就依次实现那些步骤。(友情提示:本文内容较多,如果你能耐心看完、写完,一定会有提升) 。
在实现 cli 的过程中会涉及到组件名称命名方式的转换、执行cmd命令等操作,所以在开始实现创建组件前,先准备一些工具类.
在 cli/src/util/ 目录上一篇文章中已经创建了一个 log-utils.ts 文件,现继续创建下列四个文件: cmd-utils.ts 、 loading-utils.ts 、 name-utils.ts 、 template-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))
}
在命令行中创建组件时需要有 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()
}
}
该文件封装 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('')
})
})
由于自动创建组件需要生成一些文件, template-utils.ts 为这些文件提供函数获取模板。由于内容较多,这些函数在使用到的时候再讨论.
执行 gen 命令时,会提示开发人员输入组件名、中文名、类型,此外还有一些组件名的转换,故可以将新组件的这些信息封装为一个实体类,后面在各种操作中,传递该对象即可,从而避免传递一大堆参数.
在 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}`
}
}
上面的实体中引用了 config.ts 文件,该文件用于设置组件的前缀和组件库的名称。在 src 目录下创建 config.ts :
export const Config = {
/** 组件名的前缀 */
COMPONENT_PREFIX: 'yyg',
/** 组件库名称 */
COMPONENT_LIB_NAME: 'yyg-demo-ui'
}
上一篇开篇讲了,cli 组件新组件要做四件事:
本文剩下的部分分享第一点,其余三点下一篇文章分享.
在 src 下创建 service 目录,上面四个内容拆分在不同的 service 文件中,并统一由 cli/src/command/create-component.ts 调用,这样层次结构清晰,也便于维护.
首先在 src/service 目录下创建 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'
init-component.ts 中引入了 template-utils.ts 的四个函数: indexTemplate 、 sfcTemplate 、 tsxTemplate 、 typesTemplate ,实现如下:
import { ComponentInfo } from '../domain/component-info'
/**
* .vue 文件模板
*/
export const sfcTemplate = (lineNameWithPrefix: string, lowCamelName: string): string => {
return `<template>
<div>
${lineNameWithPrefix}
</div>
</template>
<script lang="ts" setup name="${lineNameWithPrefix}">
import { defineProps } from 'vue'
import { ${lowCamelName}Props } from './types'
defineProps(${lowCamelName}Props)
</script>
<style scoped lang="scss">
.${lineNameWithPrefix} {
}
</style>
`
}
/**
* .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 () => (
<div class={NAME}>
<div>
${lineNameWithPrefix}
</div>
</div>
)
}
})
`
}
/**
* 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<typeof ${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 函数中调用.
感谢阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,了解更多内容工薇号“程序员优雅哥”.
最后此篇关于Vue3企业级优雅实战-组件库框架-9实现组件库cli-上的文章就讲到这里了,如果你想了解更多关于Vue3企业级优雅实战-组件库框架-9实现组件库cli-上的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
您如何优雅编码同一tableView中的两种类型的单元格? 显然我可以这样: NSDictionary *cellInfo = [_userInformation objectAtIndex:inde
假设我正在编写一个仅包含标题或主要包含标题的库,并且具有以下代码: using my_type = int; namespace detail { inline void foo() { my
我正在使用复选框和输入进行一系列启用/禁用选择,我想知道我是否可以使用循环、变量或复合语句来简单地处理这个js?感觉就像是使用大量代码来实现相对简单的功能。 这是我正在做的事情的一个 fiddle :
我正在尝试为来自维基百科的 API 响应编写一个解析器。它真的很困惑,我已经求助于旧的 RegEx 来清理大部分东西。然而,我坚持这一点。考虑一个字符串: var a ="[[December 1]
我正在通过一个 channel 接收多个消息,并在对其进行迭代之后,我想保留最后一个元素以供进一步使用。我的第一个(可能很糟糕!)方法是声明一些变量,然后在每个循环中分配它。 let last = 0
我正在编写一个 PHP Web 应用程序,它将在不久的将来在生产环境下运行,而不是使用非用户友好的 die() , 我想我会想出一个 Class处理错误消息。 基本上,我的思考过程是这样的: 如果 W
我们有 elb 负载平衡 2 台运行 tomcat 作为应用程序服务器的 WAS 机器。要实现AWS环境下的不间断部署,我们应该, 选择部署目标 WAS。 让它停止来自 elb 的交易。(elb 暂停
何为pythonic? pythonic如果翻译成中文的话就是很python。很+名词结构的用法在中国不少,比如:很娘,很国足,很CCTV等等。 我的理解为,很+名词表达了一种特殊和强调的意味。
认为已经有对此的答案,但找不到。我一直在以某种方式解析方法选项,并想检查并确保它是最优雅/最简洁的方式。 这是我通常做的: def some_method *args options = args
我正在清理我的一个旧项目。它必须做的一件事是——给定笛卡尔网格系统和网格上的两个正方形,找到所有正方形的列表,连接这两个正方形中心的线将通过这些正方形。 这里的特殊情况是所有起点和终点都被限制在正方形
如何使系统 ( SystemB1 ) 访问另一个系统 ( SystemA::sub ) 的字段,就好像它是自己的字段一样? SystemA是一个拥有自己领域的实用系统 Sub* sub . Syste
我有一个包含约 8.000.000 条记录的 MySQL 数据库。因为我需要处理所有这些,所以我使用 BlockingQueue 作为生产者从数据库读取数据并将 1000 条记录放入队列中。 Cons
我正在让我的 HTTP 服务器正常关闭。我从帖子中获取了提示 here ,到目前为止,我的代码是这样设置的: func start() { //...... //START HTTP/
示例脚本只是“wc -m”命令的包装器,简单的符号计数器。我尝试只用“teststrings” slice 元素提供输入。并在输出监听器 goroutine 接收每个字符串的符号数。寻找一种让“wc”
我想干净/优雅地关闭 Internet Explorer。 taskkill 会关闭它,但是当重新打开它时,它会询问您是否要重新打开上一个 session 。 最佳答案 尝试 CloseMainWin
Haskell 的简洁和优雅给我留下了深刻的印象。但我在 .Net 公司工作,所以当我可以使用 F# 时我会使用它——我可能是全国数百个使用它的人中唯一的一个。 ADO.NET 或 F# 是否提供像
如果我们不想在我们的类中实现 init 方法,并且记住 NSObject 中的 init 只返回一个没有初始化的对象实例,如果我们已经得到了,我不明白调用 init 的意义带有分配的实例。我已经尝试过
我们的组织中有许多初级 Delphi 开发人员,作为向他们教授 Delphi 过程的一部分,我希望他们能够看到“干净”、编写良好、设计良好的 Delphi 代码。 我要寻找的一些标准包括: 优秀的类(
我有一个 3D 图像扫描(形状:335x306x306,总元素:31368060),我想用相同大小的 3D bool 掩码来掩盖它以返回相同大小的蒙版图像。 当我简单地用掩码索引数组时: masked
如何使适配器类适本地支持 const 和非 const 底层数据? 具体例子 RigidBody是描述对象物理属性的类。 这是其非常简化的版本(1D):- class RigidBody{ f
我是一名优秀的程序员,十分优秀!