【前端工程化】带你使用dumi2一步一步搭建自己的React组件库和函数库

709 阅读10分钟

一. dumi简介

直接引用官网的介绍

1.1 什么是 dumi

dumi,中文发音嘟米,是一款为组件开发场景而生的静态站点框架,与 father 一起为开发者提供一站式的组件开发体验,father 负责组件源码构建,而 dumi 负责组件开发及组件文档生成

简单来说dumi是一个开发组件库和函数库的框架,可以方便的开发组件和示例文档,最终把示例文档打包为静态站点部署,并且把组件源码打包为最终发布的npm包,我们可以专注于组件开发,节省了大量配置的时间。

1.2 特性

全新的 dumi 2.0 主要具备以下特性:

  • 🚀 更好的编译性能:通过结合使用 Umi 4 MFSU、esbuild、SWC、持久缓存等方案,带来比 dumi 1.x 更快的编译速度
  • 🔍 内置全文搜索:不需要接入任何三方服务,标题、正文、demo 等内容均可被搜索,支持多关键词搜索,且不会带来产物体积的增加
  • 🎨 全新主题系统:为主题包增加插件、国际化等能力的支持,且参考 Docusaurus 为主题用户提供局部覆盖能力,更强更易用
  • 🚥 约定式路由增强:通过拆分路由概念、简化路由配置等方式,让路由生成一改 dumi 1.x 的怪异、繁琐,更加符合直觉
  • 💡 资产元数据 2.0:在 1.x 及 JSON Schema 的基础上对资产属性定义结构进行全新设计,为资产的流通提供更多可能
  • 💎 继续为组件研发而生:提供与全新的 NPM 包研发工具 father 4 集成的脚手架,为开发者提供一站式的研发体验

1.3 什么时候会用到

搭建自己的函数库和组件库通常是为了提高代码的复用性和开发效率,特别适合在以下情况下使用:

  1. 多个项目共用的函数或组件。如果你有多个项目需要使用同样的函数或组件,可以将其封装成库来提高复用性和开发效率。这样做可以避免重复编写代码,同时也方便后续维护和更新。
  2. 团队协作开发。如果你在一个团队中进行协作开发,搭建自己的函数库和组件库可以方便不同成员之间的代码共享和协作。这样做可以避免不同成员之间重复编写代码,提高开发效率和代码质量。
  3. 提高代码可维护性。搭建自己的函数库和组件库可以方便后续的代码维护和更新。这样做可以避免代码中存在大量的冗余代码和重复代码,提高代码的可读性和可维护性。

二. 安装dumi

2.1 环境准备

确保正确安装 Node.js 且版本为 14+ 即可。

$ node -v
v14.19.1
复制代码

2.2 创建项目

先找个地方建个空目录

mkdir dumi2-demo && cd dumi2-demo
复制代码

目录创建好后通过官方工具创建项目,选择你需要的模板

npx create-dumi
复制代码

dumi2目前模版有三种

  • Static Site:静态文档站点
  • React Library: react组件库
  • Theme Package: 主题包

0.jpeg

在进行模版类型的时候我们选择React Library模版,可以开发组件和生成静态站点文档,其他的是选择npm管理工具,包名称,描述和邮箱,按自己情况来输入就好了,这里包名称输入的是dumi2-demo

等待安装完依赖后启动项目:

npm start
复制代码

项目就启动起来了,输入http://localhost:8000,可以看到启动成功后的页面,这个界面也就是我们打包后的静态站点。

1.png

2.3 目录结构

一个普通的、使用 dumi 做研发的组件库目录结构大致如下:

.
├── docs               # 组件库文档目录
│   ├── index.md       # 组件库文档首页
│   ├── guide          # 组件库其他文档路由表(示意)
│   │   ├── index.md
│   │   └── help.md
├── src                # 组件库源码目录
│   ├── Button         # 单个组件
│   │   ├── index.tsx  # 组件源码
│   │   ├── index.less # 组件样式
│   │   └── index.md   # 组件文档
│   └── index.ts       # 组件库入口文件
├── .dumirc.ts         # dumi文档的配置文件
└── .fatherrc.ts       # 组件库打包npm包的配置文件
复制代码

2.4 完善工作

在运行成功后的页面,可以看到有几个问题,一个是左上角项目名称换行,一个是要把Foo修改为组件库选项。

修改项目名称项目名称换行问题可以根据类名覆盖原有样式,在.dumirc.ts文件中添加注入的css配置:

import { defineConfig } from 'dumi';
​
export default defineConfig({
  // ...
  styles: [
    `.dumi-default-header-left {
       width: 220px !important;
    }`,
  ],
});
复制代码

修改Foo选项为组件库,在.dumirc.ts文件中添加配置:

import { defineConfig } from 'dumi';
​
export default defineConfig({
  // ...
  themeConfig: {
    name: 'dumi2-demo',
    nav: [
      { title: '介绍', link: '/guide' },
      { title: '组件', link: '/components/Foo' }, // components会默认自动对应到src文件夹
    ],
  },
  // ...
});
复制代码

调整后的界面就变成了,此时就可以开始组件和函数的开发了。

2.png

三. 开发基础组件

mac电脑可以使用自带的命令行操作,window系统建议使用git命令行操作。

3.1 添加组件源代码

这里先新增一个简单的Button组件,在src下面新增Button文件内写入index.tsx文件。

mkdir src/Button && touch src/Button/index.tsx
复制代码

在文件中新增一个简单的Button组件代码:

import React, { memo } from 'react';
import './styles/index.less' // 引入样式
export interface ButtonProps {
  /** 按钮类型 */
  type?: 'primary' | 'default';
  /** 按钮文字 */
  children?: React.ReactNode;
  onClick?: React.MouseEventHandler<HTMLButtonElement>
}
​
/** 按钮组件 */
const Button: React.FC<ButtonProps> = (props) => {
  const { type = 'default', children, onClick } = props
  return (
    <button
      type='button'
      className={`dumi-btn ${type ? 'dumi-btn-' + type : ''}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
};
​
export default memo(Button);
复制代码

3.2 添加less样式和变量

先定义less样式变量,方便在项目中使用的时候改变组件主题样式。

在src下创建variables.less文件

touch src/variables.less
复制代码

添加主题代码:

// src/variables.less
@dumi-primary: #4d90fe; // 主题颜色
复制代码

然后在Button组件库下面新建styles文件夹,里面新建index.less文件

mkdir src/Button/styles && touch src/Button/styles/index.less
复制代码

添加样式文件

// src/Button/styles/index.less
@import '../../variables.less';
​
.dumi-btn {
  font-size: 14px;
  height: 32px;
  padding: 4px 15px;
  border-radius: 6px;
  transition: all .3s;
  cursor: pointer;
}
​
.dumi-btn-default {
  background: #fff;
  color: #333;
  border: 1px solid #d9d9d9;
​
  &:hover {
    color: @dumi-primary;
    border-color: @dumi-primary;
  }
}
​
.dumi-btn-primary {
  color: #fff;
  background: @dumi-primary;
  border: 1px solid @dumi-primary;
}
复制代码

组件源代码添加好后,需要在src/index.ts中引入后暴露一下:

// src/index.ts
export { default as Button } from './Button';
复制代码

在这里引入并暴露出去以后,就可以在项目中通过import { Button } from 'dumi2-demo';来引入了。

3.3 添加demo示例

每一个组件我们可以加一个demo示例,方便使用者能更方便的使用。

在Button目录下新建一个demo文件夹,内建一个基础演示base.tsx文件:

mkdir src/Button/demo && touch src/Button/demo/base.tsx
复制代码

然后添加组件的演示代码:

// src/Button/demo/base.tsximport React from 'react';
import { Button } from 'dumi2-demo';
​
export default () => {
​
  return (
    <>
      <Button type="default">默认按钮</Button> &nbsp;
      <Button type="primary">主要按钮</Button>
    </>
  );
}
复制代码

3.4添加组件文档

再在该文件同目录新建一个index.md文件作为文档说明,这也是生成静态文档站点所需要的。

touch src/Button/index.md
复制代码

添加文档内容,具体内容描述可以看官网MakeDown配置项,这里只在注释里面讲一下用到的配置。

---
category: Components
title: Button 按钮 # 组件的标题,会在菜单侧边栏展示
toc: content # 在页面右侧展示锚点链接
group: # 分组
  title: 基础组件 # 所在分组的名称
  order: 1 # 分组排序,值越小越靠前
---

# Button 按钮

## 介绍

基础的按钮组件 Button。

## 示例 

<!-- 可以通过code加载示例代码,dumi会帮我们做解析 -->
<code src="./demo/base.tsx">基础用法</code>

## APi

<!-- 会生成api表格 -->
| 属性 | 类型                   | 默认值   | 必填 | 说明 |
| ---- | ---------------------- | -------- | ---- | ---- |
| type | 'primary' | 'default' | 'default |  false  | 按钮类型 |
复制代码

全部配置好后,需要重启一下dumi2项目,重启后就可以在浏览器看到效果了。

3.png

3.5 添加单元测试

准备工作

添加好基础组件后,通常需要加上给组件添加测试代码,来保障组件的健壮性。

测试框架采用react最常用的jest工具,再配合react配套的单元测试库,安装依赖:

npm i jest @testing-library/react @types/jest ts-jest jest-environment-jsdom jest-less-loader typescript@4 -D
复制代码
  • Jest: jest单元测试核心库
  • @testing-library/react:React的测试工具库,在React应用中进行单元测试、集成测试和端到端测试。
  • @types/jest:jest的类型。
  • ts-jest:让jest支持ts语法的预设。
  • jest-environment-jsdom: jest的测试环境,使用js-dom库模拟dom环境,默认是node环境。
  • jest-less-loader: jest不认识less和css,使用该插件使jest支持less和css。
  • tytpescript: 这是安装了4版本,最新的5版本会有警告。

装好依赖后在项目根目录新建jest的配置文件jest.config.js

touch jest.config.js
复制代码

在jest.config.js添加以下配置:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest', // 使用ts-jest预设,支持用ts写单元测试
  testEnvironment: 'jsdom', // 设置测试环境为jsdom环境
  roots: ['./src'], // 查找src目录中的文件
  collectCoverage: true, // 统计覆盖率
  coverageDirectory: 'coverage', // 覆盖率结果输出的文件夹
  transform: {
    '.(less|css)$': 'jest-less-loader' // 支持less
  },
  // 单元覆盖率统计的文件
  collectCoverageFrom: ['src/**/*.tsx', 'src/**/*.ts', '!src/index.ts', '!src/**/demo/*'],
};
复制代码

编写单元测试

在Button目录下新建一个__tests__文件夹放置单元测试代码,在里面新建index.test.tsx。

mkdir src/Button/__tests__ && touch src/Button/__tests__/index.test.tsx
复制代码

在index.test.tsx文件中编写我们的单元测试代码

// src/Button/__tests__/index.test.tsximport React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Button from '..';
​
describe('Button组件', () => {
  it('能够正确渲染按钮文字', () => {
    const buttonText = '按钮文字';
    const { getByRole } = render(<Button>{buttonText}</Button>);
    const buttonElement = getByRole('button');
    expect(buttonElement.innerHTML).toBe(buttonText);
  });
​
  it('能够正确渲染默认样式的按钮', () => {
    const { getByRole } = render(<Button >默认按钮</Button>);
    const buttonElement = getByRole('button');
    expect(buttonElement.classList.contains('dumi-btn')).toBe(true);
  });
​
  it('能够正确渲染主要样式的按钮', () => {
    const { getByRole } = render(<Button type="primary">主要按钮</Button>);
    const buttonElement = getByRole('button');
    expect(buttonElement.classList.contains('dumi-btn-primary')).toBe(true);
  });
​
  it('能够触发点击事件', () => {
    const handleClick = jest.fn();
    const { getByRole } = render(<Button type="primary" onClick={handleClick}>点击按钮</Button>);
    const buttonElement = getByRole('button');
    fireEvent.click(buttonElement);
    // 断言函数被调用了一次。
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});
复制代码

单元测试代码分别测试了按钮基础渲染,渲染默认样式,渲染主要样式以及点击测试功能。

保存后在命令行执行npx jest执行一下单元测试:

npx jest
复制代码

jest会自动寻找目录中__tests__文件夹,去执行内部以.test.{js, jsx, ts, tsx}结尾的文件。

4.png

可以看到执行了Button组件的单元测试,并且最后生成了测试报告,标注了测试了哪些文件,每个文件的覆盖率,测试情况。

并且会在项目根目录下生成测试报告的静态站点coverage文件夹,在浏览器打开coverage/lcov-report/index.html文件,即可看到测试报告,通过对每个文件的分析,可以看到有哪些单元测试没覆盖到的地方,可以帮助我们更好的写单元测试。

5.png

四. 基于antd二次开发

有时候除了从0封装基础组件之外,还会基于antd等组件库进行二次开发,方式和开发基础组件是一样的,只是要在打包package包时注意css的引入。

4.1 添加组件代码

先安装antd库,安装到开发依赖,后面会添加到peerDependencies依赖中。

npm i antd -D
复制代码

先做一个基础的例子,二次封装一下antd的按钮组件, 让它是primary风格的组件。

在src下新建一个PrimaryButton文件夹,内建index.tsx

mkdir src/PrimaryButton && touch src/PrimaryButton/index.tsx
复制代码

在index.tsx里面编写代码:

// src/PrimaryButton/index.tsximport React, { memo } from "react";
import { Button, ButtonProps } from "antd";
​
type IPrimaryButtonProps = Omit<ButtonProps, 'type'>
​
const PrimaryButton: React.FC<IPrimaryButtonProps> = (props) => {
​
  const { children, ...rest } = props
​
  return (
    <Button {...rest} type='primary'>
      {children}
    </Button>
  );
};
​
export default memo(PrimaryButton);
复制代码

组件源代码添加好后,需要在src/index.ts中引入后暴露一下:

// src/index.ts
export { default as PrimaryButton } from './PrimaryButton';
复制代码

在这里引入并暴露出去以后,dumi会帮我门放在包名称dumi2-demo里面,就可以在项目中通过

import { PrimaryButton } from 'dumi2-demo';来引入了。

4.2 添加demo示例

在PrimaryButton目录下新建一个demo文件夹,内建一个基础演示base.tsx文件。

mkdir src/PrimaryButton/demo && touch src/PrimaryButton/demo/base.tsx
复制代码

添加组件示例代码:

// src/Button/demo/base.tsximport React from 'react';
import { PrimaryButton } from 'dumi2-demo';
​
export default () => {
​
  return (
    <PrimaryButton>默认按钮</PrimaryButton>
  );
}
复制代码

4.3 添加组件文档

再在该文件同目录新建一个index.md文件作为文档说明,这也是生成静态文档站点所需要的。

touch src/PrimaryButton/index.md
复制代码

添加文档内容,具体内容描述可以看官网MakeDown配置项,这里只在注释里面讲一下用到的配置。

---
category: Components
title: PrimaryButton # 组件的标题,会在菜单侧边栏展示
toc: content # 在页面右侧展示锚点链接
group: # 分组
  title: 二次封装组件 # 所在分组的名称
  order: 2 # 分组排序,值越小越靠前
---

# PrimaryButton 按钮

## 介绍

基础的按钮组件 PrimaryButton。

## 示例 

<!-- 可以通过code加载示例代码,dumi会帮我们做解析 -->
<code src="./demo/base.tsx">基础用法</code>

## APi

<!-- 会生成api表格 -->
| 属性 | 类型                   | 默认值   | 必填 | 说明 |
| ---- | ---------------------- | -------- | ---- | ---- |
| size | 'small' | 'midlle' | 'large |  false  | 按钮大小 |
复制代码

然后重启一下dumi2项目,可以看到页面上已经有最新添加的组件了。

6.png

4.4 添加单元测试

在PrimaryButton目录下新建一个__tests__文件夹放置单元测试代码,在里面新建index.test.tsx。

mkdir src/PrimaryButton/__tests__ && touch src/PrimaryButton/__tests__/index.test.tsx
复制代码

在index.test.tsx文件中编写我们的单元测试代码

// src/PrimaryButton/__tests__/index.test.tsximport { render } from '@testing-library/react';
import { ButtonProps } from 'antd';
import React from 'react';
import PrimaryButton from '..';
​
describe('PrimaryButton按钮', () => {
  const buttonProps: ButtonProps = {
    loading: false,
    onClick: jest.fn(),
  };
​
  it('正确渲染按钮', () => {
    const buttonText = '点击按钮';
    const { getByRole } = render(
      <PrimaryButton {...buttonProps}>{buttonText}</PrimaryButton>,
    );
    const buttonElement = getByRole('button');
    expect(buttonElement.textContent).toBe(buttonText);
  });
​
  it('正确渲染按钮默认type', () => {
    const { getByRole } = render(
      <PrimaryButton {...buttonProps}>点击按钮</PrimaryButton>,
    );
    const buttonElement = getByRole('button');
    expect(buttonElement.classList.contains('ant-btn-primary')).toBe(true);
  });
});
复制代码

一个基于antd4二次开发简单组件就封装好了,由于在封装的时候没有引入antd原Button组件的样式,打包后会出现样式丢失问题,在最后打包章节会有处理方式。

作者:Ausra无忧
链接:juejin.cn/post/722280…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。