文档分析在学习EggJs企业级web开发框架时,Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 的插件机制有很高的可扩展性,一个插件只做一件事(比如 Nunjucks 模板封装成了 egg-view-nunjucks、MySQL 数据库封装成了 egg-mysql)。Egg 通过框架聚合这些插件,并根据自己的业务场景定制配置,这样应用的开发成本就变得很低。
自己需要开发eggjs的插件就需要根据官方提供的文档进行学习,按照统一的开发约定进行开发。是不是看完官方文档还是一头雾水,不知道从何下手,或者你在网上搜索的内容都是一模一样的。
官方文档关键信息
-
插件没有独立的 router 和 controller。这主要出于几点考虑:
-
路由一般和应用强绑定的,不具备通用性。
-
一个应用可能依赖很多个插件,如果插件支持路由可能导致路由冲突。
-
如果确实有统一路由的需求,可以考虑在插件里通过中间件来实现。
-
-
插件需要在 package.json 中的 eggPlugin 节点指定插件特有的信息:
-
{String} name - 插件名(必须配置),具有唯一性,配置依赖关系时会指定依赖插件的 name。
-
{Array} dependencies - 当前插件强依赖的插件列表(如果依赖的插件没找到,应用启动失败)。
-
{Array} optionalDependencies - 当前插件的可选依赖插件列表(如果依赖的插件未开启,只会 warning,不会影响应用启动)。
-
{Array} env - 只有在指定运行环境才能开启,具体有哪些环境可以参考运行环境。此配置是可选的,一般情况下都不需要配置。
{ "name": "egg-rpc", "eggPlugin": { "name": "rpc", "dependencies": [ "registry" ], "optionalDependencies": [ "vip" ], "env": [ "local", "test", "unittest", "prod" ] } } -
-
插件没有 plugin.js:
-
eggPlugin.dependencies 只是用于声明依赖关系,而不是引入插件或开启插件。
-
-
包括了 path 和 package 两种加载模式
-
config/plugin.js 中通过 path 来挂载插件。
// config/plugin.js const path = require('path'); exports.ua = { enable: true, path.join(__dirname,'../lib/plugin/egg-xml-par') };-
package.json 中声明对 egg-ua 的依赖。config/plugin.js 中修改依赖声明为 package 方式。
// config/plugin.js exports.ua = { enable: true, package: 'egg-xml-par', }; -
安装eggjs基础脚手架
我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):
$ mkdir egg-example && cd egg-example $ npm init egg --type=simple $ npm i
启动项目进行测试:
$ npm run dev $ open http://localhost:7001
新建插件环境
-
新建插件文件夹lib (插件所有的配置,代码都在这里面)
-
我们是做一个解析xml请求的中间件插件,所有需要搭建下图的结构
. lib ├── plugin │ ├── egg-xml-par (插件npm名) | ├── package.json (插件名称等配置) | ├── app.js (中间件配置) | |────── app | ├── middleware | ├── xml-par.js(插件处理代码) | |────── config ├── config.default.js(插件需要的配置文件)
配置插件基础信息
Egg 是通过 eggPlugin.name 来定义插件名的,只在应用或框架具备唯一性
//lib/plugin/egg-xml-par/package.json
{
"name": "egg-xml-par",
"eggPlugin": {
"name": "XmlPar",
"version": "1.0.0"
}
}
将插件加入中间件
在 app.js 中将中间件插入到合适的位置(例如:下面将 xmlPar中间件放到 bodyParser 之前)
//lib/plugin/egg-xml-par/app.js
const assert = require('assert');
module.exports = app => {
// 将 xmlPar 中间件放到 bodyParser 之前
const index = app.config.coreMiddleware.indexOf('bodyParser');
assert(index >= 0, 'bodyParser 中间件必须存在');
app.config.coreMiddleware.splice(index, 0, 'xmlPar');
};
写插件核心代码
//lib/plugin/egg-xml-par/app/middleware/xml-par.js
'use strict';
const parse = require('co-body');
const {xml2js} = require('xml-js');
module.exports = options => {
// 获取插件配置 //lib/plugin/egg-xml-par/config/config.default.js
const xmlTypes = options.xmlTypes;
return async function xmlPar(ctx, next){
try {
const res = await parseBody(ctx);
ctx.request.body = 'parsed' in res ? res.parsed : {};
if(ctx.request.rawBody === undefined){
ctx.request.rawBody = res.raw
}
} catch (error) {
ctx.logger.warn(error)
}
await next();
}
async function parseBody(ctx){
if(ctx.request.is(xmlTypes)){
var body = await parse.text(ctx.request);
ctx.logger.info(body);
const xmlContent = xml2js(body);
return {
raw : body,
parsed : xmlContent.elements[0]
}
}
return {}
}
};
//lib/plugin/egg-xml-par/config/config.default.js
exports.xmlPar = {
xmlTypes:[
'text/x-xml',
'application/xml',
'application/x-xml'
]
}
Egg.js使用自定义插件
-
package 是 npm 方式引入,也是最常见的引入方式
-
path 是绝对路径引入,如应用内部抽了一个插件,但还没达到开源发布独立 npm 的阶段,或者是应用自己覆盖了框架的一些插件
-
关于这两种方式的使用场景,可以参见渐进式开发。
// config/plugin.js
const path = require('path');
exports.mysql = {
enable: true,
path: path.join(__dirname,'../lib/plugin/egg-xml-par')
};
取消egg.js自带解析器与关闭csrf验证
// config/config.default.js
config.security = {
csrf: {
enable: false
}
}
config.bodyParser = {
enable: false
}
完整项目结构
运行测试因为接口定义未post,为了方便这里需要关闭csrf验证
// config/config.default.js
config.security = {
csrf: {
enable: false
}
}
未使用插件
发现返回结果没有显示
使用插件测试
记得要关闭默认的解析
// config/config.default.js
config.bodyParser = {
enable: false
}
其他说明
开发其他插件原理类似,希望看完该内容你有点收获



