Web 框架做的事情太少就会导致可用性差,做的太多就会比较定制,而 Egg 是框架的框架,帮助团队的技术负责人,来定制适合特定的业务场景的上层业务框架。egg.js 的名称含义正是这样,像 egg 一样孕育 Web 框架
前面章节介绍了如何使用 egg.js 完成业务开发、定制插件,这些是把 egg.js 当做一个 web 框架使用,本章节介绍下 egg.js 做为框架的框架为业务定制一个 web 框架的能力
设计目标
可以把前面章节实现的基础功能做为 demo 框架的默认功能,封装完成后提供给团队使用
- 自带 handlebars 模板渲染能力
- 所有请求自动统计耗时
-
配置框架
初始化代码
使用 egg.js 提供的 framework 脚手架初始化框架代码
$ mkdir framework-demo && cd framework-demo
$ npm init egg --type=framework
目录结构应该很熟悉了,多出来的
lib/framework.js
是框架的入口framework-demo
├── app
│ ├── extend
│ └── service
├── config
│ ├── config.default.js
│ └── plugin.js
├── lib
│ └── framework.js
├── test
├── README.md
├── index.js
└── package.json
handlebars 模板引擎支持
egg.js 使用的章节介绍过 如何配置模板引擎,定制框架的时候步骤一样
安装 egg-view-handlebars
$ npm i egg-view-handlebars --save
启用插件
// config/plugin.js
module.exports = {
handlebars: {
enable: true,
package: 'egg-view-handlebars',
},
};
配置 view 渲染项
// config/config.default.js
config.view = {
defaultViewEngine: 'handlebars',
defaultExtension: '.hbs',
mapping: {
'.hbs': 'handlebars',
},
};
内置请求耗时中间件
中间件的编写规则和在 egg.js 中直接使用一致,不过添加到框架的方式有所不同
添加 cost 中间件
// 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.js
module.exports = app => {
// 在中间件最前面统计请求时间
app.config.coreMiddleware.unshift('cost');
};
enum、util 挂载
在框架中有很多业务的字段枚举或者通用的工具类,一般是定义了文件夹统一管理,开发使用的时候手工 require,使用 egg.js 后可以把约定内置框架,在指定目录编写后自动加载到框架
文件添加
添加文件
app/enum/error.js
和app/util/dto.js
framework-demo
├── app
│ ├── extend
│ ├── service
│ ├── enum
│ │ └── error.js
│ └── util
│ │ └── dto.js
└── package.json
```javascript // 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’, };
```javascript
// 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;
配置 loader
在配置文件中为文件夹声明路径和注入的对象,更多细节参考 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 提供了完备的测试支持,测试工作完成后可以进入发布流程
- 根据语义化版本号规则使用合适的版本
- 发布 beta 版本
npm publish --tag=beta
- 测试 OK 后发布正式版本
npm publish
使用框架
在 egg.js 应用中使用框架很简单,把 egg 脚手架生成的应用 package.json 稍作修改即可
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"
}
}
npm run dev
可以看到已经使用 egg-demo-framework 启动框架了,cost 中间件也正常工作INFO 76333 [master] egg-framework-demo started on http://127.0.0.1:7001 (1901ms)
完整代码:https://github.com/Samaritan89/egg-framework-demo