使用插件

使用场景

插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。

我们在使用 Koa 中间件过程中发现了下面一些问题:

  1. 中间件是有先后顺序的,需要统一管控,但是它自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。
  2. 中间件的定位是拦截用户请求,并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,有些功能是和请求无关的,例如:定时任务、消息订阅、后台逻辑等等。
  3. 一些复杂的初始化逻辑,需要在应用启动的时候完成,这显然也不适合放到中间件中去实现。

综上所述,我们需要一套更加强大的机制,来管理、编排那些相对独立的业务逻辑。

使用插件

举个例子,我们想引入 egg-validate 这个插件。

安装依赖

插件一般通过 npm 模块的方式进行复用:

$ npm install egg-validate --save

注意:我们建议通过 ^ 的方式引入依赖,并且强烈不建议锁定版本。

友情提示

有些插件是内置到框架中,但默认不开启的,此时无需手动安装依赖。详见下文。

挂载插件

config/plugin.js 中声明:

// config/plugin.js
exports.validate = {
  enable: true,
  package: 'egg-validate',
};

使用插件

然后就可以使用插件提供的功能:

// app/controller/user.js
class UserController extends Controller {
  async create() {
    const rule = { name: 'string' };
    ctx.validate(rule, ctx.request.body);

    // ...
  }
}

了解插件

一个插件其实就是一个『迷你的应用』,和应用几乎一模一样

目录结构

my-plugin
├── app
│   ├── service|   └── user.js
│   ├── middleware
│   |   └── response_time.js
│   └── extend
│       ├── application.js
│       ├── context.js
│       └── helper.js
├── config
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.local.js
|   └── config.unittest.js
├── test
|   └── service
|       └── user.test.js
└── package.json

Service

插件可以包含 Service,框架会自动挂载。

Config

插件可以包含 配置

插件一般会包含自己的默认配置,应用开发者可以自由覆盖对应的配置:

譬如 egg-static 插件默认的 prefix/public/

你可以在应用的配置里面覆盖掉它:

// config/config.default.js
config.static = {
  prefix: '/static/',
};

具体合并规则可以参见配置

Middleware

插件可以包含 中间件

框架把插件的 app/middleware 目录下的文件,同样加载到 app.middleware 上。

大部分情况下,插件开发者会自动挂载中间件到对应的地方,无需应用开发者处理。

但某些情况下,插件仅提供了中间件定义,并不帮应用开发者决定挂载顺序。

此时,应用开发者只需遵循 中间件 文档来使用即可。

Extend

插件可以提供 ContextApplicationHelper 等的扩展。

譬如在插件里面提供以下扩展,对应的逻辑就可以共享给其他应用。

// {plugin_root}/app/extend/context.js
const UA = Symbol('Context#ua');
const useragent = require('useragent');

module.exports = {
  get ua() {
    if (!this[UA]) {
      // this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性
      const uaString = this.get('user-agent');
      this[UA] = useragent.parse(uaString);
    }
    return this[UA];
  },
};

不支持的特性

  • 没有 RouterController
  • 没有 plugin.js,只能声明跟其他插件的依赖,而不能决定其他插件的开启与否。

插件配置

参数介绍

应用开发者通过 config/plugin.js 来声明插件的挂载。

除了上面我们使用到的 enablepackage 外,其他参数如下:

  • enable - 是否开启此插件,默认为 true
  • package - npm 模块名称,通过 npm 模块形式引入插件。
  • path - 插件绝对路径,跟 package 配置互斥。
  • env - 数组,仅在指定运行环境才开启,会覆盖插件自身 package.json 中的配置。

插件本身的 package.json 里面也会有一个 eggPlugin 属性来声明默认的属性。

开启框架内置插件

框架一般也会内置一些插件,它们有可能默认是开启或关闭的。

此时,应用无需配置 package,直接配置 enable 即可:

// config/plugin.js
exports.cors = {
  enable: true;
};

// 也可以简写为:
exports.validate = true;

packagepath

  • package:通过 npm 方式引入,也是最常见的引入方式。
  • path:通过绝对路径引入。
  • 后者主要场景是:应用内部抽象了一个插件,但还没达到可以发布独立插件的阶段临时使用。
  • 关于这两种方式的使用场景,可以参见渐进式开发
// config/plugin.js
const path = require('path');
exports.mysql = {
  enable: true,
  path: path.join(__dirname, '../lib/plugin/egg-mysql'),
};

根据环境配置

同时,我们还支持 plugin.{env}.js 这种模式,会根据运行环境加载插件配置。

比如定义了一个开发环境使用的插件 egg-dev,只希望在本地环境加载,可以安装到 devDependencies

譬如 egg-development-proxyagent 这个插件,只会在开发环境使用。

则我们可以只安装到 devDependencies

$ npm i egg-dev --save-dev

然后在 plugin.local.js 中声明:

// config/plugin.local.js
exports.proxyagent = {
  enable: true,
  package: 'egg-development-proxyagent',
};

这样在生产环境可以 npm i --production 不需要下载 egg-development-proxyagent 的包了。

注意:

  • 不存在 plugin.default.js
  • 只能在应用层使用,在框架层请勿使用。

常见问题

如何开发一个插件

恭喜你迈出这一步,可以回馈社区。

具体可以参见文档:

插件太多,每个应用都要开启怎么办?

此时应该考虑包装为一个上层框架