闲鱼前端组件库的建设

语言: CN / TW / HK

背景

闲鱼经过这些年发展已经变成了一个业务丰富,数量众多且功能复杂的产品; 而且业务高速发展,需求井喷,需要开发同学能够快速做出响应。

同时这些产品线与闲鱼的设计风格有很多的共性,但各自不同的定位也导致了更多的差异性;

制定一套各产品业务线通用的规范,并在各端提供一套灵活配置的组件物料库,得到一些稳定且高复用性的内容对当前阶段的闲鱼前端来说是有极大的业务和技术价值的。

设计

组件库定位

在确定了要做组件库后我们对前端业务一段时间内的设计稿进行了收集,分析发现各个业务线有较大差异,尤其是在营销活动场景,为了实现更好的营销效果往往需要新颖视觉和交互。在设计组件时如果为了满足所有的场景则势必会导致较大的使用成本。所以最后我们确定定位: 该组件库解决80%的产品线业务。

需求分析

从上面的分析可以看出UI规范是实现该组件库的一个十分关键的基础,为此我们建立了包括客户端和设计师的虚拟小组,进行跨端的设计语言统一。

对于组件库的目标整理如下

  1. 提升效率:不同业务线、不同项目之间工作有大量重复的内容,通过与设计师达成的UI规范为基础建设满足各业务线的组件,减少重复建设。

  2. 提高质量:许多重复被踩的坑,光靠文档效率低下,沉淀组件代码最佳实践。

  3. 统一的交互体验:提供跨端的一致交互体验。

  4. 解决兼容问题:减少重复的兼容性问题和测试的兼容成本。

结合现有的集团能力分析得出基础的能力拓扑图一

实现分析

下图是我们在建设前期分析的实现上述组件库的关键技术点,后面我将围绕这个思维导图进行具体的分析

关键点

整体架构

  • 单包

把所有的组件看成一个整体,一起打包发布。

单个仓库,单个包,统一维护统一管理,比如Antd。

优点

  1. 可以通过相对路径实现组件与组件间,公共代码间的相互引用

缺点

  1. 他是完全耦合在一起的,哪怕一个组件的细小修改都需要对整个项目发一次包。

  2. 由于闲鱼的业务特点决定了它不仅需要考虑源码开发的项目更需要考虑搭建场景。而如果用单包则无法复用集团现有的能力,导致搭建场景下组件重复被打包进页面各个模块的情况。

  3. 公共代码复用虽然有一定的收益,但是在长期维护的项目中也会大大增加维护成本

  • 多包

每个组件彼此独立,单独打包发布,单个仓库多个包,统一维护单独管理。

例如下面的目录结构,所有的组件均在packages下面管理如下图四左图所示,每个组件的目录结构如图三右图所示。

优点

  1. 组件发布灵活,并且天然支持按需使用

  2. 可以复用现在集团的项目管理流程,实现bug沉淀

  3. 可以复用现在集团的能力,实现组件发版的规范控制

  4. 解决搭建场景的依赖重复打包问题

缺点

  1. 组件与组件之间物理隔离。对于相互依赖,公共代码抽象等场景,就只能通过NPM包引用的方式来实现。

  2. 组件相互依赖的调试问题。

    由于只能通过NPM包引用的方式来实现依赖,导致调试不变,这里我们可以用软链的方式解决

  3. 多依赖多版本问题

  4. 例如依赖A,被组件库的a,b组件依赖,如果使用单包不需要考虑A包的版本问题,一个项目只会有一个依赖,但是如果使用多包,a,b组件依赖同一个组件A就会存在不同版本的可能性。

  5. 使用成本较高,每使用一个组件都要安装并引入新包,业务开发同学需要一定的记忆成本。

  • 结论

根据我们项目的特点:多人参与、长期维护、组件分层,最后我们选择基于管理工具lerna的多包管理方式。

但是由于我们的组件库在分级上有「原子组件」和「通用业务组件」之分,考虑上面的使用成本问题,我们决定将原子组件以单包的形式管理,「原子组件」和「通用业务组件」同级,则以多包的形式放在packages下管理。

UI样式

  • 样式

  1. 由于历史原因闲鱼前端的项目一直是行内样式,行内样式的设计导致了较多CSS使用的限制,对keyframes动画也不支持,所以经过调研我们确定了组件脚手架走 非行内的技术方案

  2. 使用css module的style.xxx的方案虽然能够用添加hash后缀的方式解决重名问题,但是也导致修改不够灵活,经过综合考虑最后决定采用 添加统一前缀的方式 ,且pre作为组件的props暴露给业务开发,这样他可以进行样式的配置。这里我们也考虑过借助社区的 babel-plugin-react-css-modules 插件添加统一的前缀,但是实践发现他需要安装在dependence下,原因为:

When babel-plugin-react-css-modules cannot resolve CSS module at a compile time, it imports a helper function (read  Runtime styleName resolution ). Therefore, you must install babel-plugin-react-css-modules as a direct dependency of the project.

考虑我们提供的是基础的组件,不希望引入过多的依赖所以最后放弃了社区插件的方案。

  • 样式差异化

上面也提到不同的业务线存在主题定制、样式差异化的需求,虽然我们在上面提供了pre最为props的方式,但是其不够灵活且修改多个组件时成本过高,所以我们又去参考了社区主题替换的方案,基于css3 的 var() 能力轻易地就能实现想要的效果,修改样式变量对应的值就可以实现该变量对应的样式值的变化。

这里我们定义了全局的样式变量,其拆分的纬度如下:

统一UI样式规范

  1. 色彩

  2. 字体

  3. 布局

  4. icon

统一的交互动画

  1. 动画时长

  2. 动画路径

将拆分后的样式变量放在:root上定义并提供global-css的cdn地址到组件脚手架中,让组件开发同学直接使用变量。经过一段时间实践这里我们也发现该方式会引入一定的开发成本,后期会考虑通过插件的方式关联提醒消除样式变量的使用成本。

组件质量

  • 开发环境

前端经过一段时间的发展对于低级bug的规避已经有了不少工具,这里我们统一了开发语言TypeScript,并且添加了各个linter:

  • EditorConfig

让代码在各种编辑器和IDE中保持风格一致

需要注意的是有的编辑器可以直接使用,比如:WebStorm ;而有的编辑器需要安装对应的插件,比如大部分同学使用的编辑器Visual Studio Code。 最后使用的.editorconfig配置如下:

root = true


[*] # 匹配所有文件
charset = utf-8 # 文件编码是utf-8
indent_style = space # 空格缩进
indent_size = 2 # 缩进空格为2
end_of_line = lf # 使用Unix-style 换行符
trim_trailing_whitespace = true # 除去换行行首的任意空白字符
insert_final_newline = true # 每个文件以换行结束
  • ESLint

Find and fix problems in your JavaScript code

ESLint是针对js进行检查,而我们日常开发时很多时候使用的是ts,对应的工具是TSLint,TypeScript 官方在 19 年放弃 TSLint标记为废弃,全面采用 ESLint

const { getESLintConfig } = require('@iceworks/spec');


module.exports = getESLintConfig('rax-ts');
  • Stylelint

安装stylelint插件,保存就会自动fix,同时也会对有问题的样式进行告警

详见官网: https://stylelint.io/

安装下面的插件,会调整你的样式顺序让代码更有可读性

"stylelint-config-recess-order": "^2.5.0",
"stylelint-order": "^5.0.0"
  • Commitlint

添加了Commitlint检查,其规范如下

/**
* ● feat: 新增功能
* ● fix: 修复 bug
* ● docs: 文档相关的改动
* ● style: 对代码的格式化改动,代码逻辑并未产生任何变化(例如代码缩进,分号的移除和添加)
* ● test: 新增或修改测试用例
* ● refactor: 重构代码或其他优化举措
* ● chore: 项目工程方面的改动,代码逻辑并未产生任何变化
* ● revert: 恢复之前的提交
*
* 提交格式:<type>(<scope>): <describe> 其中scope可忽略
*
* 提交实例:git commit -am 'fix(location): 登录接口地址修改'
*
*/
  • 组件分类

对组件进行分类最底层是原子组件,比如button,它会被通用业务组件引用,不会经常轻易修改;在这上面是通用业务组件,它是基于UI规范的产物,是随着设计需求实时丰富的;最上面是各个行业或者业务的通用业务组件,它不基于UI规范,只是某一时期各个业务线通用的交互能力。

组件开发

  • 脚手架

脚手架构建方面主要基于集团现有的能力不做过多介绍。我们知道lerna 提供了不少Lerna Script供开发者使用,但是我们需要接入集团的脚手架、需要接入天马组件的发布流程等闲鱼业务定制化的需求,所以在这里我们对其在各个生命周期进行了一定修改,让业务开发还是使用Lerna Script的指令,但是实现了我们需要定制化的各个需求。

  • 流程

在有了上述基础后我们已经开发组件了,为了保证组件的质量,我们制定了上面的组件开发流程。目前组件质量脚本检查还不够完善,需要人工cr保障。

s

  • demo

demo部分我们的技术方案是基于Storybook:

Storybook运行在主应用程序之外,用户可以独立地开发UI组件,而不必担心应用程序特定的依赖关系和需求,使开发人员能够独立地创建组件,并在孤立的开发环境中交互地展示组件

Storybook虽然好用但同时也引入了开发同学的学习成本他们不能像以前一样写md,参考了shopify 的polaris项目后我们决定采用将md文件转化为storybook的方式进行。

同时在实践中我们也发现由于我们组件的整体基础是Rax,而Storybook对Rax的支持是有些一言难尽的,目前我们也遇到了不少问题后面有机会可以再详细介绍。

总结

目前组件库的还在进一步的丰富中,开发可扩展性强、上手成本低的组件也是我们这一段时间以来遇到的一大挑战。

已有的部分组件已经在各个业务线使用,其提效的效果还是非常明显的,减少了重复开发工作,业务开发可以无脑使用不用担心UI还原问题,甚至还能对一些UI进行规范。

以上内容如有不对之处,欢迎指正。