近期负责公司内部 React UI 组件库的搭建,过程当中踩了很多坑,主要仍是对整个搭建过程没有一个比较清晰的概念,本文章就做为整个搭建过程的一个总结吧。前端
本文将详细讲述搭建一个前端 UI 组件库须要涉及的流程和相关知识、工具,其中参考了一些主流开源组件库的作法,阅读大约须要15分钟。vue
Monorepo
(Monolithic Repositories)是目前比较流行的一种将多个项目的代码放在同一个库统一管理的代码管理组织方式,这种方式可以比较方便地进行版本管理和依赖管理,一般配合 lerna
管理多个 package。node
那么,UI 组件库需不须要使用 Monorepo
这种模式呢?webpack
纵观目前各大开源项目,像 React
、babel
等生态较为丰富的项目,都是以”一个主包,多个从包“构建的生态系统,比较适合采用 Monorepo
的方式管理复杂的依赖关系,例如 React 的 packages
:git
但对于 UI 组件库来讲,每一个组件做为一个个独立的单元存在,相互之间的依赖通常比较少,因此对于组件库自身没有必要采用 Monorepo
的方式拆分多个 package。那以组件库为主包、各类自研的工具库做为从包的方式可使用 Monorepo
进行管理吗?答案是能够的,目前有赞的 zent 就是采用这种方式:github
因此到底应不该该采用 Monorepo
的模式呢?个人建议是,若是仅仅是想造好组件库这一个轮子,而且没有依赖关系比较复杂的其余库须要统一管理,那就直接以一个 package 来管理便可,怎么简单怎么来!web
在肯定组件库的目录结构以前,先大体捋一下组件库须要有哪些组成部分:shell
组成的目录结构大体以下:npm
├── build // 打包脚本
├── docs // 文档部署目录(Github Pages)
├── examples // 示例代码(本地开发环境)
├── lib // 打包结果
├── scripts // 自动化脚本
├── site // 文档静态站点
├── src // 组件库源码
├── components // 全部组件
├── [componentName] // 单个组件
├── __tests__ // 组件测试文件
├── styles // 样式
├── types // 类型声明文件
├── tests // 测试
├── .babelrc // 插件
├── .eslintrc // eslint 配置
├── .publish-ci.yml // npm 包发布、站点部署 CI 脚本
├── jest.config.js // Jest 配置文件
└── package.json // package.json
复制代码
首先须要一套可运行的开发环境,以供咱们在本地调试、运行组件代码。json
参考大多数 UI 组件库的作法,能够将 examples
下的示例代码组织起来并暴露一个入口,使用 webpack 配置一个 dev-server,后续对组件的调试、运行都在此 dev-server 下进行。
或者也可使用脚手架工具搭一个单页应用做为本地开发环境,可省去配置 webpack 的麻烦。方法其实有不少种,目的是为了让组件在本地 Run 起来,以方便项目的 developers 和 contributors。
最后,记得将 dev-server 的启动脚本加入 npm scripts 中,例如:
package.json:
在开发一个组件以前,须要建立组件目录、建立组件文件、初始化组件模板、建立测试目录/文件等一系列繁琐又重复的工做,这些其实均可以使用脚本自动化实现。
能够在 scripts
目录下写一个 node 脚本实现以上过程,并在 package.json
中添加 scripts
脚本加入到 npm 工做流中:
package.json:
scripts/new_component.js:
UI 组件做为高度抽象的基础公共组件,编写单元测试是颇有必要的。一方面,单元测试可以覆盖到一些端到端测试覆盖不到的点,另外一方面,也能提升组件代码的可维护性,保证代码质量。
关于单元测试框架的选型,以 React UI 组件库为例,目前比较流行的组合是 Jest
+ Enzyme
。
Jest
是 Facebook 开源的一个前端测试框架,自带断言库,配置简易,提供了 JSDOM、Mock 系统、快照测试、异步代码测试、静态分析结果等测试功能。
Jest
会在如下几个地方寻找测试文件:
__tests__
目录下后缀为 .js
的文件.test.js
的文件.spec.js
的文件通常会把测试文件放在对应的组件同级目录下,这样在语义上是有意义的,而且引入路径也更短些。
Enzyme
是 Airbnb 开源的一个 React 测试类库,提供了一套简洁又强大的 DOM 处理 API。Enzyme
是对官方测试工具库 ReactTestUtils
的二次封装,并内置了 Cheerio
(一个号称 “服务端 JQuery” 的爬虫库)。
如下是一个使用 Jest + Enzyme 编写的单元测试示例:
package.json:
对于打包后的文件,统一放在 lib
目录下,同时记得要在 .gitignore
中加上 lib
目录,避免将打包结果提交到代码库中。
umd
兼容了 AMD
和 CommonJS
两种模块化规范,可同时支持浏览器、Node 两种宿主环境,经过指定 Webpack 配置中的 output.libraryTarget
字段为 umd
便可:
libraryTarget
还可根据须要设置为 window
、global
、commonjs
等值应对不一样的打包场景,具体参考 Webpack 配置文档。
在 UI 组件库的使用场景中,每每有时候只需引入个别组件而非全量组件,那么就要求组件库须要有可以按需加载的能力,支持相似以下方式的引入语法:
实现按需加载,通常有两种方式: Tree Shaking 和单独打包组件。
若是经过 Tree Shaking 实现按需加载,那么通过 babel 编译的 umd
包确定是没法知足的,须要另外提供一份 ES6 Module
规范的包。
可是!Webpack 的 libraryTarget
不支持 ES6 Module
规范的包的导出。只能借助于其余打包工具例如 rollup
来导出 ES6 Module
规范的包,并配合 package.json
指定 module
字段为 ES6 Module
规范的包的路径,以实现使用 ES6 的方式引入包时读取到的是提供的 ES6 Module
规范的包。
若没法导出 ES6 Module
规范的包,也能够采用目前大多数组件库的作法:将组件单独打包。
将组件单独打包须要在 Webpack 中配置多个entry
,大体配置以下:
使用以上配置最终会将组件打包到不一样的目录下,这样就能够支持按需引入的方式加载组件了:
可是,要实现上文提到的 import { Alert, Button } from 'ui-library'
这种引入方式,还须要借助第三方的 babel 插件来实现引入语句的转换,目前使用比较多的有:
babel-plugin-component
babel-plugin-import
对于 umd 和按需加载的两种打包方式,须要分别提供对应的 npm script,例如:
组件库的文档通常都是对外可访问的,所以须要部署到服务器上,同时也需具有本地预览的功能。
你能够选择本身搭一个用于展现文档的站点,也可使用文档生成器来生成文档站点,比较推崇使用文档生成器。能够根据本身比较擅长的技术栈选择特定的文档生成器,目前主流的文档生成器有:
以上几种文档生成器均支持在 markdown 文件中插入 js/jsx 或特定的组件标签,区别在于语法风格不一样。若是使用的是 React 技术栈,只能说,Docz 所引入的 MDX 语法,真香!甩个图随意感觉下:
若是项目代码托管在 Github 上的话,能够将文档站点部署在 Github Pages 上。
Github Pages 提供了三种模式,你能够将静态站点放在如下三个位置:
例如在 Github 项目的 Settings 下,将 Github Pages 的 Source 改成 master branch/docs folder
:
经过访问 <username>.github.io/<repository-name>
便可访问到 docs 目录下的资源。
关于文档站点的持续集成和自动化部署,能够借助官方或第三方提供的 CI/CD 工具,例如:
组件库的某个版本完成开发工做后,须要将包发布到 npm 上。
package.json
中有几个字段值得关注:
格式:英文小写,中划线或下划线分隔。
版本号,符合语义化版本规则,即 major.minor.patch
:
major
:主版本号,不兼容的修改minor
:次版本号,向下兼容的新功能patch
:修订号,向下兼容的问题修复main
是包的入口。例如,Node 环境下使用 import pkg from 'package-name'
导入的就是 main
定义的入口文件,能够是 CommonJS
格式或者 umd
格式。
unpkg
定义浏览器环境使用的入口,通常格式为 name.min.js
。
module
定义用 ES6 模块打包的入口,通常格式为 name.esm.js
。
示例:
description
和 keywords
分别定义包的描述和关键字,有助于包的检索,并在检索结果中显示。
做者,通常格式是 ${name} ${email}
。
除了以上字段以外,根目录下的 README.md
内容会在 npm 包的详情页展现。
在发布以前,须要确保 UI 组件库的代码可以经过全部编写的测试用例,运行先前加入工做流的 npm run test
命令便可。
在发布 npm 包以前,须要执行打包构建,确保 lib
目录下的内容包含当前须要发布的内容。
使用 npm version
命令自动计算下一个版本号,并将该版本号更新到 package.json 文件中。能够手动指定符合语义化版本规则版本号,也可使用 npm version
提供的一些快捷命令自动更新版本号。npm version
的语法以下:
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
复制代码
若是在一个 Git 仓库下执行 npm version
,会默认新增一个同名 Tag,这一默认行为能够用 npm --no-git-tag-version version
禁止。
更多关于 npm version
的用法能够参考官方文档。
npm adduser
注册).npmrc
配置,确保 registry=https://registry.npmjs.org/
(可经过 npm config edit
查看或编辑)npm login
在本地登陆 npm 帐号npm publish
以上过程发布的是一个 unscoped
包,当发布的包跟 npm 上现有的包存在命名冲突时,就须要发布一个特定做用域下的 scoped
包:
@scope/package-name
npm publish --access public
,以免被默认识别为私包(私包是收费的T T)咱们须要为特定的版本运行 git tag -a [tagname]
打上 Tag,并运行 git push origin [tagname]
显式将该 Tag 推送至远程。
这一过程能够在 npm 包发布以前,也能够在 npm 发布以后,在后面的自动化发布中会涉及到这一点。
若是每次发布都须要运行测试、打包构建、更新版本号、npm 包发布等流程,未免有些繁琐,须要借助一些自动化的手段将上述流程串连起来,实现自动化发布。
自动化发布 npm 须要有一个触发点,能够是运行 npm script 时触发 pre
、post
钩子实现全流程的自动化,例如:定义 preversion
、postversion
钩子脚本,在执行 npm version
先后会分别执行这两个脚本。也能够配合 CI/CD 工具,例如在 CI/CD 脚本中监听 master 分支的 tags 推送,一旦有新 tag 推送至远程,就执行上述的发布流程,包括文档站点的部署也能够在这里实现。
通常开源项目会将 Changelog 维护到一个 Markdown 文件里,例如 CHANGELOG.md。
若是你的 commit message 使用的是 Angular 规范 ,那么可使用 conventional-changelog
工具自动根据 commit message 生成 CHANGELOG.md,如下三种 type 的 commit 会被写入 CHANGELOG.md:
feat
fix
Breaking change
一样,须要将该流程加入到 npm script 工做流中:
以上命令会在 CHANGELOG.md 头部追加上次发布以来符合规范的 commit message。
若是你想让更多的开发者参与到组件库的共建,能够以 Markdown 文档的形式提供一份简单的 Contributing Guide,命名为 CONTRIBUTING.md,大体包含如下内容:
本文主要总结了搭建一个前端 UI 组件库所涉及到各个步骤:
参考: