微医门户在移动端组件库方向上的探索

序言

来公司两年了,见证了公司组件库从无到有,从1.0到2.0的变迁过程,组件库已有1500+次提交,承载了门户技术部数个移动端项目,也算得上是一个经过考验的组件库了。

组件库内的组件固然重要,但是整体组件库的架构设计更是一个高可用,易扩展组件库的核心,那么应当如何搭建一个优质的组件库?

0、组件库的价值

在应用端的体现:统一视觉规范,保证产品体验的一致性。
在开发侧的体现:组件的可复用性削减了开发成本,也变相地减小了页面及项目体积。

1、typescript支持

相比于1.0的版本,wandui2.0采用了ts的写法,强类型的校验可以将许多语法语义上的错误扼杀在萌芽之中。类型的指定也像是注释一般增强了代码的可读性。另外,在开发完成之后通过指定package.json内的types字段,配合编辑器还有导入的语法提示功能,组件库内置的组件一览无余。
语法提示

2、一键组件生成

{
    "create": "node build/create-component"
}

一键生成新的组件规则,仅需执行一行npm run create,通过命令行inquiry式调用,传入需要新生成的组件名,脚本便会通过读取模板,进行新的组件的初始化,包括生成

  • 组件主文件
  • 组件样式文件
  • 组件文档
  • 将组件添加进路由

等等一系列重复操作,开发人员仅需关注组件本身的实现,大大提升了开发者的开发效率。
一键组件生成

3、打包配置

3.1、为了实现按需加载的处理

为了实现按需加载引入,我们常用babel-plugin-componentbabel-plugin-syntax-dynamic-import这两个babel插件来使得import { WandUploader } from @weiyi/wand-ui在打包阶段转化成import /node_modules/@weiyi/wand-ui/libaryName/uploader/index.js+组件css引入(通用css会从libaryName下引入)来实现。
因此我们要为每个组件作为打包出index.js和组件自身的的css文件,为此在按需打包的配置中我们为组件设置单独入口,样式文件使用MiniCssExtractPlugin来实现拆分,最后得到如下效果。

3.2、组件库的通用样式

当业务方使用按需加载的方式引入我们的组件库,如果每个组件都对组件库通用样式进行引用,无疑会有大量的重复样式代码,由于babel-plugin-component插件已经提供了style选项,当该选项设置为true时,会将组件库通用样式额外引入,因此,为了单独打包出一份通用css文件,我们引入了gulp额外为通用css进行一次打包。

// less编译
gulp.task('css', done => {
  gulp
    .src('../../src/styles/base.less')
    .pipe(less())
    .pipe(concat('base.css'))
    .pipe(autoprefixer({
      overrideBrowserslist: ['last 2 versions', 'ie > 8']
    }))
    .pipe(cleanCSS())
    .pipe(rename(function (path) {
      path.dirname = path.dirname.replace('less', 'css')
    }))
    .pipe(gulp.dest('../../lib/theme-chalk/'))

    done()
})

3.3、获取运行时依赖

配置中需要外置化vue,防止vue被打包进入组件bundle中,而是通过运行时再去外部获取这些扩展依赖,配合指定package.json中的peerDependencies,强制要求接入方对我们外置的扩展依赖进行安装和打包。

// webpack.base.conf.js
externals: [
    {
        vue: {
        root: 'Vue',
        commonjs: 'vue',
        commonjs2: 'vue',
        amd: 'vue'
        }
    }
]
// package.json
"peerDependencies": {
    "vue": ">=2.5.15"
}

4、组件库文档

一份优质的文档是一个成功的组件库的基础,element-ui的官网文档是经受了无数厂商的考验的,仔细观察可以发现element-ui的组件案例和组件文档是一一对应的,难道element-ui的组件demo写了两次?仔细阅读后可以发现,element-ui的文档路由是使用的md类型的文件,而在打包配置中指定了一个自定义的md-loader来处理该路由文件,md文件中的被:::demo:::所包裹的代码块会被执行并编译,内部执行流程大概是这个样子的:
md-loader流程图
在展示demo的项目里,我们只需要注册demo-block组件,并提供source插槽承载编译后的组件demo
highlight插槽承载md中的源代码,即可得到文档即demo的效果了。效果图:
文档即demo
另一个值得权衡的问题是,文档是新开一个项目还是就放在组件库的项目里?
如果放在项目里,组件库一大,项目会略显臃肿,并且每次跑项目都需要把组件进行重新编译,速度堪忧。而如果分离出一个新的项目,则享受不到开发时热更新的遍历,需要把打包文件频繁输出,才能实时看到组件的效果,而且另起一个工程也是有成本的,需要安装两次通用的依赖。而且从维护成本的角度来看,还是放在一个项目内会方便得多,除非你的目标是做一个大而美的业界组件库,而不是为了业务需要快速迭代的产物。(然而没有资本支撑,估计也没人愿意做这么一套东西)

5、发包规范

早期wand-ui2.0刚出时采用指定负责人手动发包的形式,当时刚出的组件库有很多小问题,频繁的发包步骤十分繁琐。后期为了解决这一问题,项目使用gitlab-ci,配合gitlab-runner,在yml配置文件中划分了打包和发包两个stage,打包机完成文档和组件库打包后进行压缩,再远程传输到虚拟机,执行publish脚本,比对线上和当前package.json内的组件库版本,检测到落后后自动publish,再也不用手动发包了。

6、轻量的组件库脚手架

文档打包,组件打包,全量、按需、开发、生产,require("package.json").script会越来越大build目录下的文件也会越来越多,使项目变得臃肿不堪。我们尝试对组件库基本不再修改的打包配置进行了封装,通过命令式的调用形式,读取项目配置,执行命令内置的打包配置,即可完成打包。项目内甚至无需build目录。

同时我们还抽离了wand-template一份组件库最小单元的模板,配合抽离的wand-cli,可以一键式地生成一个组件即demo的vue移动端组件库,并提供了ts环境开发,提供md即文档的特性。

结语

一个好的组件库不仅在组件本身的通用性,易用性,也在于整体组件库架构设计的可重用性,规范性,易扩展性,市面上的组件库层次不齐,组件库搭建可以参考学习业内如element-ui,ant-design,vant等优秀组件库的设计理念,如果有错误烦请在评论区指正交流,谢谢。

文中组件库由wand-ui2.0开发小分队共同完成。