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)
複製代碼