前面咱们已经介绍了,什么是 Headless UI 无头组件库了,以及如何去使用它,我相信同学们看完了之后能够已经在实际项目中运用自如了;
但是我的目的是带领大家能实现一个属于自己的,一个属于公司的、甚至属于公司和个人 KPI 的产物,那么接下来将会手摸手的一步一步指引大家去实现一个真正意义上的 Headless UI 无头组件库;
让我们一起开始动手吧~
说明:为了更好符合国内的大部分用户群体,所以主要实现一个 vue3 的 Headless UI 无头组件库!
1. 生成目录 & 初始化
# 创建目录mkdir my-project# 进入cd my-project# 初始化pnpm init
2. 创建 pnpm-workspace.yaml 文件
touch pnpm-workspace.yaml
3. 修改 pnpm-workspace.yaml
packages: - packages/* - playground - docs
4. 新增 packages/vue 目录
mkdir packagesmkdir packages/vuecd packages/vuepnpm init
5. 新增 typescript 依赖
# 安装到 my-project 根目录下pnpm i typescript @types/node -Dw # 初始化 npx tsc --init
6. 新增 README.md 和 LICENSE 文件
touch README.md LICENSE
pnpm i eslint @antfu/eslint-config -Dw
import antfu from '@antfu/eslint-config'export default antfu({ ignores: ['/dist', '/node_modules', '/packages/**/dist', '/packages/**/node_modules'], rules: { '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/consistent-type-definitions': 'off', 'import/first': 'off', 'import/order': 'off', 'symbol-description': 'off', 'no-console': 'warn', 'max-statements-per-line': ['error', { max: 2 }], 'vue/one-component-per-file': 'off', },})
"lint": "eslint .","lint:fix": "eslint . --fix",
pnpm i simple-git-hooks lint-staged -Dw
script:{ "prepare": "npx simple-git-hooks",},"lint-staged": { "*": "eslint --fix"}
pnpm i @commitlint/config-conventional @commitlint/cli -Dw
"commitlint": { "extends": [ "@commitlint/config-conventional" ]},"simple-git-hooks": { "pre-commit": "pnpm lint-staged", "commit-msg": "pnpm commitlint --edit " },
根据上面的配置,我们在每次修改文件 git 提交后,都会按照以下顺序执行:
1. 共同点:
2. 差异点:
总结:
选择使用 simple-git-hooks 还是 husky 取决于项目的具体需求和团队的偏好。根据项目的规模、复杂度以及对 Git 钩子管理的需求,选择适合的工具可以提高开发流程的效率和代码质量。
# 进入目录cd package/vue# 初始化pnpm init
pnpm install vue vue-tsc -D
pnpm install @vueuse/core -D
1. 安装 typescript 、@tsconfig/node18 和 @vue/tsconfig
pnpm install typescript @tsconfig/node18 @vue/tsconfig -D
2. 添加 tsconfig 配置文件
{ "files": [], "extends": ["./tsconfig.app.json"]}
{ "extends": "@vue/tsconfig/tsconfig.dom.json", "include": [ "env.d.ts", "src/**/*", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue" ], "compilerOptions": { "paths": { "@/*": ["src/*"], }, "target": "esnext", "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "declaration": false, "lib": ["esnext", "dom"], "baseUrl": ".", "skipLibCheck": true, "outDir": "dist" }}
{ "extends": "@tsconfig/node18/tsconfig.json", "include": [ "vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*" ], "compilerOptions": { "composite": true, "module": "ESNext", "types": ["node"] }}
pnpm install vite @vitejs/plugin-vue vite-plugin-dts -D
配置 vite.config.ts
import { resolve } from 'node:path'import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import dts from 'vite-plugin-dts'// https://vitejs.dev/config/export default defineConfig({ plugins: [ vue(), dts({ tsconfigPath: 'tsconfig.build.json', cleanVueFileName: true, exclude: ['src/test/**'], }), ], build: { lib: { name: 'yi-ui', fileName: 'index', entry: resolve(__dirname, 'src/index.ts'), }, },})
创建 src
mkdir src touch src/index.ts
编辑 src/index.ts
const a = '1111'const b = '2222'const fn = () => { console.log('fn')}const add = (a: number, b: number) => { return a + b}export { a, b, fn, add }
添加 package.json 脚本
"build": "vite build",
build 构建
pnpm build
结果就会生成了 dist目录,如下图的文件
因为我们要的是能开箱即用,所以 esm、cjs 的文件格式就要配置好
"exports": { ".": { "types": "./dist/index.d.ts", "require": "./dist/index.umd.js", "import": "./dist/index.mjs" }},"main": "./dist/index.umd.js","module": "./dist/index.mjs","types": "./dist/index.d.ts","typings": "./dist/index.d.ts",
这样我们就可以直接在项目中使用 import 或者 require 来使用库了;
例如:
import { add } from '@yi-ui/vue'add(1, 2)
在下面讲解的 docs文档 和 playground 也会使用到;
到这里一个最最基本的核心库就完成了最基本的搭建,下一步我们考虑的就是测试问题了
因为我们写的是一个上层的工具库,所以单元测试是必不可少的;
毕竟单元测试可以验证库的每个功能模块是否按照预期工作;
在开发阶段就能发现和修复问题,而不是等到系统集成测试甚至上线后才发现,这样可以显著降低修复成本。
等等一系列的原因我们都必须得安排上;
因为咱们主要是开发一个 vue 相关的无头组件库,为了更好的适配,所以咱们的选择就必须是 vitest 了 。
1. 安装
cs package/vuepnpm install vitest -D
2.新建 vitest.config.ts
import { resolve } from 'node:path'import { defineConfig } from 'vitest/config'import Vue from '@vitejs/plugin-vue'const r = (p: string) => resolve(__dirname, p)export default defineConfig({ plugins: [Vue()], resolve: { alias: { '@': r('./src'), }, },})
3. 配置运行脚本:package.json
"script": { ... "test": "vitest", ...}
4. 新建一个 src/index.test.ts 文件
import { describe, expect, test, it } from 'vitest'import { add } from './index'describe('测试', () => { test('函数返回值', () => { expect(add(1, 2)).toBe(3) })})
5.执行 pnpm test
新建 docs 目录
mkdir docscd docs pnpm init
安装 vitepress & tailwindcss
pnpm i vitepress tailwindcss -D
配置 package.json
"scripts": { "docs:dev": "vitepress dev", "docs:build": "vitepress build", "docs:preview": "vitepress preview" },
运行 pnpm docs:dev
到这里其实文档就基本配置好了~
但是还要与我们的核心库做关联,还要有一些基本config配置和样式等等,这些暂时不表,因为涉及的点太多,待后续完善。
一句话概括就是:配置 playground 的主要目的是为了提供给开发者一个交互式的、即时反馈的环境,以更加便捷和直观的方式探索和学习该库的功能。
mkdir playgroundcd playground
初始化 vue3 项目
pnpm create vite
pnpm create vite# 选择 nuxt
配置 playground/vue3 和 nuxt 项目的 package.json
"dependencies": { "@yi-ui/vue": "link:../../packages/vue",},
import { add } from '@yi-ui/vue'add(1, 2)
其实我们在上面配置 package/vue 核心库的时候有添加了一个 build 命令,但是在子项目中 build 不是很方便;
所以为了统一多包管理,需要在根目录的 package.json 下配置一下
"scripts": { "clear": "rimraf packages/**/dist", "build": "pnpm run clear && pnpm -r --filter=./packages/** run build",},
另外,我们还需要额外安装一下 rimraf,来删除打包的产物 dist 等目录;
pnpm install rimraf -Dw
rimraf:一个在 Node.js 环境中常用的 npm 包,用于递归删除文件和文件夹。其名称来源于 "rm -rf" 命令,这是在 Unix/Linux 系统中用于递归删除文件和文件夹的命令。
pnpm build
npm login
注意:
如果发布的 npm 包名为:@xxx/yyy 格式,需要先在 npm 注册名为:xxx 的 organization,否则会出现提交不成功; 发布到 npm group 时默认为 private,所以我们需要手动在每个 packages 子包中的 package.json 中添加如下配置; "publishConfig": { "access": "public" },
因为我们的项目是一个 monorepo 多包项目,所以我们使用普通的办法显然不能了;
那么这时候搭配 pnpm workspace 的工具 changesets 就出现了
1. 安装 changesets
pnpm i @changesets/cli -Dw
2. 初始化 changesets
pnpm changeset init
3. 完成后项目会出现一个.changeset的文件夹
|-- my-project |-- .changeset |-- config.json |-- README.md |-- ...
4. 配置 .changeset/config.json
{ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [ "@yi-ui/playground", "@yi-ui/docs" ]}
5. 配置 package.json 的发布脚本
{ "script": { // 1. 开始交互式填写变更集,每次发布版本的时候执行,生成对应的 md 文件 "changeset": "changeset", // 2. 用来统一提升版本号以及对应的md文档 "vp": "changeset version", // 3. 构建产物后发版 "release": "pnpm build && pnpm release:only", "release:only": "changeset publish" }}
1. 随便修改 package/vue 下 src/index.ts 的代码
2. 按照顺序执行
至此就会在子包中生成你每次发布版本的 md 文档说明了,如下图:
3. 运行 pnpm release 的最终发布
Tips:
playground 和 docs 目录下的包需要在 package.json 中设置 "private": true,否则每次 pnpm release 会把队友的包 `publish 至 npm,从而导致 release 失败。
安装:
pnpm install @yi-ui/vue
使用:
import { add } from '@yi-ui/vue'add(1, 2)
其实上述的流程,只是一个很基本的搭建,还有更详细配置,例如文档、单元测试等等配置其实不止上述这一点,因为要和核心库做深度绑定;
但是为了不显得文章臃肿,咱们只是一笔带过的间接了解下其最基本的配置。
当然我们不可能就这样抛弃了,所以接下来的文章,将会实现一个最基本无头组件,以及如何耦合单元测试、文档等等。
作者:易师傅
链接:https://juejin.cn/post/7352100456334426122