写点什么

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

  • 2020-08-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-08-16 14:074857

评论 1 条评论

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

网络攻防学习笔记 Day114

穿过生命散发芬芳

网络安全 8月日更

iOS开发:Mac电脑Xcode里面添加导入真机调试包的步骤

三掌柜

8月日更 8月

面试重灾区:请说说mybatis的一级缓存和二级缓存

小鲍侃java

8月日更

最小二乘法,了解一下?

华为云开发者联盟

数据 数据处理 计算 最小二乘法 数学工具

获取 NodeJS 程序退出码

编程三昧

node.js Node 8月日更

架构师实战训练营|课后作业| 1

Frode

#架构实战营

CSS 数学函数之calc、clamp、min、max

devpoint

CSS css3 8月日更

百度地图开发-引入地图SDK并配置 02

Andy阿辉

android Android 小菜鸟 8月日更

Vue进阶(六十三):如何使浏览器打开时,默认的文档模式就是标准模式

No Silver Bullet

Vue 8月日更

懵逼!阿里一面就被虐了,幸获内推华为技术四面,成功拿到offer

编程susu

Java 编程 程序员 面试 计算机

牛逼Git,豆瓣评分高达9.3神著“Pro Git”电子版国内首次开源

Java~~~

Java git 架构 面试 架构师

脉脉转发3W次的字节内部首发“数据结构算法”手册!惨大厂被封杀

Java~~~

Java 架构 面试 算法 架构师

NodeJs深入浅出之旅:异步I/O (上)🐋

空城机

JavaScript 大前端 Node 8月日更

智能运维系列直播间开讲啦,就在今天!

浪潮云

【漏洞分析】远程命令执行漏洞总结

网络安全学海

网络安全 信息安全 网络 渗透测试 安全漏洞

暴力美学,拒绝平庸,Alibab开源内部神仙级“K8S核心笔记”下载

Java~~~

Java 架构 面试 微服务 k8s

教你一招疯狂拿Offer!用微服务设计一个超大型分布式电商平台

Java~~~

Java 架构 面试 Spring Cloud 架构师

架构实战营 模块一作业

💤 ZZzz💤

架构实战营

MySQL大版本间的区别

4ye

MySQL 后端 innodb 版本 8月日更

Alibaba史上最牛的分布式核心原理解析全彩手册开源!称霸GitHub

Java~~~

Java 架构 面试 分布式 微服务

阿里新产!Spring+SpringBoot+SpringCloud Alibaba全系列高阶笔记

Java~~~

Java spring 架构 面试 Spring Cloud

纯CSS实现beautiful按钮

执鸢者

CSS 大前端 按钮

Linux之export命令

入门小站

Linux

在线JSON转PHP Array工具

入门小站

工具

聊聊 Linux 登陆提示信息 motd 文件

耳东@Erdong

Linux 8月日更 motd

ShardingSphere JDBC 分库分表 读写分离 数据加密

Java 源码 ShardingSphere

不少同学想要放弃秋招了........

今晚早点睡

程序员 秋招

手撸二叉树之二叉树的坡度

HelloWorld杰少

8月日更

HVEC安装与卸载

林建

HVEC DISM++

架构实战营作业--模块一

冬瓜茶

PageHelper原理深度剖析(集成+源码)

阿Q说代码

ThreadLocal 分页 PageHelper 8月日更 mybatis的拦截器

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