浅析阿里云前端构建工具——Dawn

2,269 阅读2分钟

Dawn 取「黎明、破晓」之意,原为「阿里云·业务运营团队」内部的前端构建和工程化工具,现已完全开源。它通过 pipelinemiddleware 将开发过程抽象为相对固定的阶段和有限的操作,简化并统一了开发人员的日常构建与开发相关的工作。

官方文档

入口

开发过程中,经常会用到如下一些命令,dn也可以替换为dawn

dn init
dn dev
dn build

dawn项目的package.json中,可以看到如下一些配置信息。属性bin则是执行命令的入口。

{
  "name": "dawn",
  "version": "1.9.0",
  "description": "dawn cli",
  "main": "./lib/index.js",
  "bin": {
    "dn": "./bin/cli.js",
    "dawn": "./bin/cli.js"
  }
 }

cli和cli-core

cli.js中执行require('./cli-core').ready();cli-core.js中,使用cmdline库将涉及的命令和执行动作action进行初始化配置。当触发某个命令,对应的action回调函数执行,就会初始化Context,调用context.run()方法执行后续一系列任务。

cmdline
.root.command([
    'dev', 'add', 'test', 'build', 'publish', 'start', 'run',
    'd', 'a', 't', 'b', 'p', 's', 'r'
  ])// 配置与action有关的命令
  .option(['-e', '--env'], 'string') // 命令后面的可选项
  .action(async function (cmd, env, $1) {
    if (cmd == 'r' || cmd == 'run') {
      cmd = $1 || 'dev';
    }
    cmd = ALIAS[cmd] || cmd;
    this.set('command', cmd);
    process.env.DN_CMD = cmd || '';
    process.env.DN_ENV = env || '';
    process.env.NODE_ENV = env || process.env.NODE_ENV || '';
    try {
      let context = new Context(this, { cmd, env });
      await context.run();
      cmdline.onDone(context);
    } catch (err) {
      cmdline.onFail(err);
    }
  }, false)

Context

  1. 负责实例化一个全局对象,配置相关的属性信息。
  2. 安装依赖包,_installProjectDeps
  3. 检测.dawn目录下配置文件,configIsExists
  4. 解析pipeline中命令对应的任务及任务参数,loadPipeline
  5. 按顺序调用中间件,_execQueue
  6. 加载中间件,load
安装依赖——_installProjectDeps

触发modexec模块当中的方法执行。

如果.dawn目录、node_modules目录和package.json文件都存在,则执行安装依赖操作:npm i --registry=https://registry.npmjs.com/

依次调用中间件——_execQueue

在中间件 middwares 队列中获取头元素,通过load 方法加载到对应的中间件函数 handler,调用 handler 方法,同时将 next 传递到此 handler 中。方便调用下一个中间件的调用。

  async _execQueue(middlewares, args, onFail) {
    const middleware = middlewares.shift();
    if (!middleware) return;
    // 加载中间件
    const handler = await this.load(middleware);、
    const next = (args) => {
      console.log(next.__result)
      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);
  }
加载中间件——load
  /**
   * opts就是loadPipeline解析出来的每个任务参数
   */
  async load(opts) {
    if (utils.isFunction(opts)) return opts;
    if (!opts || !opts.name) {
      throw new Error('Invalid pipeline config');
    }
    opts = this._parseOpts(opts);
    const modFactory = opts.location ?
      require(path.resolve(this.cwd, opts.location)) :
      await middleware.require(opts.name, this.cwd); // 核心代码
    if (!utils.isFunction(modFactory)) {
      throw new Error(`Invalid middleware '${opts.name}'`);
    }
    return modFactory.call(this, opts, this);
  }

加载中间件,会调用middleware模块的require方法,通过opts.name和中间件前缀dn-middleware拼接出中间件完整的name, 再拼接上node_modules,生成绝对路径,通过noderequire方法导入。最终导入的就是中间件的函数。赋值给modFactory调用;

中间件前缀则是在 package.jsonconfigs 中配置好的。

 {
  "configs": {
    "server": "https://alibaba.github.io/dawn",
    "registry": "https://registry.npm.taobao.org",
    "npm": "npm",
    "cache": 3600000,
    "middlewarePrefix": "dn-middleware",
    "templatePrefix": "dn-template"
  },
 }