Web 框架作的事情太少就会致使可用性差,作的太多就会比较定制,而 Egg 是框架的框架,帮助团队的技术负责人,来定制适合特定的业务场景的上层业务框架。egg.js 的名称含义正是这样,像 egg 同样孕育 Web 框架html
前面章节介绍了如何使用 egg.js 完成业务开发、定制插件,这些是把 egg.js 当作一个 web 框架使用,本章节介绍下 egg.js 作为框架的框架为业务定制一个 web 框架的能力node
能够把前面章节实现的基础功能作为 demo 框架的默认功能,封装完成后提供给团队使用git
this.app
使用 egg.js 提供的 framework 脚手架初始化框架代码github
$ mkdir framework-demo && cd framework-demo
$ npm init egg --type=framework
复制代码
目录结构应该很熟悉了,多出来的lib/framework.js
是框架的入口web
framework-demo
├── app
│ ├── extend
│ └── service
├── config
│ ├── config.default.js
│ └── plugin.js
├── lib
│ └── framework.js
├── test
├── README.md
├── index.js
└── package.json
复制代码
egg.js 使用的章节介绍过 如何配置模板引擎,定制框架的时候步骤同样npm
$ npm i egg-view-handlebars --save
复制代码
// config/plugin.js
module.exports = {
handlebars: {
enable: true,
package: 'egg-view-handlebars',
},
};
复制代码
// config/config.default.js
config.view = {
defaultViewEngine: 'handlebars',
defaultExtension: '.hbs',
mapping: {
'.hbs': 'handlebars',
},
};
复制代码
这样使用该框架就默认具有了 handlebars 渲染能力json
中间件的编写规则和在 egg.js 中直接使用一致,不过添加到框架的方式有所不一样markdown
// app/middleware/cost.js
module.exports = options => {
const header = options.header || 'X-Response-Time';
return async function cost(ctx, next) {
const now = Date.now();
await next();
ctx.set(header, `${Date.now() - now}ms`);
};
};
复制代码
框架和插间添加中间件和直接在应用中使用不一样,不支持修改 config 文件,须要在项目根目录下的 app.js
修改app
// app.js
module.exports = app => {
// 在中间件最前面统计请求时间
app.config.coreMiddleware.unshift('cost');
};
复制代码
在框架中有不少业务的字段枚举或者通用的工具类,通常是定义了文件夹统一管理,开发使用的时候手工 require,使用 egg.js 后能够把约定内置框架,在指定目录编写后自动加载到框架框架
添加文件 app/enum/error.js
和 app/util/dto.js
framework-demo
├── app
│ ├── extend
│ ├── service
│ ├── enum
│ │ └── error.js
│ └── util
│ │ └── dto.js
└── package.json
// app/enum/error.js
'use strict';
exports.ERR_AUTH = {
code: '403',
msg: 'not perm',
};
exports.ERR_NOTFOUND = {
code: '404',
msg: 'not found',
};
exports.ERR_SERVER = {
code: '500',
msg: 'internal server error',
};
// app/util/dto.js
'use strict';
const assert = require('assert');
function isObject(obj) {
const objType = Object.prototype.toString.call(obj);
return objType === '[object Object]' || objType === '[object Array]' || objType === '[object Null]';
}
class ResultDto {
constructor(result, code = 200, errorMsg = '', errorStack = null) {
assert(isObject(result), '[ResultDto:constructor]: arg[0] must be an object or null!');
this.result = result;
this.success = code === 200;
this.code = code;
if (code !== 200) {
this.errorMsg = errorMsg;
this.errorStack = errorStack;
}
}
}
exports.ResultDto = ResultDto;
复制代码
在配置文件中为文件夹声明路径和注入的对象,更多细节参考 EggJS 加载器
// config/config.default.js
config.customLoader = {
enum: {
directory: 'app/enum',
inject: 'app',
loadunit: true,
},
util: {
directory: 'app/util',
inject: 'app',
loadunit: true,
},
};
复制代码
因为 Egg 是动态挂载的,如需 TS 和智能提示支持,须要经过 egg-ts-helper 来自动生成映射
首先修改 package.json 文件声明
// package.json
{
"name": "framework-demo",
"egg": {
"declaration": true,
"tsHelper": {
"watchDirs": {
"enum": {
"enabled": true,
"directory": "app/enum",
"declareTo": "Application.enum"
},
"util": {
"enabled": true,
"directory": "app/util",
"declareTo": "Application.util"
}
}
}
}
}
复制代码
egg-bin 内置支持了自动生成 typings
文件夹,但框架开发一般不会使用 egg-bin dev
为了方便框架开发能够在 scripts 配置生成 typeings 的命令
"scripts": {
"typing": "npx ets"
},
复制代码
这样就完成了框架定制,框架由于涉及多人使用,须要有完善的测试保证可用性,egg.js 提供了完备的测试支持,测试工做完成后能够进入发布流程
npm publish --tag=beta
npm publish
在 egg.js 应用中使用框架很简单,把 egg 脚手架生成的应用 package.json 稍做修改便可
{
"name": "egg-demo",
"version": "1.0.0",
"egg": {
"declarations": true,
"framework": "egg-framework-demo"
},
"dependencies": {
"egg-framework-demo": "^1",
"egg-scripts": "^2.11.0"
}
}
复制代码
package.json 声明框架后 npm run dev
能够看到已经使用 egg-demo-framework 启动框架了,cost 中间件也正常工做
INFO 76333 [master] egg-framework-demo started on http://127.0.0.1:7001 (1901ms)
复制代码