微店前端工程化迭代史

 

微店前端工程化起步于一个内部产品 vbuilder,对外我们有一个开源版本 

bio-cli: https://github.com/weidian-inc/bio-cli

去年我们也写过一篇文章介绍该产品:

bio:一站式前端开发工具(https://juejin.im/post/5aa0d6eb51882555867ef7f4)

这么长时间过去了,我们在前端工程化方面有了哪些变化、遇到了哪些问题、用怎样的方案解决这些问题等等,值得为大家再分享。

V0.0

这里也就是介绍下背景,为什么我们会开发 vbuilder。

总体思路就是:将重复性工作集成化。

当时,团队面临几个问题:

  • 重复 :每个项目要新开一个脚手架(webpack / gulp 之类)

  • 混杂 :工程目录既包含脚手架文件,也包含业务文件

  • 混杂packge.json 中的依赖既有脚手架的依赖,也有业务依赖,难以区分

  • 难更新 :脚手架一旦确定,几乎不再更新,如 webpack 1.0 的项目极有可能会一直维持在 webpack 1.0 状态

  • 协作 :团队协作中,项目的技术栈纷杂,不同人员维护同一个项目成本高昂,如:需重新理解对应工程配置等

总结为下图:

基于以上问题,我们开始了 vbuilder 的研发。

最终产品以命令行的形式发布。

此时的 vbuilder 为 V0.0 状态。

V1.0

vbuilder V1.0 提供了以下能力:

  • 默认命令集 :内置一套命令集,用于常见功能开发,包括  mock / update / help

  • 静默更新 :用户安装一次命令即无需关注更新,其更新自行静默完成

  • 收敛脚手架 :将工程内的脚手架配置隐藏,并统一管理,开发者可快速聚焦业务逻辑

  • 开放接入脚手架 :不限制技术栈类型( vue / react / angular / weex 等),开放接入不同技术栈

  • 插件化 :除内置命令集外,插件化扩展命令集,供团队同学实现订制逻辑

vbuilder 的不断推进下,我们欣喜地看到,团队发生了一些变化:

  • 便捷 :新项目一个命令即创建,直接开始业务开发

  • 纯粹 :工程目录只保留了业务文件,脚手架等工程配置被隐藏

  • 更新 :脚手架被收敛为统一管理,统一更新,尽可能应用最新的技术栈

  • 协作 :绝大部分项目协作的成本范围收敛到 “业务逻辑”,剔除了 “工程配置逻辑”,协作成本大大降低

  • 开放 :在收敛脚手架配置的同时,开放性接入各类技术栈脚手架,如  weex / vms / 后台管理 / serverside project

  • 协作 :团队统一性的技术更新得以快速进行,不会再遇到因工程配置不同不断适配的问题

总结为下图:

V1.0 出现后,推进的很顺利,在推进过程中秉持如下原则:

  • 提效:帮助业务开发者节省时间

  • 共担:开发者参与生态建设(脚手架开发维护、插件开发),至少在绩效上会得以加分

  • 好用:使用方式简单好用,才让人有用的欲望

V1.0 基本解决了以下角色的痛点:

  • 后端同学:内部系统开发场景被 100% 覆盖

  • 前端同学:绝大部分业务场景被覆盖

  • 脚手架开发者:强大的脚手架被开发好后,得以快速推广

  • 插件开发者:自定义命令,满足个性化开发

V1.0 的问题

  • 封闭性

    高度定制化的工程配置需求实现难度增大

    脚手架配置的主题被隐藏,虽然仍然开放给开发者一些配置性文件,对于高度定制化的配置需求而言依然杯水车薪。

    此时,就必须新开一个脚手架,重新接入 vbuilder 体系。

    在 “开放性” 来说,打了折扣。

  • 插件开发的冲突

    由于 vbuilder 是基于命令行开发,插件开发者扩展自定义命令式,依然是自定义命令行,团队规模不断扩大的状态下,很容易出现不同插件使用同一个命令,被同时安装的状态下,重复执行该命令。

V2.0

V2.0 至少要解决 V1.0 存在的问题,同时需要有更明确的发展方向。

V2.0 依然基于命令行。

V2.0 如何解决封闭性问题

V1.0 的思路是 “闭合”,虽然有一定的开放性,但仍然不够。

V2.0 新增 “开放” 的能力,脚手架配置可以被隐藏,也可以 随时在需要的时候暴露在工程配置中 ,进行定制化开发。

当然,会遇到脚手架难以统一管理的问题,这一点仍然有办法可以解决。

因为被暴露的工程配置是 vbuilder 提供的,vbuilder 得以方便地统计哪些项目使用了自定义的脚手架,将通用型工具包下发给该工程。

V2.0 如何解决插件开发的冲突

  • 问题 1:插件间的冲突

    举个例子,有两个插件中,都有一个命令 run 。如果用户安装了这两个插件,在执行  run 命令的时候,两个插件的逻辑均会触发。

    在某些情况下,这不是用户希望看到的场景,可能 TA 希望的只是运行插件 A 的命令 run

  • 问题 2:插件命令集与内置命令的冲突

    例如,内置命令集中有命令 init ,而某个插件也有  init

    那么在用户执行 init 命令时,依然会执行两遍逻辑。

  • 怎样解决?

    我们组合使用了以下方案:

    • vbuilder 检测是否有重复命令,如有,提示用户是都运行、还是选择运行某一个插件中的命令

    • 为命令圈定生效条件

      vbuilder 的命令行基于 commander 。我们基于  commander 扩展了一些方法。

      假如我们希望,插件中的命令 show 只在工程目录中  xx.show 文件存在的情况下生效,那么代码如下:

      commander
      .command('show [param]')
      .effect(cwd  => fs.existsSync(path.join(cwd, 'xx.show'))) ---- 这是我们扩展的命令
      .description('我的自定义命令')
      .action((param, options) => {
        console.log('my show');
      });
    • 为内置命令集声明其为“内置命令”,插件命令可以阻止内置命令执行

      假如插件中有个命令 init ,而 vbuilder 内置命令中也有  init ,我们希望插件中的  init 命令生效,内置命令不生效,该怎么做呢?

      我们扩展了 commander 的 2 个方法: declareDefault 声明内置命令、 preventDefault 阻止内置命令执行。

      定义内置命令时,代码如下:

      commander
      .command('init [param]')
      .declareDefault() --- 声明内置命令
      .description('内置的 init 命令')
      .action((param, options) => {
        console.log('init inside');
      });

      开发插件命令时,代码如下:

      commander
      .command('init [param]')
      .preventDefault() --- 阻止内置命令执行
      .description('内置的 init 命令')
      .action((param, options) => {
        console.log('init inside');
      });

      Commander 的源码只有 1000 行左右,逻辑还是很清晰的,扩展起来非常方便,这里不再列举实现。

V2.0 的新功能

在命令行这个场景下,我们把 vbuilder 定义为公司内部开发的一个“水电煤”性质的基础设施。

通过 vbuilder,我们新增了以下场景:

  • 支持 chrome 插件 es6/7 化开发

  • 支持组件库快速开发

  • 支持 js 工具库快速开发

  • 支持快速打开文档库等等

得益于插件化,通过充分调动开发者积极性,我们可以将其能力无限延展。

V3.0

我们目前还没有进入 3.0 的开发,但有一些方向是我们可以尝试的:

  • cloudIDE(内部已有该类平台)

  • vscode 定制化 IDE,该类场景在超大型团队比较适合,IDE 定制化开发有更多的应用场景,更快的开发效率

  • 云化(其实不算新了,很多公司已经实现了云化)

这是目前我们在微店前端工程化领域的一些实践和思考,希望对大家有帮助。

I n