Skip to content

Addon Development Guide

Daniel Yin edited this page Mar 14, 2018 · 16 revisions

ibird的设计中,框架内核采用轻量化设计,只负责相对基础的Web功能和提供插件整合能力,扩展功能皆通过插件来实现。

插件开发

ibird的插件叫addon,原因是想将插件定位为是框架核心的扩展功能或附加程序,插件在设计上并不支持单独运行。

入口文件

插件模块本质上是一个Node项目或文件,它的入口文件必须导出一个对象,其中支持的参数情况如下所示:

  • namespace - 插件命名空间,必填参数,字符串类型,一般直接为项目名或者npm包名,需保证全局唯一。
  • onload - 加载回调函数,该函数接收两个参数:应用实例app和引用选项options,其中options由用户在调用app.import时指定。
  • locales - 插件国际化配置,对象结构,key为语言标记,value为语言配置内容,该配置会在插件被引用时合并到应用实例的全局国际化配置中。
  • api - 需要挂载的模块API,对象结构,该参数指定的所有API都会被挂载到项目实例上。
  • routes - 需要挂载的路由,对象结构。
  • middleware - 需要挂载的中间件,对象结构。
  • onplay - 启动回调函数,该函数接收一个应用实例的参数,在应用app.play时触发调用。

应用配置与引用选项

  1. 在插件的onload函数中,第一个参数是应用实例app,第二个参数是引用选项options(引用选项为对象类型);
  2. 插件内部如果需要获取外部参数,那么其实通过应用配置和引用选项都能满足要求;但一般应用在不同的线上环境运行时,会采用不同的应用配置;
  3. 如果你希望外部参数会随着线上环境变化而变化的话,建议设计成从应用配置中读取,反之从引用选项中读取;
  4. 但不论采用哪种方式获取参数,都需要在插件文档中对支持的参数进行说明并告知用户正确的配置方式,即是需要设置到应用配置中还是在import时指定。

读取应用配置

获取方式:app.c(),示例如下:

// myAddon.js
module.exports = {
    namespace: 'myAddon',
    onload: function(app, options){
        console.log(app.name); // output 'myApp'
        console.log(app.mongo); // output '127.0.0.1:27017/myApp'
    }
}


// index.js
const myAddon = require('./myAddon.js');
const app = require('ibird').newApp({
    name: 'myApp',
    mongo: '127.0.0.1:27017/myApp'
});
app.import(myAddon)
app.play();

读取引用选项

获取方式:options,示例如下:

// myAddon.js
module.exports = {
    namespace: 'myAddon',
    onload: function(app, options){
        console.log(app.name); // output 'myApp'
        console.log(options.p1); // output 'hello'
    }
}


// index.js
const myAddon = require('./myAddon.js');
const app = require('ibird').newApp({
    name: ' myApp'
});
app.import(myAddon, { p1: 'hello' })
app.play();

API调用

插件内可调用其他插件或者应用提供的API,如国际化API和日志API

// 国际化API:
app.getLocaleString(key, params, localeOrName);
// 示例:
app.getLocaleString('create_error', { model: '用户' }, 'zh_CN')
app.getLocaleString('remove_error', null, 'zh_CN')

// 国际化API(getLocaleString)还提供了更为简单的别名函数:app.L = app.locale = app.getLocaleString


// 日志API:
app[.level](msg); // level可选值为:info、error、warn、verbose、debug、silly
// 示例:
app.info('普通日志输出');
app.error('异常日志输出');

详见国际化插件日志插件的文档说明。

注意:国际化API和日志API比较特殊,框架核心已做了特殊处理,不管用户是否引用了国际化插件或者日志插件,都可以正常使用以上API

事件支持

插件既能订阅框架内核发布的事件或者其他插件发布的事件,也能发布自己的事件来供其他模块调用。

订阅事件

通过app.on实现事件订阅,例如:

// 内核事件:应用初始化前
app.on('ibird:app:initialize:pre', (app, opts) => {
    console.log(app.name);
});

// 内核事件:应用监听
app.on('ibird:app:listen', (app) => {
    console.log(`Listen and serve on 0.0.0.0:${app.c().port}`);
});

// 内核事件:应用启动后
app.on('ibird:app:play:post', (app) => {
    console.log(`应用 ${app.name} 启动成功!`);
});

// 插件事件:模型挂载后
app.on('ibird:mongoose:model:post', (app, obj) => {
    console.log(`模型 ${obj.name} 挂载成功!`);
});

框架内核所发布的事件详见相关事件说明文档;插件所发布的事件见对应的插件文档。

发布事件

发布事件的函数为app.emit,为了规范事件命名,我们统一约定事件名的命名规则为:命名空间:xxx[:xxx][:xxx],例如:

app.emit(`${namespace}:model:pre`, obj);
app.emit(`${namespace}:model:post`, obj);
app.emit(`${namespace}:modelDir`);

插件引用

引用插件的方式很简单,首先导入插件模块,然后通过调用项目实例的import函数即可引用插件,例如:

const loggerAddon = require('ibird-logger');
const app = require('ibird').newApp();
app.import(loggerAddon, { dir: __dirname + '/logs' })

上述例子中,dir参数所在的对象即为传递个插件onload函数的第二个参数。该对象拥有几个默认参数:

  • autoMountRoutes - 是否自动挂载插件路由,布尔类型,默认为true
  • autoUseMiddleware - 是否自动挂载插件中间件,布尔类型,默认为true
  • apiAlias - 插件的api别名设置,对象结构。因为所有插件导出的api都会被挂载到应用实例上,那么当引用插件变多时,其实不同插件中是有可能会出现同名api声明的情况,那么这时候,后加载的插件会覆盖掉先加载插件的同名api,这时候通过指定api别名即可改变插件原有的api声明,进一步避免同名api被覆盖的情况。

别名设置如下所示:

app.import(loggerAddon, { 
    apiAlias: {
        abc: 'def',
        ping: 'pong'
    }
})

如果插件之间存在相互引用的关系,那么需要注意插件引用顺序。

再啰嗦一下

为了便于插件的使用,开发时需要有一些非强制性约定:

  1. 插件内部应该直接使用日志API来输出日志信息,这样做的好处是不论用户是否使用的了日志插件,插件本身都可以完美适配。
  2. 插件内部应该直接使用国际化API,原因同上。
  3. 会发布到npm的插件,插件命名空间应直接采用npm包名。
  4. 为独立项目的插件,插件命名空间应直接采用项目名。
  5. 应用内部使用的插件,需保证所有引用插件的命名空间不重复。

命令行工具

只生成单文件:

$ ibird addon
? your namespace:  myaddon
? a short description:  template addon for ibird.
? your name:  yinfxs
? code repository:  https://github.com/yinfxs/myaddon
? is it a package? :  false
Success to create file: /home/yinfxs/myaddon.js

同时生成package.json

$ ibird addon
? your namespace:  myaddon
? a short description:  template addon for ibird.
? your name:  yinfxs
? code repository:  https://github.com/yinfxs/myaddon
? is it a package? :  true
Success to create file: /home/yinfxs/myaddon.js
Success to create file: /home/yinfxs/package.json