前端 UI 组件库搭建指南

近期负责公司内部 React UI 组件库的搭建,过程当中踩了很多坑,主要仍是对整个搭建过程没有一个比较清晰的概念,本文章就做为整个搭建过程的一个总结吧。前端

本文将详细讲述搭建一个前端 UI 组件库须要涉及的流程和相关知识、工具,其中参考了一些主流开源组件库的作法,阅读大约须要15分钟。vue

Monorepo?

Monorepo(Monolithic Repositories)是目前比较流行的一种将多个项目的代码放在同一个库统一管理的代码管理组织方式,这种方式可以比较方便地进行版本管理和依赖管理,一般配合 lerna 管理多个 package。node

那么,UI 组件库需不须要使用 Monorepo 这种模式呢?webpack

纵观目前各大开源项目,像 Reactbabel 等生态较为丰富的项目,都是以”一个主包,多个从包“构建的生态系统,比较适合采用 Monorepo 的方式管理复杂的依赖关系,例如 React 的 packagesgit

但对于 UI 组件库来讲,每一个组件做为一个个独立的单元存在,相互之间的依赖通常比较少,因此对于组件库自身没有必要采用 Monorepo 的方式拆分多个 package。那以组件库为主包、各类自研的工具库做为从包的方式可使用 Monorepo 进行管理吗?答案是能够的,目前有赞的 zent 就是采用这种方式:github

因此到底应不该该采用 Monorepo 的模式呢?个人建议是,若是仅仅是想造好组件库这一个轮子,而且没有依赖关系比较复杂的其余库须要统一管理,那就直接以一个 package 来管理便可,怎么简单怎么来!web

目录结构

在肯定组件库的目录结构以前,先大体捋一下组件库须要有哪些组成部分:shell

  • 源代码
  • 示例代码
  • 文档
  • 打包结果
  • 测试代码
  • 打包构建配置、脚本
  • 配置文件(babel、eslint、jest 等)
  • 自动化脚本
  • ...

组成的目录结构大体以下: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

Jest 是 Facebook 开源的一个前端测试框架,自带断言库,配置简易,提供了 JSDOM、Mock 系统、快照测试、异步代码测试、静态分析结果等测试功能。

Jest 会在如下几个地方寻找测试文件:

  • __tests__ 目录下后缀为 .js 的文件
  • 后缀为 .test.js 的文件
  • 后缀为 .spec.js 的文件

通常会把测试文件放在对应的组件同级目录下,这样在语义上是有意义的,而且引入路径也更短些。

Enzyme

Enzyme 是 Airbnb 开源的一个 React 测试类库,提供了一套简洁又强大的 DOM 处理 API。Enzyme 是对官方测试工具库 ReactTestUtils 的二次封装,并内置了 Cheerio(一个号称 “服务端 JQuery” 的爬虫库)。

Jest + Enzyme

如下是一个使用 Jest + Enzyme 编写的单元测试示例:

将测试流程加入工做流

package.json

打包

对于打包后的文件,统一放在 lib 目录下,同时记得要在 .gitignore 中加上 lib 目录,避免将打包结果提交到代码库中。

提供 umd 规范的包

umd 兼容了 AMDCommonJS 两种模块化规范,可同时支持浏览器、Node 两种宿主环境,经过指定 Webpack 配置中的 output.libraryTarget 字段为 umd 便可:

libraryTarget 还可根据须要设置为 windowglobalcommonjs 等值应对不一样的打包场景,具体参考 Webpack 配置文档

按需加载

在 UI 组件库的使用场景中,每每有时候只需引入个别组件而非全量组件,那么就要求组件库须要有可以按需加载的能力,支持相似以下方式的引入语法:

实现按需加载,通常有两种方式: Tree Shaking 和单独打包组件。

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 插件来实现引入语句的转换,目前使用比较多的有:


对于 umd 和按需加载的两种打包方式,须要分别提供对应的 npm script,例如:

文档

组件库的文档通常都是对外可访问的,所以须要部署到服务器上,同时也需具有本地预览的功能。

文档生成器

你能够选择本身搭一个用于展现文档的站点,也可使用文档生成器来生成文档站点,比较推崇使用文档生成器。能够根据本身比较擅长的技术栈选择特定的文档生成器,目前主流的文档生成器有:

  • Docz:React 技术栈,MDX(Markdown + jsx)语法,基于 Gatsby.js
  • Storybook:支持 Vue/React/Angular 等,提供功能丰富的 addons 插件加强文档交互体验。
  • React Styleguidist:React 技术栈,支持在 md 文件中解析 js/jsx 代码块。
  • VuePress:Vue 技术栈,支持在 md 文件中插入 Vue 组件。

以上几种文档生成器均支持在 markdown 文件中插入 js/jsx 或特定的组件标签,区别在于语法风格不一样。若是使用的是 React 技术栈,只能说,Docz 所引入的 MDX 语法,真香!甩个图随意感觉下:

文档部署

Github Pages

若是项目代码托管在 Github 上的话,能够将文档站点部署在 Github Pages 上。

Github Pages 提供了三种模式,你能够将静态站点放在如下三个位置:

  • master 分支
  • master 分支的 docs 目录
  • gh-pages 分支

例如在 Github 项目的 Settings 下,将 Github Pages 的 Source 改成 master branch/docs folder

经过访问 <username>.github.io/<repository-name> 便可访问到 docs 目录下的资源。

CI/CD

关于文档站点的持续集成和自动化部署,能够借助官方或第三方提供的 CI/CD 工具,例如:

  • Github + Travis CI
  • Github + Github Actions
  • Gitlab + Gitlab CI/CD

发布

组件库的某个版本完成开发工做后,须要将包发布到 npm 上。

关于 package.json 你须要知道的

package.json 中有几个字段值得关注:

name

  • 发布到 npm 上的包名
  • 安装时的包名

格式:英文小写,中划线或下划线分隔。

version

版本号,符合语义化版本规则,即 major.minor.patch

  • major:主版本号,不兼容的修改
  • minor:次版本号,向下兼容的新功能
  • patch:修订号,向下兼容的问题修复

main、unpkg、module/jsnext:main

main包的入口。例如,Node 环境下使用 import pkg from 'package-name' 导入的就是 main 定义的入口文件,能够是 CommonJS 格式或者 umd 格式。

unpkg 定义浏览器环境使用的入口,通常格式为 name.min.js

module 定义用 ES6 模块打包的入口,通常格式为 name.esm.js

示例:

description、keywords

descriptionkeywords 分别定义包的描述和关键字,有助于包的检索,并在检索结果中显示。

author

做者,通常格式是 ${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 包发布

  • npm 官网 注册一个帐号(或者使用 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 时触发 prepost 钩子实现全流程的自动化,例如:定义 preversionpostversion 钩子脚本,在执行 npm version 先后会分别执行这两个脚本。也能够配合 CI/CD 工具,例如在 CI/CD 脚本中监听 master 分支的 tags 推送,一旦有新 tag 推送至远程,就执行上述的发布流程,包括文档站点的部署也能够在这里实现。

维护

CHANGELOG.md

通常开源项目会将 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。

CONTRIBUTING.md

若是你想让更多的开发者参与到组件库的共建,能够以 Markdown 文档的形式提供一份简单的 Contributing Guide,命名为 CONTRIBUTING.md,大体包含如下内容:

  • Issue/Pull Request 规范
  • 开发环境搭建
  • 开发规范

总结

本文主要总结了搭建一个前端 UI 组件库所涉及到各个步骤:

  • 代码组织方式:是否应该采用 MonoRepo?
  • 目录结构
  • 组件开发:本地开发环境、组件初始化
  • 单元测试:框架选型(Jest + Enzyme,以 React UI 组件库为例)
  • 打包:提供 umd 规范的包、按需加载的两种打包方式
  • 文档:文档生成器对比、文档部署
  • 发布:package.json 相关字段说明、发布前的准备工做、npm 发布流程、自动化发布
  • 维护:CHANGELOG.md 自动生成、CONTRIBUTING.md 主要内容

参考:

相关文章
相关标签/搜索