QCon广州站Web 3.0 专题上线,关注基础设施及相关技术,戳此了解 了解详情
写点什么

从 0 到 1,搭建一个体系完善的前端 React 组件库

  • 2020 年 8 月 16 日
  • 本文字数:4042 字

    阅读完需:约 13 分钟

从0到1,搭建一个体系完善的前端React组件库

随着前端工程的发展,组件化的思想早已深入人心;现代的前端框架 React/Vue 等,都是围绕组件设计;组件化的开发模式,大大提高了开发效率;设计和开发高质量高复用性的公共组件,可以更好地保持产品迭代的高效和稳定。


我们以 React 的技术栈为背景,在日常的需求与迭代中, 历时两年多时间,沉淀出了携程用车各大产线(接送机/包车/打车服务等)的公共组件(机场、航班、城市、地址、时间控件等)。通过持续交付了一系列的组件库,让各个产线的开发小组不用再各自维护重复而难以迭代的代码,完成了前端组件与公共方法的收口,解决了用车前端业务组件一致性的问题。同时随着组件库工作流上的逐步完善,让前端开发同学脱离了刀耕火种的开发方式,进入了全新的自动化构建与高效开发的时代。


开发和维护一个可持续迭代的组件库,从来都不是一件容易的事情。本文将从组件库的基础搭建开始,从开发、打包、发布、拆包、优化、自动化测试等各方面,由浅及深地进行介绍,给大家分享一个相对完善的组件库落地的过程。同时也会介绍组件库的迭代过程中真正会遇到哪些问题,以及我们是如何解决这些问题的。希望这些实战中的经验,可以带给大家一些启发和想法。


一、实现最基础的 npm 发布流程

在组件库的设计之初,我们最先需要考虑的是,如何让 npm 包的发布流程安全、可靠可行。为了保证代码的安全性,公司内部会独立维护内网的 npm 管理平台。



在最早的发布设计中,我们仍然通过官方定义的 cli 命令,在本地通过设置 registry 指向内网仓库后,执行 npm publish 进行发布。


可是对于公司内部而言,平台开放而 BU 众多,任何人都可以对任何已发布的包进行常规操作,这会带来一系列的不安全因素。最终在前端委员会的推动下,我司实现了内网 npm 与 gitlab ci 的关联。将发布操作迁移到了 gitlab 上,在发布权限上有一定的约束;通过开启 npm deploy 插件,以实现可视化交互式的发布管理,同时得益于 gitlab hook 的强大, 我们更是在流程实现了 push event 来触发 auto publish,这一系列的进步,让我们的组件库在后续的发布流程上变得更加正式、稳定而可靠。



Npm 关联 gitlab 后,通过指定指定分支下特定目录的 package.json,实现版本升级后自动发布


二、组件库的打包处理

我们的技术栈涉及 ReactWeb 与 React Native, 对于 RN 的代码,我们一般会走源码直接发布,RN 项目中的编译过程会自动处理 node_modules 里的源文件。但是对于 Web 组件库而言,更传统的做法,则是需要在发布之前进行一些编译和转码,这样才能确保发布之后的 npm 包,可以在大多数环境下正常运行起来。


对于 Web 端组件库的打包,我们进行了多次的探索和优化。


使用 webpack 对每个组件进行单独打包,打包类型由 umd 改为 commonjs2。


module.exports = {    output: {        filename: '[name].js',        path: path.resolve(__dirname, '..', 'dist'),        library: 'Tha',        libraryTarget: 'commonjs2' // umd    }}
复制代码


通常我们对组件库的建议是 umd 打包,因为这样可以实现多种模块方案的加载通用性。但在实践过程中发现,每个组件都需要单独打包时,UMD 的打包方式,会显著增大每个文件的基础体积;而且我们 99% 的场景下,其实已经并不用再去兼容 AMD、CMD 等模块加载方式。


在确保我们的代码一定是通过 node 模块方式加载的时候,我们只需要打出 commonjs2 的模块即可。这一步的调整,显著地提升了打包速度,也明显减小了各个文件的打包体积。


进一步编译优化


对于组件库而言,使用 webpack 进行打包,即使是使用了 commonjs2 的模式,繁重的配置工具仍然是显得重了一些,而且需要额外配置各种 external 规则,以防止打包时打入了额外的第三方库的代码。使用 rollup 来处理组件库的打包固然比 webpack 要合适,但是又会额外引入新的构建工具,增加学习成本。


最终我们选择的更优化的方案,是使用 babel 直接做编译转换,不使用任何额外的构建工具,也不做压缩优化处理---- 这些工作,在现代化的前端项目中,都会自动处理,不需要组件库再做多余的构建动作。Babel 直接转码的方式,帮助我们省去了很多复杂的配置工作,并且让组件库打出来的生产代码更加容易调试。


优化前,使用 webpack 等构建工具打包组件:


{  "scripts": {    "build:components": "webpack --config ./build/webpack.config.js --color",    "build": "npm run build:components && npm run build:css && npm run copy_package"  }}
复制代码


优化后, 编写脚本直接对组件源文件转码


{  "scripts": {    "build:components": "cross-env NODE_ENV=production node ./build/trans"  }}
复制代码


提取组件中的样式文件,单独打包


Css-in-js 的开发方式固然是方便许多,但是在打正式包时,内嵌的 css 实际会占用更多的代码体积,并且 node_modules 里的 js 代码中如果有显式 require css 的语句时,在同构项目中,可能会遇到服务端解析 css 文件的各种问题。


为了解决这个问题,我们提取了所有组件的 css 进行单独打包。其中所有的基础组件样式,会整体打包成一个 main.css;而复杂业务组件的样式,则会以组件为单位进行单独打包,以便实现后续流程中业务组件的按需加载。



三、组件库实现业务组件的按需加载

与各大知名的开源组件库类似,为了减少项目的打包体积,我们对组件库中的复杂业务组件,如航班组件、机场组件、城市选择组件等,设计了按需加载的模式。


对 RN 而言,我们直接利用了 require 的特性,通过修改导出对象的 get 方法,显式地声明了 lazyLoad 的组件程式。


module.exports = {    //按需动态加载的模块    get AddressList() { return require('./Address/List').default; }};
复制代码


对于 Web 而言,我们采用了类似 ant-design 的方式,在前面对业务组件的 css 进行单独打包处理后,通过在项目中引入 babel 插件的方式,实现组件的按需加载。


import { Address } from '@ctrip/thanos-ctrip-mobile/components.biz'/** 等价于import Address from '@ctrip/thanos-ctrip-mobile/components.biz/Address'import '@ctrip/thanos-ctrip-mobile/components.biz/Address/style.css'*/
复制代码


四、解决组件库日渐臃肿问题

随着组件库的不断迭代,组件代码会不断增多,需求也会越来越复杂。其他研发同学也可能会开发独立的 npm 组件包,但是会基于已开发完成的组件库的部分功能来实现。


这种情况下,开发其他 npm 包的同学,可能只想使用当前已有库中的部分功能,而不太愿意引入一个完整而庞大的组件库。为了使组件库的功能更加独立且通用,让 UI 组件与功能模块之间更好地解耦,我们需要对组件库进行拆子包处理。


如组件项目中基础 UI 部分,从组件库中剥离,拆分成独立的 ui-basic 组件库;组件项目中工具方法(表单校验、环境判断、正则处理、时间日期格式化等),拆分成独立的 util 库。这种拆分组件包的开发形式,组件库不再是所有功能都揉在一个仓库中,开发和维护将变得更加灵活且易于扩展。


拆包前,core 的部分将随着功能的增加而越来越臃肿:



拆包后的结构:



如图所示,拆分独立功能包后,可以让我们扩展和组合出更多灵活多样的组件库,让组件库不再单一而臃肿。


五、解决子组件包的开发环境问题

拆分子组件包后,给组件库的多样性扩展带来了极大的便利,但随之而来的问题便是,每一个子组件包都需要单独维护,在开发子组件包时,每一个包都需要一个可运行的本地开发环境。


随着子组件包的数量逐渐增多,给每一个包都单独设立一个开发环境,必然会带来更大的维护成本。我们目前选择的解决方案是,对于粒度更细的子组件包,所有的子包会公用一套 dev 的开发仓库,通过 git modules 在开发仓库中嵌套子模块仓库,实现了只维护一套开发环境,产出多个子模块包的组件库工厂。



在这种环境下,还可以做到当子模块之间存在相互依赖时,可以直接引用相对路径下其他模块的源码,不必为了调试某个模块的代码,而跑到 node_modules 里去翻找,徒增调试难度。


六、组件库文档化与协同开发

为了让组件库的开发流程更加规范,减少接入方的沟通成本,对组件库进行适当的文档梳理是十分必要的,我们使用 gitbook 编写组件库的文档,并部署到公司内部的 books 平台上。同样借助于 gitlab 强大的 web hook 的能力,实现了文档仓库的自动更新与发布。





与此同时,我们也启用了协同开发的模式,让组件库成为一个内部的开源库,用车产线的研发同学,可以通过提交 issuse 和 merge request 的方式,自行对组件库中的个别需求进行开发,提升开发效率。


七、组件库单元测试、自动化与持续集成

单元测试


当组件库在开发和交付流程上趋于完善后,在公司 G2 战略背景下,为了保证代码的高质量,我们开始在组件库中接入自动化单元测试。接入单元测试也是一项十分曲折的过程。在测试技术框架的选型上,综合考虑了当前技术栈、框架市面通用性等多种因素,最终选择如下:


测试框架:Jest


选取原因:对 React 技术栈友好,同时也是 React-Native 官方推荐的测试框架


测试库:


web 端 -> @testing-library/react


RN ->@testing-library/react-native


选取原因:React 的官方测试库,对 hooks 类型的组件支持度高,选择这两个库,也是为了能够保持后续与 react 官方版本更新的同步


自动化与持续集成


在接入单元测试后,我们依然借助 gitlab 的 CI/CD,对整个组件库的流程进行自动化构建与持续集成交付,在内置 CtripDevOps 或者自定义 gitlab-ci.yml 的配置下,我们将单元测试的环节加入到了 pipeline 中,同时通过公司统一的 sonar 检测,提供最终的组件库质量统计报告。





八、结语

要搭建一个相对完善的组件库,都是需要经过一系列项目的沉淀的。目前而言,组件库的开发流程上依然会存在一些问题,比如版本管理、升级回退等。时间是最好的老师,相信在后面的迭代中,我们依然能够保持初心与热情,积极探索与发现,构建出更加完善的前端工程体系。


作者介绍


剑桥,携程资深前端开发工程师,关注自动化工具开发、前端工程自动构建相关技术。


本文转载自公众号携程技术(ID:ctriptech)。


原文链接


https://mp.weixin.qq.com/s/ZIRV8zHf_hQNhRZJx-q6UA


2020 年 8 月 16 日 14:073767

评论 1 条评论

发布
用户头像
关于子组件和依赖管理的问题,可以试试基于lerna的mono-repo的方式。
2020 年 08 月 25 日 14:52
回复
没有更多了
发现更多内容

TcaplusDB X 黎明觉醒,探索不止,黎明将至

数据人er

数据库 nosql 腾讯云 TcaplusDB

TcaplusDB君 · 行业新闻汇编(8月)

数据人er

数据库 nosql 腾讯云 TcaplusDB

TcaplusDB君 · 行业新闻汇编(8月11日)

数据人er

数据库 nosql 腾讯云 TcaplusDB

课程排课软件开发

(王经理)专业app小程序开发

TcaplusDB x 王者荣耀|五五开黑节,我们无惧挑战!

数据人er

数据库 nosql 腾讯云

AI自助帮你换背景,超强实时人像扣图算法开源啦!

百度大脑

人工智能

仓储管理系统开发介绍

(王经理)专业app小程序开发

ipfs矿机怎样选?ipfs矿机多少钱一台?

分布式存储 IPFS ipfs挖矿 ipfs矿机 filecoin挖矿

TcaplusDB君 · 行业新闻汇编(8月9日)

数据人er

数据库 nosql 腾讯云 TcaplusDB

TcaplusDB君 · 行业新闻汇编(8月12日)

数据人er

数据库 nosql 腾讯云 TcaplusDB

TcaplusDB数据库 X《大主宰:大千世界》,战斗永不停歇

数据人er

数据库 nosql 腾讯云 TcaplusDB

培训教育系统开发

(王经理)专业app小程序开发

ipfs矿机怎么买?ipfs矿机怎么获取?

ipfs矿机怎么买 ipfs矿机怎么获取

ARM工控主板比X86工控主板好吗?

双赞工控

fil矿机怎么买?fil矿机厂商最强哪家?

分布式存储 IPFS Filecoin filecoin挖矿 filecoin矿机

阿里第七次更新的Java面试突击宝典,已累计拿到759份Offer

Java 程序员 架构 面试 后端

学术顶会再突破!计算平台MaxCompute论文入选国际顶会VLDB 2021

阿里云大数据AI技术

这个夏天ChinaJoy“游戏咖啡厅”TcaplusDB如约而至

数据人er

数据库 nosql 腾讯云 TcaplusDB

TcaplusDB君 · 行业新闻汇编(8月7日)

数据人er

数据库 nosql 腾讯云 TcaplusDB

开源大数据Meetup回顾 | 第四范式:现代存储架构下的系统优化实践

ZEGO 教程 | RTC + AI 视觉的最佳实践(移动端)

ZEGO即构

AI RTC 滤镜

24位出品人已确认,开放原子全球开发者峰会(OADC)分论坛速览(更新中)

开放原子开源基金会

从350ms到80ms,揭秘阿里工程师 iOS 短视频优化方案

阿里云视频云

ios 短视频 视频 视频处理 视频流

好身体是吃出来的

石云升

健康 8月日更

TcaplusDB君 · 行业新闻汇编(8月10号)

数据人er

数据库 nosql 腾讯云 TcaplusDB

5月26日来IGS大会腾讯云游戏&新文娱分论坛和TcaplusDB约会吧!

数据人er

数据库 nosql 腾讯云 TcaplusDB

Python代码阅读(第19篇):合并多个字典

Felix

Python 编程 Code Programing 阅读代码

网络攻防学习笔记 Day118

穿过生命散发芬芳

网络安全 8月日更

GaussDB(for Redis)与原生Redis的性能对比

华为云数据库小助手

redis GaussDB GaussDB ( for Redis ) 华为云数据库

TcaplusDB君 · 行业新闻汇编(8月8日)

数据人er

数据库 nosql 腾讯云 TcaplusDB

“腾讯待办”小程序正式发布!初次见面,请多指教。

从0到1,搭建一个体系完善的前端React组件库_前端_剑桥_InfoQ精选文章