8

如何为团队量身定制 EggJS 目录挂载规范?

 3 years ago
source link: https://zhuanlan.zhihu.com/p/153322661
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何为团队量身定制 EggJS 目录挂载规范?

活多人少有挑战,撸起袖子拼命干。

背景

我们知道 Egg 内置了 Controller/Service 等目录规范,会自动挂载到对应的 ctx 上。

而在实际业务开发中,往往需要为我们的团队定制一些新规范,如何实现呢?

Egg 的定位是框架的框架,因此这类的能力是天然具备的,开发者只需要简单配置下即可。

下面我们来一起定制以下规范:

demo
└── app
    ├── enum
    |   ├── error.js
    |   └── status.js
    ├── utils
    |   └── formatter.js
    └── rpc
        └── user.js

app.enum

全局静态枚举,约定:app/enum/** 的文件都被挂载到 app.enum

只需要一个简单的配置:

// config/config.default.js
exports.customLoader = {
  enum: {
    directory: 'app/enum',
  },
};

然后编写 app/enum/error.js 来提供对应的定义:

// app/enum/error.js
exports.ERR_AUTH = {
  code: '403',
  msg: 'not perm',
};

exports.ERR_NOTFOUND = {
  code: '404',
  msg: 'not found',
};

就可以直接在业务代码中使用了:

// app/controller/home.js
class HomeController extends Controller {
  async index() {
    console.log(this.app.enum.error.ERR_AUTH);
  }
}

app.utils

经常有同学反馈,内置的 Helper 太简单的,只支持单文件。实际上 Helper 的定位是给模板渲染用的,如果大家有一些公共方法,可以自定义挂载到 app.utils 上。

配置类似上面的方式,此处演示下如何获取 app 对象:

// config/config.default.js
exports.customLoader = {
  utils: {
    directory: 'app/utils',
    inject: 'app',
  },
};
// app/utils/formatter.js
module.exports = class Formatter {
  constructor(app) {
    this.app = app;
    this.config = app.config;
    this.logger = app.logger;
  }

  // 会被挂载为 `app.utils.formatter.random()`
  random(max) {
    return Math.floor(Math.random() * Math.floor(max));
  }
};

当然,你也可以简化为:

module.exports = app => {
 return {
   random() {},
 };
};

ctx.rpc

内置的Service 一般用来作为业务逻辑层,但有些时候,我们也需要独立出一层,如把跟后端相关的逻辑,都封装为 rpc 的概念。

app/rpc/** 的文件都被挂载到 ctx.rpc

一样只需要简单配置:

// config/config.default.js
exports.customLoader = {
  rpc: {
    directory: 'app/rpc',
    inject: 'ctx',
  },
  // ...
};

然后编写 app/rpc/test.js 来提供对应的定义:

// app/rpc/test.js
module.exports = class TestRpc {
  constructor(ctx) {
    this.ctx = ctx;
    this.config = ctx.app.config;
    this.logger = ctx.logger;
  }

  async sayHi(name) {
    console.log(this.ctx.app.config.env);
    return `hi, ${name}`;
  }
};

就可以直接在业务代码中使用了:

// app/controller/home.js
class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = await ctx.rpc.test.sayHi('egg');
  }
}

生成声明

由于 Egg 是动态挂载的,如需 TS 和智能提示支持,需要通过我们的 ets 来自动生成映射。

简单配置下 package.json,重启即可自动生成对应的 typings。

{
  "name: "egg-showcase",
  "egg": {
    "declarations": true,
    "tsHelper": {
      "watchDirs": {
        "enum": {
          "enabled": true,
          "directory": "app/enum",
          "declareTo": "Application.enum"
        },
        "utils": {
          "enabled": true,
          "directory": "app/utils",
          "declareTo": "Application.utils"
        },
        "rpc": {
          "enabled": true,
          "directory": "app/rpc",
          "declareTo": "Context.rpc"
        }
      }
    }
  },
}

享受智能提示吧:




规范化 && 示例

上面的演示,都是在应用里面配置,但往往我们需要的是制定团队规范,因此推荐把上述配置封装到插件,或者上层框架里,这样可以统一管控和升级。

除此之外,如果我们期望想 Egg 内置规范那样,希望在插件里面定义的 rpc 文件也能被自动加载,怎么办呢?

很简单,只需要多配置下 loadunit: true 即可。

// config/config.default.js
exports.customLoader = {
  rpc: {
    directory: 'app/rpc',
    inject: 'ctx',
    loadunit: true,
  },
  // ...
};

最后,别忘了写单测哈。

完整的示例代码,可以参见:https://github.com/atian25/egg-showcase/pull/13/files


内部原理

上述的配置,其实就是调用了 Egg 内置的 app.loader API 即可,等价于:

// some_plugin/app.js
module.exports = class AppLifeCycle {
  constructor(app) {
    this.app = app;
  }

  configDidLoad() {
    // 所有的配置已经加载完毕,可以用来加载应用自定义的文件,初始化自定义的服务

    // 只加载应用目录
    const enumPaths = path.join(this.app.config.baseDir, 'app/enum');
    this.app.loader.loadToApp(enumPaths, 'enum');

    // 加载所有的 loadunit
    const rpcPaths = this.app.loader.getLoadUnits().map(unit => path.join(unit.path, 'app/rpc'));
    this.app.loader.loadToContext(rpcPaths, 'rpc');
  }
};

Egg 进一步封装了这个能力,使得开发者只需要简单的一个配置即可加载。

有兴趣的也可以看下 egg-core 这个库的源码。


写在最后

从本文中,读者可以学习到如何在 Egg 上定制适合自己团队的目录规范。这也是 Egg 设计理念很重要的一点,它本身的定位就是框架的框架。我们内部的很多插件,都是通过这个方式来实现标准化的。

不过,在 2020 这个时间点,框架其实只是整个研发解决方案里面的一个单点,我们还需要打通上下游的 PaaS、云服务、部署平台等等深水区的事,如果你对此感兴趣的话,欢迎加入我们。


我们隶属于玉伯的蚂蚁体验技术部。

目前正致力于 为蚂蚁提供 轻研发、免运维 的下一代 Node.js 研发方案。

目前团队活多人少有挑战,撸起袖子拼命干,Base 地可以选:广州、杭州、上海

欢迎加微信沟通


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK