上文创建了一堆 utils、component-info,并实现了新组件模块相关目录和文件的创建。本文继续实现后面的内容。

1 组件样式文件并导入

src/service 目录中创建 init-scss.ts 文件,该文件导出 initScss 函数。

由于 .vue 类型的组件的样式就直接写在了 style 中,故首先判断组件类型是否是 tsx,tsx 类型的组件才进行这一步的操作:

  1. scss/components/ 目录下创建组件的 scss 文件 _xxx.module.scss
  2. scss/components/index.scss 中导入 _xxx.module.scss

1.1 init-scss.ts

代码实现如下:

import { ComponentInfo } from '../domain/component-info'
import path from 'path'
import { scssTemplate } from '../util/template-utils'
import fs from 'fs'
import { g } from '../util/log-utils' const updateComponentScssIndex = (scssRootPath: string, lineName: string) => {
const indexScssPath = path.resolve(scssRootPath, 'components/index.scss') const content = fs.readFileSync(indexScssPath).toString()
const newContent = content.substring(0, content.length) + `@use "${lineName}.module";\n`
fs.writeFileSync(indexScssPath, newContent)
} /**
* 创建组件库 scss 文件,并在 scss/components/index.scss 中引入该文件
*/
export const initScss = (componentInfo: ComponentInfo) => new Promise((resolve, reject) => {
// tsx 类型需要创建scss文件
if (componentInfo.type === 'tsx') {
const { parentPath, lineName, lineNameWithPrefix } = componentInfo // scss 根目录(packages/scss)
const scssRootPath = path.resolve(parentPath, 'scss') // 1. 创建组件的 scss 文件
const componentScssPath = path.resolve(scssRootPath, `components/_${lineName}.module.scss`)
fs.writeFileSync(componentScssPath, scssTemplate(lineNameWithPrefix)) // 2. 在组件库 scss 入口文件 (packages/components/index.scss)引入上面创建的文件
updateComponentScssIndex(scssRootPath, lineName) g('component scss init success')
}
resolve(componentInfo)
})

1.2 template-utils.ts

上面的 init-scss.ts 在创建 scss 文件时调用了 template-utils.ts 中的 scssTemplate 函数获取模板。故需要在 util/template-utils.ts 中添加该函数:

/**
* scss 文件模板
*/
export const scssTemplate = (lineNameWithPrefix: string): string => {
return `@import "../tools";
@import "../acss/mp";
@import "../base/var.module"; @include b('${lineNameWithPrefix}') {
}
`
}

2 添加到组件库入口模块

新组件和样式创建完成,接下来便是将新组件模块安装到组件库入口模块的依赖中。在 src/service 目录中创建 update-component-lib.ts 文件,该文件导出函数 updateComponentLib。该函数需要完成两件事:

  1. 在组件库入口模块中安装新组件为依赖;
  2. 更新组件库入口模块的 index.ts 文件,引入新组件。

代码实现如下:

import { ComponentInfo } from '../domain/component-info'
import { execCmd } from '../util/cmd-utils'
import path from 'path'
import { Config } from '../config'
import fs from 'fs'
import { g } from '../util/log-utils' const updateComponentLibIndex = (libPath: string, componentInfo: ComponentInfo) => {
const indexPath = path.join(libPath, 'index.ts')
const content = fs.readFileSync(indexPath).toString() const index1 = content.indexOf('// import component end')
const index2 = content.indexOf('] // components') const result = `${content.substring(0, index1)}` +
`import ${componentInfo.upCamelName} from '${componentInfo.nameWithLib}'\n` +
content.substring(index1, index2 - 1) +
`,\n ${componentInfo.upCamelName}\n` +
content.substring(index2) fs.writeFileSync(indexPath, result)
} /**
* 更新组件库入口
*/
export const updateComponentLib = async (componentInfo: ComponentInfo) => {
// 组件库入口的路径
const libPath = path.resolve(componentInfo.parentPath, Config.COMPONENT_LIB_NAME) // 1. 添加新创建的组件到依赖中
await execCmd(`cd ${libPath} && pnpm install ${componentInfo.nameWithLib}`) // 2. 更新入口 index.ts
updateComponentLibIndex(libPath, componentInfo) g('component library update success')
}

3 组件库文档相关文件

3.1 init-doc.ts

src/service 目录中创建 init-doc.ts 文件,该文件导出函数 initDoc。该函数需要完成三件事:

  1. 创建组件的 MarkDown 文档;
  2. 创建组件 MD 文档中的 demo;
  3. 更新组件库文档菜单。

代码实现如下:

import { ComponentInfo } from '../domain/component-info'
import { g } from '../util/log-utils'
import path from 'path'
import fs from 'fs'
import { demoTemplate, mdTemplate } from '../util/template-utils' /**
* 创建组件文档、demo及更新菜单
*/
export const initDoc = (componentInfo: ComponentInfo) => {
// 组件库文档根路径
const docRootPath = path.resolve(componentInfo.parentPath, '../docs')
const { lineName, lineNameWithPrefix, upCamelName, zhName } = componentInfo // 1. 创建组件的 MD 文档
fs.writeFileSync(path.resolve(docRootPath, `components/${lineName}.md`), mdTemplate(componentInfo)) // 2. 创建组件文档中的 Demo
fs.mkdirSync(path.resolve(docRootPath, `demos/${lineName}`))
fs.writeFileSync(path.resolve(docRootPath, `demos/${lineName}/${lineName}-1.vue`), demoTemplate(lineNameWithPrefix)) // 3. 更新组件库文档菜单
const menuPath = path.resolve(docRootPath, 'components.ts')
const content = fs.readFileSync(menuPath).toString()
const index = content.indexOf('] // end')
const result = content.substring(0, index - 1) +
`,\n { text: '${upCamelName} ${zhName}', link: '/components/${lineName}' }\n` +
content.substring(index)
fs.writeFileSync(menuPath, result) g('component document init success')
}

3.2 template-utils.ts

上面的 init-doc.ts 调用了 mdTemplatedemoTemplate 两个函数,在 template-utils.ts 中添加这两个函数:

export const mdTemplate = (componentInfo: ComponentInfo) => {
return `
# ${componentInfo.upCamelName} ${componentInfo.zhName} ## 基本使用 <preview path="../demos/${componentInfo.lineName}/${componentInfo.lineName}-1.vue" title="基本使用" description=" "></preview> ## 组件 API ### Attributes 属性 | 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ---- | ---- | ---- | ---- | ---- |
| | | | | | ### Methods 方法 | 方法名 | 说明 | 参数 | 返回值 |
| ---- | ---- | ---- | ---- |
| | | | | ### Events 事件 | 事件名 | 说明 | 参数 | 返回值 |
| ---- | ---- | ---- | ---- |
| | | | | ### Slots 插槽 | 插槽名 | 说明 | 参数 |
| ---- | ---- | ---- |
| | | |
`
} export const demoTemplate = (lineNameWithPrefix: string) => {
return `<template>
<${lineNameWithPrefix}></${lineNameWithPrefix}>
</template> <script lang="ts" setup>
</script> <style scoped lang="scss">
</style>
`
}

这两个函数的模板可以自己去定义。

4 create-component.ts

四个步骤都已实现,最后需要在 src/command/create-component.ts 文件中的 createNewComponent 函数中完成上面四个 service 的调用。

4.1 import

导入四个service及使用到的其他函数:

import { ComponentInfo } from '../domain/component-info'
import { closeLoading, showLoading } from '../util/loading-utils'
import { g, r } from '../util/log-utils'
import { initComponent } from '../service/init-component'
import { initScss } from '../service/init-scss'
import { updateComponentLib } from '../service/update-component-lib'
import { initDoc } from '../service/init-doc'

4.2 createNewComponent

该函数首先根据用户输入,构造 ComponentInfo 对象,然后依次调用引入的四个 service,完成组件创建的全部流程:

const createNewComponent = async (componentName: string, description: string, componentType: string) => {
console.log(componentName, description, componentType)
showLoading('Generating, please wait...')
try {
// 1. 构造 ComponentInfo 对象
const componentInfo = new ComponentInfo(componentName, description, componentType)
// 2. 创建组件目录及文件
await initComponent(componentInfo)
// 3. 创建样式
await initScss(componentInfo)
// 4. 更新组件库入口
await updateComponentLib(componentInfo)
// 5. 组件库文档
initDoc(componentInfo) closeLoading()
g(`component [${componentInfo.lineName} ${componentInfo.zhName}] created done!`)
} catch (e: any) {
closeLoading()
r(e.message)
}
}

组件库 cli 就这样完成了。运行 pnpm run gen,依次输入组件名、组件中文名,选择组件类型,便自动完成组件的创建、注册、文档的创建了。优雅哥花了大量篇幅介绍 cli 的开发,不仅仅可以在这里使用,通过本案例的实现,希望大家可以将这种方式移植到其他地方,如从 github 拉取代码模板、自动化 CI/CD 等。

下一篇文章将介绍组件库的打包构建和发布。

感谢阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,了解更多内容工薇号“程序员优雅哥”。

Vue3 企业级优雅实战 - 组件库框架 - 10 实现组件库 cli - 下的更多相关文章

  1. Vue3 企业级优雅实战 - 组件库框架 - 1 搭建 pnpm monorepo

    前两篇文章分享了基于 vite3 vue3 的组件库基础工程 vue3-component-library-archetype 和用于快速创建该工程的工具 yyg-cli,但在中大型的企业级项目中,通 ...

  2. Vue3 企业级优雅实战 - 组件库框架 - 4 组件库的 CSS 架构

    在前一篇文章中分享了搭建组件库的基本开发环境.创建了 foo 组件模块和组件库入口模块,本文分享组件库的样式架构设计. 1 常见的 CSS 架构模式 常见的 CSS 架构模式有很多:OOCSS.ACS ...

  3. Vue3 企业级优雅实战 - 组件库框架 - 3 搭建组件库开发环境

    前文已经初始化了 workspace-root,从本文开始就需要依次搭建组件库.example.文档.cli.本文内容是搭建 组件库的开发环境. 1 packages 目录 前面在项目根目录下创建了 ...

  4. Vue3 企业级优雅实战 - 组件库框架 - 2 初始化 workspace-root

    上文已经搭建了 pnpm + monorepo 的基础环境,本文对 workspace-root 进行初始化配置,包括:通用配置文件.公共依赖.ESLint. 1 通用配置文件 在项目 根目录 下添加 ...

  5. Vue企业级优雅实战04-组件开发01-SVG图标组件

    (后续的文章 公众号会提前一周更新,欢迎关注文末的微信公众号:程序员搞艺术) 预览本文的实现效果: # gitee git clone git@gitee.com:cloudyly/dscloudy- ...

  6. Vue企业级优雅实战-00-开篇

    从2018.1.开始参与了多个企业的中台建设,这些中台的技术选型几乎都是基于 Spring Cloud 微服务架构 + 基于 Vue 全家桶的前端.我前后端架构及开发我几乎各占一半的精力,在企业级前端 ...

  7. Vue企业级优雅实战05-框架开发01-登录界面

    预览本文的实现效果: # gitee git clone git@gitee.com:cloudyly/dscloudy-admin-single.git # github git clone git ...

  8. Vue企业级优雅实战03-准备工作04-全局设置

    本文包括如下几个部分: 初始化环境变量文件 JS 配置文件初始化:如是否开启 Mock 数据.加载本地菜单.URL 请求路径等: 国际化文件初始化:初始化国际化文件的结构: 整合 Element UI ...

  9. Vue企业级优雅实战02-准备工作03-提交 GIT 平台

    代码管理.版本管理是件老大难的事情,尤其多人开发中的代码冲突.突击功能时面临的 hotfix 等.本文只是简单说说如何将一套代码提交到两个 Git 平台(GitHub.GitEE)上.其他的 Git ...

  10. 10个优秀的 Web UI库/框架

    UI(User Interface)即用户界面,也称人机界面.是指用户和某些系统进行交互方法的集合,实现信息的内部形式与人类可以接受形式之间的转换.本文为WUI用户整理了10个优秀的 Web UI 库 ...

随机推荐

  1. 万字详解JVM,让你一文吃透

    摘要:本文将带大家详细地了解关于JVM的一些知识点. 本文分享自华为云社区<[JVM]关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列>,作者: 冰 河 . JDK 是什么? JDK ...

  2. Redis Cluster 数据分片

    介绍 Redis Cluster Redis 集群是 Redis 提供的分布式数据库方案, 集群通过分片(sharding) 来进行数据共享, 并提供复制和故障转移功能. 节点 一个 Redis 集群 ...

  3. Go map 竟然也会发生内存泄露?

    Go 程序运行时,有些场景下会导致进程进入某个"高点",然后就再也下不来了. 比如,多年前曹大写过的一篇文章讲过,在做活动时线上涌入的大流量把 goroutine 数抬升了不少,流 ...

  4. Go语言核心36讲34

    我们在上篇文章中讲到了sync.WaitGroup类型:一个可以帮我们实现一对多goroutine协作流程的同步工具. 在使用WaitGroup值的时候,我们最好用"先统一Add,再并发Do ...

  5. 关于model,modelsmanager,db以及phql之间关系的梳理

    摘要: model在前,db在model后面作为驱动支持.phql是phalcon自创的查询语言,主要特性是在sql语句中用模型名替代表名,其聪明地解析模型类,包括关联关系等,还支持参数绑定.mode ...

  6. 解决Qt5 mouseMoveEvent事件不能直接触发

    问题描述 mouseMoveEvent 需要鼠标点击(左右中),然后在按下的同时移动鼠标才会触发 mouseMoveEvent事件函数. 解决 setMouseTracking(true);

  7. 关于仿照js写python算法

    前言 我们学校的统一认证的登录系统,用了一套不知道哪弄来的 js加密算法 (我已经查到了,应该是出自这里 地址),有一个参数是通过 js 计算得到的,无奈我先想模拟登录就必须解决这个算法,这个说明是d ...

  8. day02 数据类型 & 运算符

    day02 数据类型 基本数据类型 共有四类八种 1)整数类型 byte short int long ​ byte: 字节 bit比特,1bit = 1二进制位 ,byte占8位 [-128,128 ...

  9. SpringBoot3.x中spring.factories功能被移除的解决方案

    背景 笔者所在项目组在搭建一个全新项目的时候选用了SpringBoot3.x,项目中应用了很多SpringBoot2.x时代相关的第三方组件例如baomidou出品的mybatis-plus.dyna ...

  10. org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException

    1.问题概述 将一个 springboot 项目打成 Jar 包后,在本地使用 java -jar 命令启动服务,服务能启动成功,但是会有如下报错信息. 说明: 配置文件为外置配置文件,与 jar 处 ...