上文创建了一堆 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. 关于ASP.NET Core WebSocket实现集群的思考

    前言 提到WebSocket相信大家都听说过,它的初衷是为了解决客户端浏览器与服务端进行双向通信,是在单个TCP连接上进行全双工通讯的协议.在没有WebSocket之前只能通过浏览器到服务端的请求应答 ...

  2. JAVA-注解之 TODO、FIXME、XXX

    TODO.FIXME.XXX    //TODO : 表示待实现的功能    //FIXME: 代码存在Bug,不能Run或运行结果不正确,需要修复    //XXX  : 勉强可以工作,但是实现的方 ...

  3. C++初阶(命名空间+缺省参数+const总结+引用总结+内联函数+auto关键字)

    命名空间 概述 在C/C++中,变量.函数和后面要学到的类都是大量存在的,这些变量.函数和类的名称将都存在于全局作用域中,可能会导致很多冲突.使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲 ...

  4. Node.js 的学习(四)分别连接MongoDB与MySQL数据库,实现增删查改功能

    一.Node.js 访问MongoDB 数据库 MongoDB 对许多平台都提供驱动可以访问数据库,如C#.Java.Node.js等. 1.1.安装MongoDB访问驱动 命令如下: 全局安装驱动: ...

  5. 2022-11-03 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...

  6. Java开发环境安装与配置(干货详细教程)

    Java开发环境安装与配置(干货详细教程)  对于文章中出现的任何错误请大家批评指出,会及时做出修改!  安装JDK JDK是 Java 语言的软件开发工具包要学习 链接Java中国官网https:/ ...

  7. C温故补缺(八):结构体与共用体

    结构体与共用体 结构体 是一个可以存储多个不同类型的变量的结构,类似于面对对象中的类(只有成员变量的类). struct tag { member-list member-list member-li ...

  8. JavaScript入门②-函数(1)基础{浅出}

    01.JS函数基础 1.1.函数定义 函数(方法)就是一段定义好的逻辑代码,函数本身也是一个object引用对象.三种函数构造方式: ① 函数申明:function 函数名(参数){代码},申明函数有 ...

  9. OpenVINO计算机视觉模型加速

    OpenVINO计算机视觉模型加速 OpenVINO介绍 计算机视觉部署框架,支持多种边缘硬件平台 Intel开发并开源使用的计算机视觉库 支持多个场景视觉任务场景的快速演示 四个主要模块: 1.开发 ...

  10. 三步快速搭建Typora图床(SM.MS+PicGo)

    三步快速搭建Typora图床(基于SM.MS+PicGo) 前言 在有些同学使用Typora的过程中,会发现Typora不像Word一样,在文档脱离本机后依然正常显示图片,自己的tyopora文件在发 ...