前端构建工具 dawn 不完全解密

1 实现原理

dawn 是一个采用中间件技术实现的轻量的任务流协调器,它和 gulp, grunt 有异曲同工之妙。dawn 本身并不处理任务,转而交由中间件承担这一职能,如同 gulp, grunt 插件。在实现上,dawn 是 webpack 出台后的产物,就不需要像 gulp, grunt 那样关注任务流的始点 —— 文件位置,而更容易聚焦于任务的分解,将编译、压缩作业交给 dn-middleware-webpack 中间件。若说 gulp 中的任务流是鱼跃似的,一个跟着另一个,那么,dawn 通过 ctx 上下文使得两个任务之间可以借助事或实例属性进行通信。dawn 的中间件实现机制如同 koa,其一使用 next 引用下一个中间件串联任务流,其二以继承事件模型的 ctx 实例作为上下文。其核心代码如下:

class Context extends EventEmitter {
  async _execQueue(middlewares, args, onFail) {
    const middleware = middlewares.shift();
    if (!middleware) return;

    // this.load 安装中间件,并执行中间件的外层函数,获得实际的任务逻辑 handler
    const handler = await this.load(middleware);

    const next = (args) => {
      // 若返回真值,在 watch 状态下,也只执行一次
      if (next.__result) return next.__result;

      next.__result = this._execQueue(middlewares, args, onFail)
        .catch(err => onFail(err));
      return next.__result;
    };
    return handler.call(this, next, this, args);
  }
}

因为 dawn 是一个任务流协调器,使得它就像一架航母,其能力仰赖于战斗机群 —— 由丰富的中间件、模板构成的生态系统。第一,这样使得 dawn 更着眼于为开发团队提供服务(两者呈互为因果的关系)。dawn 内建了复合远程配置的功能。不少公司会有私有的 npm 仓库,开发的 dn 中间件、模板先期都会在这些私有仓库中发展成形。借助于 dn config registry your_server_url 命令,dawn 安装中间件时会从这些私有仓库中拉取中间件或模板。对于配置文件,借助于 dn config server your_server_url 命令,dawn 也会从私有服务器中拉取远程配置,并与本地配置合并。如此,既能保有模板的闭源特征,又能实现配置文件的复用。扯开一个话题,使用 .yml 文件配置选项、将配置文件存于远端,这和我稍有耳闻的 spring boot 项目有些相仿。

第二,为了开发中间件的方便,ctx 必然需要提供大而全的功能。以下简要地展示 ctx 提供的 api 列表:

  • cli: 命令行 Command 实例。
  • command: 当前执行的命令,如 init, dev, build 等。
  • pipeline: 当前命令实际所用中间件列表。
  • cwd: 项目路径。
  • project: 项目 package.json 文件内容。
  • middlewareMgr: 中间件管理器,用于查询私有中心的中间件列表,或者安装中间件。
  • templateMgr: 模板管理器,用于查询私有中心的模板列表,或者文件重命名,或者下载模板。
  • conf: rc配置管理器,用于获取本地或远程 rc 配置,或者设置 rc 配置,默认获取 .dawnrc 文件。
  • console: 命令行编辑器 console。
  • inquirer, utils.inquirer : 命令行交互接口,即 inquirer 类库。
  • utils.exec(script, opts): 执行 script 命令,返回 promise;utils.exec.withResult(script, opts) 等待并返回执行结果。
  • utils.writeFile(filename, content): 写文件,返回 promise。
  • utils.readFile(filename): 读文件,返回 promise。
  • utils.del: 删除文件,返回 promise。
  • utils.mkdirp: 创建目录,返回 promise。
  • download: fetch 响应。
  • sleep: 延时执行。
  • prompt: 使用 inquirer.prompt 在命令行编辑器创建引导式交互界面。
  • utils.mod: 模块管理器,用于执行 npm 命令,或者安装依赖,或者下载模板(缓存在计算机的特定位置),或者获取 npm 包信息。
  • utils.open: 打开浏览器,即 react-dev-utils/openBrowser 模块。
  • utils.oneport: 获取一个空闲的端口,即 oneport 类库。
  • utils.fetch: fetch 响应。
  • utils.yaml: 通过 js-yaml 类库解析或编译 .yml 文件格式数据。
  • utils.globby: 通过匹配规则获取文件路径,即 globby 类库
  • utils.confman: 配置文件加载器,即 confman 类库
  • utils.streamToBuffer, utils.stream2buffer: 将可读流转化成 字符串或 buffer,返回 promise。
  • utils.bufferToStream, utils.buffer2stream: 将字符串或 buffer 转化成可读流,返回 promise。
  • utils.copydir: 拷贝文件夹,返回 promise。
  • utils.findCommand(dirname, command): 获取执行脚本文件。
  • load(opts): 加载中间件,并执行外层函数。
  • exec(middlewares, initailArgs): 构建任务流,可用于在中间件中构建子任务流。

2 常用中间件

2.1 dn-middleware-webpack

概述:基于 webpack3 实现的中间件,打包模块。本地开发模式也将打包模块,而不是读取 webpack 缓存数据。

奥妙:

  1. dn-middleware-webpack 在回调中执行后续中间件的处理逻辑。
  2. 通过 vmodule-webpack-plugin 插件将 config.yml 类配置文件注入为可以 import 引入的虚拟模块。
  3. ctx 中添加 webpack 属性,即 webpack 类库。

选项:

babel 选项:

事件:

  • 'webpack.opts',可用于修改 opts 配置项,参数 opts。
  • 'webpack.config',可用于修改注入 webpack 的 config 配置,参数 config, webpack, opts。
  • 'webpack.compiler',操纵 webpack 的编译器,参数 compiler。
  • 'webpack.stats',可用于监控编译状态,参数 stats。

2.2 dn-middleware-server

概述:基于 nokit,启动本地服务。首次执行时将在项目空间创建 server.yml 配置文件。

奥妙:

  1. 在 nokit 服务器中设置拦截器,通过 httpProxy 转发请求。
  2. ctx 中添加 server 属性,即 nokit.Server 实例;以及 httpServer 属性,即 server.httpServer。

选项:

server.yml 中 proxy 支持配置选项:

事件:

  • 'server.init',服务未启动时事件,参数 server 实例。
  • 'server.start',服务启动成功时事件,参数 server 实例。

2.3 dn-middleware-dll

概述:独立构建项目依赖,节省打包时间。

奥妙:

  1. 借助 ctx.exec 方法执行 webpack 中间件,打包项目的依赖,默认存放在工程目录 .cache 文件夹内。子文件夹名基于项目所使用的依赖通过 md5 生成散列,以便在依赖更新时重新打包。再借助 ctx.exec 方法执行 copy 中间件,将打包文件拷贝到 build/js 文件夹内。
  2. 通过 'webpack.config' 事件,在 webpackConfig 插件中注入 webpack.DllReferencePlugin 插件。

选项:

2.4 dn-middleware-faked

概述:基于 faked 提供数据模拟服务。

奥妙:

  1. 基于 faked 创建 gui server 服务器,配置的模拟数据将输出到工程目录 mock 文件夹中 index.js, gui.data.json。
  2. 模拟数据文件最终将作为 webpack 入口文件,以此实现远程请求的拦截,并实现热更新。
  3. 执行逻辑被封装为 ctx.faked.apply 方法,在 dn-middleware-webpack 中执行,两个中间件耦合度较高。

选项:

2.5 dn-middleware-i18n

概述:将工程目录中 locales 语言包加载为 $locales 模块,通过 $i18n 获取指定模块,实现国际化。

奥妙:

  1. 使用 confman.webpackPlugin 方法将工程目录中的语言包输出为 $locales 虚拟模块。
  2. 使用 vmodule-webpack-plugin 类库输出 $i18n 虚拟模块,以获取指定文案。

选项:

2.6 其他

  • dn-middleware-clean,清理文件或目录,可用 opts.target 加以配置,默认清理 './build//.'。
  • dn-middleware-copy,复制文件。选项 from 查询源文件的文件夹路径,默认 './from'; to 目标文件夹路径; log 是否打印日志; dot 源文件匹配规则是否支持 '.' 起始; direction 影响映射 key 键指代源文件还是目标文件; files 源文件和目标文件映射,目标文件路径支持占位符 {index} 替换(index 自右而左),或使用源文件路径(映射中,目标文件以 '/' 结尾)。
  • dn-middleware-browser-sync 基于 'browser-sync' 监听打包文件变更,借助 'connect-browser-sync' express中间件实现热更新。选项 files 配置监听的文件,默认 ['./build//.'];port 为 'browser-sync' 服务启动端口,默认 5001。
  • dn-middleware-git-sync,git 操作,包含 commit, push 动作(push 又区分日常和预发环境)。
  • dn-middleware-jcs,在 babel-loader 中添加 'jsx-control-statements' 插件,以使 jsx 可使用结构控制语句,同时 lint 阶段也会作代码检查,参考 通过 JSX Control Statements 编写 JSX
  • dn-middleware-lint,使用 eslint 命令作语法检查。
  • dn-middleware-tslint,对 typescript 进行语法检查。
  • dn-middleware-typedoc,使用 typedoc 为 typescript 项目生成文档。
  • dn-middleware-typescript,在 webpackConfig 中添加 awesome-typescript-loader,支持编译 tsx 模块。选项 declaration 是否分离 ts, js 文件,默认分离,false 时不分离。
  • dn-middleware-pkginfo,更新项目 package.json 中的 name, version, description 信息。
  • dn-middleware-prepush,在 .git/hooks/pre-push 添加 shell 命令,推送前执行 dn build 命令。
  • dn-middleware-sensitive-path,在 webpackConfig 中添加 'case-sensitive-paths-webpack-plugin' 插件,使 mac, windows 引入模块时严格区分模块的大小写。
  • dn-middleware-shell,调用 ctx.utils.exec 执行 shell 命令。选项 script 命令内容;wscript 为 windows 系统下命令内容;async 是否异步执行,默认同步。
  • dn-middleware-watch,基于 'chokidar' 监听执行文件变更。选项 match 匹配的文件,默认为 './src//.';event 监听的事件类型;script 事件发生后执行的脚本;onChange 事件发生后执行的动作。
  • dn-middleware-unit,基于 'mocha' 对 ./test/unit 文件夹中内容作单元测试。
编辑于 2018-09-16

文章被以下专栏收录