用 gulp 提高你的开发效率

728 阅读5分钟

砖业团队@blueju

背景

注解:

① ngfed:我们内部基于 antd 二次封装的一个 react ui 组件库

我最近在基于阿里低代码引擎开发 ngfed 低代码物料①,官方提供了一个 lowcode-demo 的 github 仓库,我正是参考此仓库进行的开发,此仓库示例使用一个名称叫 assets.json 的文件对物料进行描述,主要包含三块,分别是:packages、components、componentList,不过本篇文章我们只关注 components 和 componentList。

其中 componentList 描述的是物料列表,它决定了左侧物料栏上显示哪些物料可用等;components 描述的是各物料的具体配置;所以我们如果需要添加物料,一般的流程是先在 componentList 添加物料使其可在左侧物料栏上可见,然后再去 components 中完善要添加物料的具体配置描述。

看起来就三块并不多,但实际开始时会发现,一个 assets.json 文件足足有两万三千多行,这里就衍生出一个问题:每当我添加或修改一个物料时,我就需要在一个两万三千多行的文件里用鼠标滚轮或鼠标拖拽频繁的滚上滚下。 😰😱

如果能做成:新增或更新组件物料时,我只需要找到一个行数不多的且与组件同名的物料文件并修改,修改完毕后自动更新到 assets.json 中,就好了。

收获

基于此你可以了解或延伸了解到以下内容:

  1. gulp 的使用场景
  2. gulp 的使用方法和流程(但不包含原理)
  3. 如何使用 gulp 插件
  4. 如何写一个简单的 gulp 插件
  5. node 的 require 及缓存

想法

经过对 asse.json 的观察,就属 componentList、components 所占行数最多,因此我设想是否可将 componentList、components 中的各组件物料拆分管理,以下是具体想法:

  1. 将 json 文件改为 ts 文件,调试时注释某些物料不影响代码运行
  2. 使用 ts 文件,方便后续可通过提供 interface 接口的方式智能提示属性,提高开发效率
  3. 将 assets 内的 components 和 componentList 拆分出去
  4. components 再次拆分出去,每个组件物料一个 ts 文件,每当启动和更新时编译生成 components.json 供 assets 调用

方案

感觉也谈不上方案

首先明确目标:将拆分散在 components 目录下各组件物料的具体配置描述整合成一个 components.json

大概要思考以下几点:

  1. 使用什么技术栈进行各组件物料配置描述的整合?
  2. ts 如何转为 js?
  3. js 如何转为 json?
  4. 如何在启动和更新时监听各组件物料配置描述文件?

1、使用什么技术栈进行各组件物料配置描述的整合?
答:

  • rollup 主要用于 js 库的打包,不适用;
  • webpack 可用,大致思路是在 entry 处将入口范围圈定在存放各组件物料配置描述文件的 components 目录,在 loader 中将 ts 转为 js,在 plugins 中将 js 转为 json,不太清楚的是如何将 json 转为文件流输出已文件的形式保存;
  • gulp 基于任务流,像记录步骤一样,不过具体如何使用不太清楚,但可借鉴参考 antd、ahooks 等开源组件库,而且我们前端组件 ngfed 中也用到了 gulp 可借鉴参考;

最终,鉴于 gulp 基于任务流(类似面向过程编程),也比较清晰简洁,且较多开源库都用到了,抱着学习的态度,最终决定用 gulp 实现。

2、ts 如何转为 js?
答:
如果只是单纯的使用命令的方式将 ts 转为 js,可直接全局安装 typescript,并用 tsc 命令将 ts 文件转为 js。
幸运的是,在 gulp 中有 gulp-typescript 这么一个插件可以将 ts 文件转为 js 文件,很简单。

后续: 在这块反而是花时间最多的,原因在于未配置 skipLibCheck 这个属性,导致 ts 转义 js 过程中 ts 为了完整的类型检查而深入到依赖内部导致报错,而由于我并未深入 typescript 导致在此我花费了很多时间。 最终的错误原因仍不得而知,由于我只是期望通过 ts 为一个导出对象赋予一个 interface 以让 vscode 等 ide 提供智能提示,不需要很完整的类型,所以此错误我忽略了。

3、js 如何转为 json?
答:
这一块也是比较关键的,我期望直接在 gulp 将文件流转为 json 并研究了一波(说到底就是将文件流中的 content 从 string 转为 json,并将 json 合并起来)。如果你没有接触过 node 文件流你可能会一脸懵逼,但你只需要清楚,(据我目前所知)我不会/做不到/代价较大😂。

所以我最后选择的方案是:
① ts 转为 js 后直接输出
② 使用 require 动态导入 js 文件,用一个变量接收 js 文件中暴露的变量
③ 将各个 js 文件导出的变量合并到一个数组里,并转为 buffer 文件流,通过 gulp 输出成 json 文件

4、如何在启动和更新时监听各组件物料配置描述文件?

这一块也是我花了较多时间的,也让我明白 gulp.series 它终究只是整合任务流并返回一个函数而已,要执行得自己在最后面加 ()

答:
很幸运,gulp 提供了 watch 功能。

代码

gulpfile.js

const del = require('del');
const gulp = require('gulp');
const ts = require('gulp-typescript');

const selfJs2Json = require('./gulp-js-to-json');

const ts2Js = () => {
  return gulp
    .src('./src/pages/BasicFusion/components/Fc*.ts')
    .pipe(
      ts({
        target: 'es6',
        skipLibCheck: true,
      }),
    )
    .pipe(gulp.dest('./src/pages/BasicFusion/components'));
};
exports.ts2Js = ts2Js;

const js2Json = () => {
  return gulp
    .src('./src/pages/BasicFusion/components/Fc*.js')
    .pipe(selfJs2Json())
    .pipe(gulp.dest('./src/pages/BasicFusion'));
};

const clearJs = (done) => {
  del(['./src/pages/BasicFusion/components/Fc*.js']);
  done();
};
exports.clearJs = clearJs;

exports.default = gulp.series([ts2Js, js2Json, clearJs]);

const watchComponents = () => {
  const watcher = gulp.watch('./src/pages/BasicFusion/components/*.ts');
  watcher.on('change', () => {
    // 必须加最后的()
    gulp.series([ts2Js, js2Json, clearJs])();
  });
};

exports.watchCps = watchComponents;

gulp-js-to-json.js

const path = require('path');
const through2 = require('through2');

module.exports = function jsToJson() {
  let json = [];
  let file = null;

  function jsToJson(chunk, encoding, cb) {
    // 固定套路
    if (chunk.isNull()) {
      cb();
      return;
    }
    //固定套路
    if (chunk.isStream()) {
      this.emit('error', new Error('glup-js-to-json: Streaming 不支持'));
      cb();
      return;
    }
    if (!file) {
      file = chunk;
    }
    // 清除 require 模块加载缓存
    delete require.cache[chunk.path];
    json.push(require(chunk.path));
    cb();
  }

  function endStream(cb) {
    file.contents = new Buffer.from(JSON.stringify(json, null, 2));
    file.path = path.join(file.base, 'components.json');
    this.push(file);
    cb();
  }

  return through2.obj(jsToJson, endStream);
};