使用 egg.js 定制业务 web 框架

前端开发工程师 @ 阿里巴巴

作者:ICBU 谦行

写在最前:欢迎来到阿里巴巴 ICBU 交互&端技术前端团队专栏,我们将与你分享优质的前端技术文章,欢迎关注&交流哟!

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 脚手架初始化框架代码

$ 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',
  },
};
复制代码

这样使用该框架就默认具备了 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.jsapp/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.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 提供了完备的测试支持,测试工作完成后可以进入发布流程

  1. 根据语义化版本号规则使用合适的版本
  2. 发布 beta 版本 npm publish --tag=beta
  3. 测试 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)
复制代码

完整代码:github.com/Samaritan89…

参考

  1. 如何为团队量身定制 EggJS 目录挂载规范

  2. 基于 EggJS 为团队定制自己的 Node.js 框架

❤️ 感谢你看到最后~

阿里巴巴国际站(ICBU,Alibaba.com)是全球最大的跨境贸易和服务平台。我们时刻有新的技术挑战,有足够有趣的挑战满足你所有的好奇心和求知欲,有国外知名合作团队(Google & OpenSky)。

如果你想来 ICBU 和我一起开发前端,欢迎发简历到邮箱 shudai.lyy@alibaba-inc.com ,我们将快速响应你的面试安排。:-)

分类:
前端
标签: