从文档开发框架到package.json,带你走一轮React组件库构建与发布

1,940 阅读9分钟

1 前言

大家好,我是心锁,一枚23届准毕业生。

近期我正在尝试完成所谓的「拐角轮播」,目前稍有收获(虽然拐角部分完成的并不是很好)

image-20220824235700796

在完成之后,本来是打算写一下「CornerSwiper」的实现思路的,但是后来在打包组件库时却屡屡翻车,最终怒火之下我决定先把我在打包与发布React组件库时踩的所有坑进行一个总结,并尝试输出一份能让读者在十分钟内完成react组件库构建与发布的实践指南。

2 技术方案

经过调研,发现一件严肃的事情,即国内的组件库构建实战分别两个特点,要么是特别详细但是需要大量配置,要么特别容易上手但是巨多坑,所以经过不断进行技术方案的调研,最终选用了如下的技术方案进行组件库搭建。

  • dumi - 负责组件开发及组件文档生成,基于umi框架
  • father 2.x - 负责组件库的构建,即打包的过程
  • tailwindcss 3.x - 负责提供原子类,优化开发体验
  • ts+less - 组件库经典方案
  • github-pages发布组件库文档

同时,相关demo已以分支的形式放在了github上,可以拉下来尝试github.com/GrinZero/ma…

2.1 核心方案对比

2.1.1 dumi对比storybook

经过笔者实践,得出一份在React组件库构建时的对比

对比dumistorybook
国内教程多🔥
安装难度简单🔥(开箱即用)一般
组件文档编写简单🔥(基本全React原生语法)麻烦(需要额外学习mdx以及storybook自己实现的语法)
编译体验快🔥(MFSU,号称比VIte还快)一般
UI我喜欢❤️真觉得不好看

对比过程我就不说了,都是泪,这是被我废弃的storybook仓库github.com/GrinZero/ma…

mdx的编写麻烦不说,丢失代码提示、学习新的并且看起来很无语的API,都着实让我心累。

  • dumi的文档编写

image-20220825012204503

image-20220825005000466

  • storybook的文档编写

image-20220825012318208

(而且UI也让我不甚喜欢)

image-20220825004754459

故此,一觉醒来我换成了dumi,半个小时即迁移完毕~

2.1.2 构建库(打包)方式对比

常见的打包方式包括webpack、rollup以及vite(基于rollup),更老的则包括gulp

而我最后选择了father,准确来说是father 2.x,一方面是dumi官方推荐,一方面是上边各个打包方式均有一个特点,就是需要从头配置,并且大多包含大量的依赖项、配置项,有一定的学习成本,比较复杂。

image-20220825015907272

而在father4.rcfather 2.x之间我之所以选择了2.x而不是dumi官方脚手架@umijs/create-dumi-lib 安装下来的4.rc,主要原因在于father4.rc真的非常多坑:

对比father2.xfather4.rc(father next)
less打包可以选择性转换成css与bundle🔥不支持😈
自定义rollup插件支持添加rollup插件🔥仅umd模式下支持
cssModulesrollup模式下支持不支持
postCSS支持通过extraPostCSSPlugins或者extraRollupPlugins都可以仅umd模式下支持

会发现,father4比较还是处于rc阶段的产物,太多功能是不支持的,想在father4.rc集成原子类要踩的坑实在太多,通过father4打包出来的产物和下图类似

image-20220825020037451

这种打包形式目前已知的问题包括

  • 在NextJs无法使用,因为Next是约定式导入样式文件,仅允许在_app.tsx文件中导入样式
  • 正常项目未配置less-loader无法使用,怎么会有组件库打包继续使用import "xxx.less"这种语法,这本身就是不对的

而通过father2.x配置后,理想的导出结果类似于:

image-20220825020439694

2.2 CSS方案选择

专门为CSS开了一个单节,因为发现在组件库的设计中,CSS其实是很重要的一部分。

我们知道,CSS样式覆盖的问题一直是CSS的"特性",社区中为了解决css样式冲突也有不少方案,其中基本包括css modules、css in js、BEM规范、原子类这些解决方案。

对于前两者,很遗憾,对于组件库来讲我们并不推荐使用,原因是会给使用者的样式覆盖造成一定的困惹。

所以我对比tailwindcsswindicss后选择了tailwindcss

8E0B48BD4AA1E478A961D2C5EC0ECDDB

一方面,经过调研,常规认知中tailwindcss的卡顿特性,随着“JIT”模式的推出,已经消失得一干二净。

另一方面,调研发现,windicss实现的技术是所谓的virtual包,虚拟npm包。

而很遗憾,上文提到的MFSU功能并不支持虚拟npm包(见这个issue github.com/umijs/umi/i…

image-20220825103919732

image-20220825103947107

而且即便通过webpack集成进去,我们也会发现,由于father2father4.rc并不是很支持配置webpack插件,在此基础上,配置windicss会很艰难。

(不要寄希望于postcss,虚拟包就是不支持)

3 方案实现

说完了技术方案的选型,我们进入方案的实践(踩坑)阶段。

当然如果你不想重新走一遍,可以直接拉取github.com/GrinZero/ma…

3.1 初始化项目

3.1.1 使用dumi脚手架初始化项目

基于我们需要搭建组件文档的需求,这里我们只需要运行

yarn create @umijs/dumi-lib

那么我们可以得到如图的目录

image-20220825122938348

3.1.2 使用father2而不是father4

需要注意的是,默认使用father4.rc来进行构建,我们需要手动将版本修改为更稳定的2.x,我这里是使用2.30.21

image-20220825123204157

3.1.3 安装tailwindcss

值得注意的是,由于tailwindcss3依赖于postcss8,而umi目前为止还是在使用postcss7,所以我们必须安装兼容postcss7版本的tailwindcss

yarn add tailwindcss@npm:@tailwindcss/postcss7-compat

image-20220825123619698

3.2 构建打包

在进行下一步之前,请先执行

yarn install

完成依赖安装

3.2.1 文件迁移

  • docs/index.md移动到src/index.md,原因在后边说。

3.2.2 .umirc.ts

可以看到,这是umi脚手架生成的初始配置

image-20220825124448591

而我们需要把它改成这样

image-20220825124546407

在这一步,需要安装一个依赖(autoprefixer其实默认配置了,但是我还是加上了)

yarn add postcss-import@^11 -D

而这里是完整的.umirc.ts的代码

import { defineConfig } from 'dumi';

export default defineConfig({
	mode: 'doc',
	base: '/magic-design-react',
	publicPath: '/magic-design-react/',
	exportStatic: {},
  outputPath: 'docs',
	title: 'magic-design-react',
	favicon: 'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
	logo: 'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png',
	mfsu: {},
	theme: {
		'@primary-color': '#7FA1F7',
	},
	extraPostCSSPlugins: [
		require('postcss-import'),
		require('tailwindcss')({
			config: './tailwind.config.js',
		}),
		require('autoprefixer'),
	],
});

我们分点解释一下这里的各个配置原因:

#1 mode:'doc'

显式配置mode,确保创建的是文档模式组件库

#2 base、publicPath、outputPath

可以看到,base和publicPath均被配置了我的工程名称

image-20220825125038239

	base: '/magic-design-react',
	publicPath: '/magic-design-react/',

目的是为了适应在github pages上进行部署,github pages带有项目名后缀,

image-20220825125246599

而另外一点值得注意的是outputPath,我们填写的是docs,原因就在上图的红圈处,github page目前看到提供的选项就是「root」和「docs」,所以我们需要打包到docs文件夹。

#3 extraPostCSSPlugins

我们发现,这里新增了两个插件

image-20220825163530686

其中,tailwindcss负责为我们引入tailwindcss,而postcss-import则是做了一个把css文件中的@import语法进行bundle的操作。

  • 为什么需要postcss-import?

踩了多次坑之后,我参考了tdesign-reactantd这两个组件库,发现这两个组件库都是通过直接引入bundle样式来引入组件库样式的。并不存在css按需引入

3.2.3 .fatherrc.ts

我们知道,脚手架生成的基本没啥配置

image-20220825164748275

而现在我们需要改成这样:

image-20220825165622548

import resolve from '@rollup/plugin-node-resolve';
import postcss from 'rollup-plugin-postcss';

export default {
	esm: 'rollup',
	lessInRollupMode: {
		javascriptEnabled: true,
	},
	entry: ['./src/index.ts'],
	extraRollupPlugins: [
		resolve(),
		postcss({
			extract: true,
			plugins: [
				require('postcss-import'),
				require('tailwindcss')({
					config: './tailwind.config.js',
				}),
				require('autoprefixer'),
			],
		}),
	],
	autoprefixer: {
		browsers: ['ie>9', 'Safari >= 6'],
	},
};

解释一下各个选项~

#1 lessInRollupMode

重要,把less转成css,而不是保持less,当然也可以配置同时打包,和antd一样方面用户配置样式

#2 extraRollupPlugins

  • resolve插件

    image-20220825170029933

  • postcss插件

    • 开启extract,输出CSS
    • 配置plugins,和umi保持一致,这也是为什么我通过postcss来引入tailwindcss,我们尽量保证生产和开发一样。

3.2.4 全局CSS的配置

建立src/global.css,并在src/index.ts引入

image-20220825163011189

必须引入,原因是,umi虽然约定式自动引入,但是对于father无效的。

同时我建议在global.css中配置需要全局引入的css

image-20220825170609445

postcss-import会把这份文件打包在一起

image-20220825171613204

注意!我们需要手动开启tailwindcss的jit模式

image.png

3.2.5 配置package.json

最后一步,我们需要配置package.json

这是最简单却重要的一步

一方面,我们需要设定module,typings以及files,前两者指定了npm包的入口\npm包的类型入口,而files则指定了我们发包时需要上传的文件(图中代表需要上传dist)

image-20220825171804185

另一方面,很重要的一步是设定peerDependencies指定react框架的版本,详情看reactjs.org/warnings/in…

image-20220825172237776

而指定React版本,实际上是最大的坑,非常建议能做兼容就兼容,否则问题很大。

image-20220825172333300

我们通过||来兼容不同版本的React,否则我们可能会遇到类似这样的报错

TypeError: Cannot read properties of null (reading 'useRef')

3.3 发布npm

npm包的发布也是有坑的~

我们的包名是magic-design-react,在这种情况下,不会遇到什么坑。

但如果我们发的包名是@xxx/magic-design-react,我们就有必要做两件事。

3.3.1 创建组织

创建组织是免费的,尽管创建就是了

image-20220825172950609

3.3.2 配置token

写在根目录的.npmrc,记得不要跟着git仓库上传了

image-20220825173234668

3.3.3 发布命令变化

如果想免费发布一个带有作用域的npm包,我们需要使用npm publish --access public 来发布

我默认读者对于npm发布是比较理解的,所以不细说,只讲了一些小坑

3.4 发布Github Page

3.4.1 github page配置

我们使用main分支下的docs文件夹来支持github page

image-20220825174026925

3.4.2 打包发布

在根目录下运行docs:build命令就会进行打包

yarn docs:build

打包产物在docs文件夹,注意该文件夹需要跟着git上传,不可以配置.gitignore

image-20220825174323269

然后直接通过docs:deploy发布即可

image-20220825174401890

(平时使用npm run deploy就行,不需要拆开)

4 总结

那么跟着本文跑完,我们已然完成了一个esm标准的React组件库

再次抛一个deom链接,可以直接拉下来跑github.com/GrinZero/ma…

(当然如果想从头跑一圈也行)

然后就是,如果觉得好用给点个Star呗~我看大家反馈来确定要不要抽成一个简单的脚手架。

最后,亲来参观下我的组件库吧grinzero.github.io/magic-desig…

(也可以给组件库点个star哦github.com/GrinZero/ma…

image-20220825174534670

image-20220825174603397

额外附带一份vite的rollup我的最佳实践

# vite的rollup打包

这里是rollupOptions部分,用于打包出组件库

image-20220825013348543

后边这里则生成.d.ts,提供类型支持

image-20220825013712052

在vite的plugins中配置

image-20220825013736353