Web 框架做的事情太少就会导致可用性差,做的太多就会比较定制,而 Egg 是框架的框架,帮助团队的技术负责人,来定制适合特定的业务场景的上层业务框架。egg.js 的名称含义正是这样,像 egg 一样孕育 Web 框架
前面推文介绍了如何使用 egg.js 完成业务开发、定制插件,这些是把 egg.js 当做一个 web 框架使用,本章节介绍下 egg.js 做为框架的框架为业务定制一个 web 框架的能力
设计目标
可以把前面章节实现的基础功能做为 demo 框架的默认功能,封装完成后提供给团队使用
- 自带 handlebars 模板渲染能力
- 所有请求自动统计耗时
- enum、util 挂载到
this.app
配置框架
初始化代码
使用 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.jsmodule.exports = { handlebars: { enable: true, package: 'egg-view-handlebars', },};
配置 view 渲染项
// config/config.default.jsconfig.view = { defaultViewEngine: 'handlebars', defaultExtension: '.hbs', mapping: { '.hbs': 'handlebars', },};
这样使用该框架就默认具备了 handlebars 渲染能力
内置请求耗时中间件
中间件的编写规则和在 egg.js 中直接使用一致,不过添加到框架的方式有所不同
添加 cost 中间件
// app/middleware/cost.jsmodule.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.jsmodule.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
// 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;
配置 loader
在配置文件中为文件夹声明路径和注入的对象,更多细节参考 EggJS 加载器
// config/config.default.jsconfig.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 稍作修改即可
{ "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)
完整代码:https://github.com/Samaritan89/egg-framework-demo
参考
- 如何为团队量身定制 EggJS 目录挂载规范
https://zhuanlan.zhihu.com/p/153322661 - 基于 EggJS 为团队定制自己的 Node.js 框架
https://zhuanlan.zhihu.com/p/154643011