Web 框架做的事情太少就会导致可用性差,做的太多就会比较定制,而 Egg 是框架的框架,帮助团队的技术负责人,来定制适合特定的业务场景的上层业务框架。egg.js 的名称含义正是这样,像 egg 一样孕育 Web 框架

前面章节介绍了如何使用 egg.js 完成业务开发、定制插件,这些是把 egg.js 当做一个 web 框架使用,本章节介绍下 egg.js 做为框架的框架为业务定制一个 web 框架的能力

设计目标

可以把前面章节实现的基础功能做为 demo 框架的默认功能,封装完成后提供给团队使用

  1. 自带 handlebars 模板渲染能力
  2. 所有请求自动统计耗时
  3. enum、util 挂载到 this.app

    配置框架

    初始化代码

    使用 egg.js 提供的 framework 脚手架初始化框架代码

    1. $ mkdir framework-demo && cd framework-demo
    2. $ npm init egg --type=framework

    目录结构应该很熟悉了,多出来的lib/framework.js 是框架的入口

    1. framework-demo
    2. ├── app
    3. ├── extend
    4. └── service
    5. ├── config
    6. ├── config.default.js
    7. └── plugin.js
    8. ├── lib
    9. └── framework.js
    10. ├── test
    11. ├── README.md
    12. ├── index.js
    13. └── package.json

    handlebars 模板引擎支持

    egg.js 使用的章节介绍过 如何配置模板引擎,定制框架的时候步骤一样

    安装 egg-view-handlebars

    1. $ npm i egg-view-handlebars --save

    启用插件

    1. // config/plugin.js
    2. module.exports = {
    3. handlebars: {
    4. enable: true,
    5. package: 'egg-view-handlebars',
    6. },
    7. };

    配置 view 渲染项

    1. // config/config.default.js
    2. config.view = {
    3. defaultViewEngine: 'handlebars',
    4. defaultExtension: '.hbs',
    5. mapping: {
    6. '.hbs': 'handlebars',
    7. },
    8. };

    这样使用该框架就默认具备了 handlebars 渲染能力

    内置请求耗时中间件

    中间件的编写规则和在 egg.js 中直接使用一致,不过添加到框架的方式有所不同

    添加 cost 中间件

    1. // app/middleware/cost.js
    2. module.exports = options => {
    3. const header = options.header || 'X-Response-Time';
    4. return async function cost(ctx, next) {
    5. const now = Date.now();
    6. await next();
    7. ctx.set(header, `${Date.now() - now}ms`);
    8. };
    9. };

    应用中间件

    框架和插间添加中间件和直接在应用中使用不同,不支持修改 config 文件,需要在项目根目录下的 app.js 修改

    1. // app.js
    2. module.exports = app => {
    3. // 在中间件最前面统计请求时间
    4. app.config.coreMiddleware.unshift('cost');
    5. };

    enum、util 挂载

    在框架中有很多业务的字段枚举或者通用的工具类,一般是定义了文件夹统一管理,开发使用的时候手工 require,使用 egg.js 后可以把约定内置框架,在指定目录编写后自动加载到框架

    文件添加

    添加文件 app/enum/error.jsapp/util/dto.js

    1. framework-demo
    2. ├── app
    3. ├── extend
    4. ├── service
    5. ├── enum
    6. └── error.js
    7. └── util
    8. └── dto.js
    9. └── 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’, };

  1. ```javascript
  2. // app/util/dto.js
  3. 'use strict';
  4. const assert = require('assert');
  5. function isObject(obj) {
  6. const objType = Object.prototype.toString.call(obj);
  7. return objType === '[object Object]' || objType === '[object Array]' || objType === '[object Null]';
  8. }
  9. class ResultDto {
  10. constructor(result, code = 200, errorMsg = '', errorStack = null) {
  11. assert(isObject(result), '[ResultDto:constructor]: arg[0] must be an object or null!');
  12. this.result = result;
  13. this.success = code === 200;
  14. this.code = code;
  15. if (code !== 200) {
  16. this.errorMsg = errorMsg;
  17. this.errorStack = errorStack;
  18. }
  19. }
  20. }
  21. exports.ResultDto = ResultDto;

配置 loader

在配置文件中为文件夹声明路径和注入的对象,更多细节参考 EggJS 加载器

  1. // config/config.default.js
  2. config.customLoader = {
  3. enum: {
  4. directory: 'app/enum',
  5. inject: 'app',
  6. loadunit: true,
  7. },
  8. util: {
  9. directory: 'app/util',
  10. inject: 'app',
  11. loadunit: true,
  12. },
  13. };

自动提示

由于 Egg 是动态挂载的,如需 TS 和智能提示支持,需要通过 egg-ts-helper 来自动生成映射
首先修改 package.json 文件声明

  1. // package.json
  2. {
  3. "name": "framework-demo",
  4. "egg": {
  5. "declaration": true,
  6. "tsHelper": {
  7. "watchDirs": {
  8. "enum": {
  9. "enabled": true,
  10. "directory": "app/enum",
  11. "declareTo": "Application.enum"
  12. },
  13. "util": {
  14. "enabled": true,
  15. "directory": "app/util",
  16. "declareTo": "Application.util"
  17. }
  18. }
  19. }
  20. }
  21. }

egg-bin 内置支持了自动生成 typings 文件夹,但框架开发通常不会使用 egg-bin dev 为了方便框架开发可以在 scripts 配置生成 typeings 的命令

  1. "scripts": {
  2. "typing": "npx ets"
  3. },

使用

image.png

测试 & 发布

这样就完成了框架定制,框架因为涉及多人使用,需要有完善的测试保证可用性,egg.js 提供了完备的测试支持,测试工作完成后可以进入发布流程

  1. 根据语义化版本号规则使用合适的版本
  2. 发布 beta 版本 npm publish --tag=beta
  3. 测试 OK 后发布正式版本 npm publish

    使用框架

    在 egg.js 应用中使用框架很简单,把 egg 脚手架生成的应用 package.json 稍作修改即可
    1. {
    2. "name": "egg-demo",
    3. "version": "1.0.0",
    4. "egg": {
    5. "declarations": true,
    6. "framework": "egg-framework-demo"
    7. },
    8. "dependencies": {
    9. "egg-framework-demo": "^1",
    10. "egg-scripts": "^2.11.0"
    11. }
    12. }
    package.json 声明框架后 npm run dev 可以看到已经使用 egg-demo-framework 启动框架了,cost 中间件也正常工作
    1. INFO 76333 [master] egg-framework-demo started on http://127.0.0.1:7001 (1901ms)
    image.png

完整代码:https://github.com/Samaritan89/egg-framework-demo

参考

  1. 如何为团队量身定制 EggJS 目录挂载规范
  2. 基于 EggJS 为团队定制自己的 Node.js 框架