写在前面
组件库源码地址:https://github.com/Kylin93CN/kylin-design 求star✨~
文档地址:https://kylin93cn.github.io/kylin-design/
npm地址:https://www.npmjs.com/package/kylin-design
设计与色彩学
设计
关于设计,可以参考antd中的文档:https://ant.design/docs/spec/overview-cn
色彩
既然要开发组件库,首先我们得知道开发的组件长什么样,是什么颜色。一个优秀的组件库肯定离不开她的色彩搭配与设计。
antd官方文档中单独有一个头部导航 来介绍设计,可想而知这块也是组件开发的一个头等大事。
我参考了国内主流的UI组件库:
p.s. 可以打开网站,对应的就是组件库中关于色彩描述的文档
总结下来就是我们要有以下几个色板:
- 基础色板
- 中性色板
- 功能色板
关于基础色板,就是根据一个主题核心的颜色,通过算法得到一组渐进的颜色组。
我们可以通过antd的色板生成工具 帮我们计算得出
可以通过这个网站计算得到:https://mycolor.space/
关于中性色板,就是#fff到#000的一个过程,中间的渐变颜色可以自己定义。
关于功能色板,就是一些功能性组件所使用的的颜色。比如,警告⚠️类型的,一般都是黄色;提示类型的,一般都是蓝色;危险或者错误❌,一般都是红色。
当然,除了颜色之外,还有字体、阴影、布局中,本文不做过多探讨。
P.S.关于色彩的详情,可参考:https://www.yuque.com/kylin93/uayuig/qypb6u
开发
起名
首先,给我们的组件起一个响亮的名字!!
那就叫做:kylin-design
创建项目
通过cra脚手架创建项目:
npx create-react-app kylin-design --template typescript # or yarn create react-app kylin-design --template typescript
配置色彩颜色
step1
本项目使用的scss,涉及一些scss的特性,比如:文件模块化、mixin、变量等。
首先我们在src下创建一个styles文件夹,用于存放样式文件。
创建第一个scss文件:_variables.scss 文件名前添加了一个下划线“_”表示这个只是部分文件,打包时,不会生成对应的css文件。
在这个文件中,我们会定义各种颜色,包含中性色、基本色、功能色以及各种预设样式,比如字体大小、行高、按钮的border-radius等。详情可见:https://raw.githubusercontent.com/Kylin93CN/kylin-design/main/src/styles/_variables.scss
下面代码为文件的部分:
/********* 中性色--start *********/ $gray-1: #fff !default; $gray-2: #f5f5f5 !default; $gray-3: #f0f0f0 !default; $gray-4: #d9d9d9 !default; $gray-5: #8c8c8c !default; $gray-6: #595959 !default; $gray-7: #434343 !default; $gray-8: #262626 !default; $gray-9: #1f1f1f !default; $gray-10: #000 !default; /********* 中性色--end ***********/ /********* 基础色--start *********/ // 使用antd-Dust Red / 薄暮 $red-1: #fff1f0 !default; $red-2: #ffccc7 !default; $red-3: #ffa39e !default; $red-4: #ff7875 !default; $red-5: #ff4d4f !default; $red-6: #f5222d !default; $red-7: #cf1322 !default; $red-8: #a8071a !default; $red-9: #820014 !default; $red-10: #5c0011 !default; $transparent: transparent !default;
关于组件的基础色,那肯定跟我们的组件名称有关系,麒麟属火,我就选择了antd提供的一套:
然后组件的设计风格也是根据这个主题仿照的:
p.s. 可在antd官网底部选择主题修改查看
step2
引入normalize.css文件,帮我们抹平里浏览器之间的差异。
github地址:https://github.com/necolas/normalize.css/
它的作用:
- 与reset.css不同,normalize会保留有用的默认值。
- 标准化各种元素的样式。
- 通过细微的修改提高可用性。
对主流浏览器也是支持的。
这里我会基于normalize.css,修改为_normalize.scss,并将一些变量加载进去。
step3
创建_mixin.scss, 用于复用
step4
创建index.scss(注意这个没有下划线),将上述3个文件导入。
开发组件--Button
在src下创建components的文件夹,然后再创建一个Button的文件夹,接下来创建下面几个文件:
- _button.scss——文件用于button组件的样式;
- button.test.tsx——用于组件测试
- button.tsx——是组件的主体
- index.tsx——是组件的导出文件,对于Menu这种组件,我们会统一把MenuItem等挂在Menu上,方便导出。
对于组件的开发,我们首先要提前定义好或者设计好组件到底有哪些属性。对于经常使用antd-button的小伙伴们来说,常用的属性就是type、size、disabled等。
那么我们以实现最小MVP来说,就先定义几个常用的:
export type ButtonSize = "lg" | "sm"; export type ButtonType = "primary" | "default" | "danger" | "link" | "ghost"; interface BaseButtonProps { /** 设置 Button 的classname */ className?: string; /** 设置 Button 的尺寸 */ size?: ButtonSize; /** 设置 Button 是否禁用 */ disabled?: boolean; /**设 置 Button 类型 */ btnType?: ButtonType; /** 设置 Button 文案 */ children?: React.ReactNode; /** 设置 Button 类型为link时的跳转地址 */ href?: string; }
按钮的实现我们用到了button跟a标签(用于link类型),除了我们制定的属性,还需要标签的原生属性,比如button的onClick,因此我们需要把a标签的原生属性、button的原生属性还有我们定义的按钮属性联合起来。使用的ts类型定义代码如下:
type NativeButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLElement>; type NativeAnchorProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLElement>; export type ButtonProps = Partial<NativeButtonProps & NativeAnchorProps>;
p.s. 涉及到ts的 & 用法与Partial的用法
按钮的不同尺寸、类型,其实就是css不同,我们借助classnames库,帮我们处理这个逻辑:
// 按钮默认有btn的类 const classes = classnames("btn", className, { [`btn-${btnType}`]: btnType, // 根据类型追加类 [`btn-${size}`]: size, // 同上 disabled: btnType === "link" && disabled, // link使用css处理,btn使用原生属性处理 });
渲染组件就通过type判断下,如果是link就使用a标签,其他就使用button
if (btnType === "link" && href) { return ( <a className={classes} href={href} {...restProps}> {children} </a> ); } return ( <button className={classes} disabled={disabled} {...restProps}> {children} </button> );
接下来我们开始去编写样式文件,默认的btn,不同类型的btn-primary、btn-danger、btn-lg等
这些根据我们之前定义的变量去使用就好了。
.btn-danger { // 使用mixin,在styles/_mixin.scss中定义的 @include btn-style($gray-1, $red-5,$red-5); } .btn-default { @include btn-style($gray-1, $gray-4,$gray-10,); } .btn-ghost { @include btn-style($transparent, $gray-1,$gray-1,); }
测试
react推荐的testing-library,cra(貌似是3.3+)也默认集成的库。yyds!
关于测试,会用到2个库:
提供了一些match,比如toBeEmptyDOMElement、toBeInTheDocument、toHaveClass
帮助我们将jsx转化为真实dom,并提供一些辅助的事件模拟方法等。
比如我们可以写一个简单的测试用例:
import React from "react"; import { render, fireEvent } from "@testing-library/react"; import Button from "./button"; // 组件的默认属性 const defaultBtnProps: ButtonProps = { btnType: "default", className: "test-cls", onClick: jest.fn(), }; // 测试默认button it("test default button", () => { // 通过render将button渲染为真实dom const btnDom = render( <Button {...defaultProps}> Default Button </Button> ); // 通过getByText方法获取btn的实例 const ele = btnDom.getByText("Default Button"); // button是否存在--@testing-library/jest-dom提供的match expect(ele).toBeInTheDocument(); // dom属性是否是BUTTON expect(ele.tagName).toEqual("BUTTON"); // --@testing-library/jest-dom提供的match expect(ele).toHaveClass("btn btn-default test-cls"); // 模拟点击 fireEvent.click(ele); // --@testing-library/jest-dom提供的match expect(defaultProps.onClick).toHaveBeenCalled(); });
文档生成
组件库肯定是写给人用的,但是使用者肯定不能通过看源码来查看btn的type属性有哪些、设置大小的枚举值有哪些吧,这里我们使用的storybook 来生成文档库。
直接运行npx sb init
During its install process, Storybook will look into your project's dependencies and provide you with the best configuration available.
我们尽管安装,sb会根据我们项目的依赖帮我们安装最合适的配置,比如我们项目是ts的,它就会生成ts的文件。
新版本的sb(6.x)新增了control的功能,简单的配置,就可以帮我们在页面上动态修改组件的属性,实时渲染。可到 此link 体验尝鲜。
sb默认集成的addon(插件)已满足我目前的需求,如有其它需求,也可到插件市场逛逛,地址是:
https://storybook.js.org/addons/
在.storybook/main.js
中,追加了一行代码,用于把css文件导入。
"stories": [ "../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)", + "../src/styles/index.scss" ],
sb默认会在src下创建一个stories文件夹,里面会有一些示例,我们可以运行npm run storybook
启动本地服务,查看生成的文档库。
Introduction.stories.mdx
mdx就是markdown + jsx,这里我当做了介绍页,把文件中没用的都删掉了。
sb会根据文件名规则约束,以文件名格式为:XXX.stories.XXX的文件当做一个story,删除无用的文件,然后我们保留Button.stories.tsx
这个文件,将button组件改为我们写的组件,然后根据示例补充我们的属性即可。
btnType: { name: "btnType", description: "button type", type: "radio", options: ["primary", "default", "danger", "link", "ghost"], table: { type: { summary: "string" }, defaultValue: { summary: "default" }, }, },
生成的结果如下,我们可以在单选框中选择,实时修改组件的类型等属性
点击docs,可以查看属性的文档详情,比如默认值、类型、详情介绍等
组件发布
项目配置
1. 配置scripts
- "build": "npm run clear && npm run build-css && npm run build-ts",
- "build-ts": "tsc -p tsconfig.build.json",
- "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
- "clear": "rimraf ./dist",
- "prepublishOnly": "npm run build",
- "test-ci": "cross-env CI=true react-scripts test",
clear命令,我们安装rimraf库,帮我们抹平操作系统的命令差异,该命令执行删除./dist文件夹下面的内容;
build-css命令,通过node-sass打包css文件;
build-ts命令,这里我们新增了一个tsconfig.build.json
文件用于build时使用,用于打包组件库js文件;
build命令,首先清空./dist下的内容,然后打包css、js
test-ci命令执行测试用例
prepublishOnly命令,发布前执行打包操作
2.添加husky
用于我们提交commit时,强执行测试用例,保证组件库的稳定性。
目前husky是7.x版本,会使用到set-script,因此需要保证你的Npm也是7.x版本。
npm -v
可查看当前npm版本,升级的话执行:
npm i npm -g
详细安装可参考官方文档:https://github.com/typicode/husky#usage
然后我们在pre-commit
中添加需要执行的脚本为:
npm run test-ci && npm run lint
3. 完善readme.md
必不可少
4. 修改依赖
目前dependencies我只保留了一个classnames,其他的依赖库都塞到了devDependencies中,
一些是因为都是开发依赖,还有一些比如react、react-dom,这些的话,如果组件使用的话,会使用安装组件库项目中的react,而不是组件库的react。
特此,加了peerDependencies ,如果使用的项目没有这个库或者版本不对,会有warning提示。
"peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" },
5.修改package.json
- 修改版本号
- 添加一些字段,比如author、desc、keywords、license、homepage等
- 修改private为false
- 添加入口信息字段
- main
- modules
- types
- 添加files字段
- 内容为['dist']
发布NPM
首先需要登录npm,执行命令 npm whoami
,如果没有,则需要登录,没有账号的自行去npm官网注册,有账号的执行npm login
进行登录。
p.s. 登录时不能把源改为淘宝的。
万事俱备,执行:
npm publish
大功告成!!!
部署
项目部署CI/CD & 文档库部署
本项目使用的github actions
创建文件:.github/workflows/ci.yml
name: KYLIN-DESIGN CI # 名称 on: push: branches: - main # main分支push时触发 jobs: build-and-deploy: runs-on: ubuntu-latest # 运行环境为乌班图 steps: # actions/checkout@v2 是一个官方的actions,用于下载项目代码 - name: Checkout uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. with: persist-credentials: false - name: Install and Build # step的名称 run: | # 需要执行的脚本命令 npm install # 安装依赖 npm run test-ci # 运行测试 npm run-script build # 打包组件库的静态资源 npm run build-storybook # 打包文档库资源 - name: Deploy # 这是一个别人写的actions,用于静态资源的部署 uses: JamesIves/github-pages-deploy-action@releases/v3 with: ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} # 环境变量 BRANCH: gh-pages # 部署的分支 FOLDER: storybook-static # sb的dist目录
如何生成ACCESS_TOKEN
打开https://github.com/settings/tokens,点击生成新的token
使用ACCESS_TOKEN
在项目的Settings中,点Scerets
配置好之后,我们下次提交的时候就会发出ci/cd,然后执行命令部署sb。