-
Notifications
You must be signed in to change notification settings - Fork 8
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
时触发调用。
- 在插件的
onload
函数中,第一个参数是应用实例app
,第二个参数是引用选项options
(引用选项为对象类型); - 插件内部如果需要获取外部参数,那么其实通过应用配置和引用选项都能满足要求;但一般应用在不同的线上环境运行时,会采用不同的应用配置;
- 如果你希望外部参数会随着线上环境变化而变化的话,建议设计成从应用配置中读取,反之从引用选项中读取;
- 但不论采用哪种方式获取参数,都需要在插件文档中对支持的参数进行说明并告知用户正确的配置方式,即是需要设置到应用配置中还是在
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:
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'
}
})
如果插件之间存在相互引用的关系,那么需要注意插件引用顺序。
为了便于插件的使用,开发时需要有一些非强制性约定:
- 插件内部应该直接使用日志API来输出日志信息,这样做的好处是不论用户是否使用的了日志插件,插件本身都可以完美适配。
- 插件内部应该直接使用国际化API,原因同上。
- 会发布到
npm
的插件,插件命名空间应直接采用npm
包名。 - 为独立项目的插件,插件命名空间应直接采用项目名。
- 应用内部使用的插件,需保证所有引用插件的命名空间不重复。
只生成单文件:
$ 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