文章目录
-
- [一、Egg.js 4 为什么突然又火了](#一、Egg.js 4 为什么突然又火了)
- [二、Egg.js 4 全景:一张图看懂变化](#二、Egg.js 4 全景:一张图看懂变化)
- [三、上手:5 分钟跑起一个 Egg 4 应用](#三、上手:5 分钟跑起一个 Egg 4 应用)
- 四、五大核心变化逐个拆解
-
- [4.1 全面拥抱 TypeScript + ESM](#4.1 全面拥抱 TypeScript + ESM)
- [4.2 测试框架升级 Vitest](#4.2 测试框架升级 Vitest)
- [4.3 冷启动性能暴涨 60%:Bundle + V8 Snapshot](#4.3 冷启动性能暴涨 60%:Bundle + V8 Snapshot)
- [4.4 Monorepo 重构,插件生态整合](#4.4 Monorepo 重构,插件生态整合)
- [4.5 Tegg 整合,竟然还支持 MCP!](#4.5 Tegg 整合,竟然还支持 MCP!)
- [五、选型对比:Egg 4 vs 主流 Node 后端方案](#五、选型对比:Egg 4 vs 主流 Node 后端方案)
- [六、从 3.x 升级到 4 的迁移路线](#六、从 3.x 升级到 4 的迁移路线)
- 七、踩坑清单
- [八、结论:什么时候该选 Egg.js 4](#八、结论:什么时候该选 Egg.js 4)
- 参考与延伸阅读
摘要:Egg.js 4 是阿里开源企业级 Node.js 框架的一次断代式重构。它把三件事做进了主干:全 TypeScript + ESM/CommonJS 双发布、Tegg 模块化(依赖注入 + 领域边界 + 生命周期)、以及 HTTP/MCP/Agent 三协议共用同一份业务能力。本文按「为什么火 → 全景架构 → 5 分钟上手 → 五大核心变化逐个拆解(配图 + 可跑代码)→ 选型对比 → 3.x 迁移路线 → 踩坑清单」展开,面向正在做技术选型或维护中大型 Egg 应用的团队。文中所有数据均标注来源,代码基于官方文档与仓库示例整理,可直接复现。
一、Egg.js 4 为什么突然又火了
先对齐时间线,避免被网上口径不一的信息带偏:
| 版本 | 时间 | 关键变化 |
|---|---|---|
| egg@4.0.0 | 2025-01-11 | 断代升级:移除 generator、要求 Node ≥ 18.19、TypeScript 重写、CJS + ESM 双格式发布 |
| egg@4.1.x | 2025 年内持续迭代 | 运行基线抬到 Node ≥ 22.18.0;测试接入 Vitest;Monorepo(utoo 工作区)+ Tegg 默认集成;Bundle / V8 启动快照进入预发布 |
| egg@4.2.0 | 2026-10 | 当前 latest 稳定版,全包小版本统一升级,egg-status 等收编进 Monorepo |
说明:截至本文写作时,
npm dist-tags显示egg@latest = 4.2.0、create-egg@latest = 4.2.0。官方快速入门目前仍使用create-egg@beta标签,两条线内容基本一致;实际以npm view egg dist-tags为准。
那为什么是「突然」火?我的判断是三条线在这两年同时收敛了:
- AI 原生落地需求爆发。过去让大模型调用业务系统,团队要自己写 JSON-RPC 处理、Schema 校验、路由分发。Egg 4 把 MCP(Model Context Protocol)的 Tool / Prompt / Resource 三种能力直接做成了装饰器------写一个类,业务能力就成了可被 Claude、Cursor 或自建 Agent 调用的工具。
- 中大型应用的架构债到了还账期。3.x 时代「二十多个 controller 挤在一个目录、路由靠手写、类型靠注释」的项目太多,Tegg 的模块化 + 依赖注入正好对症。
- 性能焦虑。Serverless / 弹性扩容场景下,Node 冷启动被反复吐槽。Egg 4 用 Manifest、编译缓存、Bundle、V8 启动快照四层优化正面回应。
一句话概括定位变化:Egg 从「约定式 MVC 框架」,变成了「可承载 AI 能力的模块化应用平台」。
二、Egg.js 4 全景:一张图看懂变化
#mermaid-svg-mGsAWRN70TGlXdiO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mGsAWRN70TGlXdiO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mGsAWRN70TGlXdiO .error-icon{fill:#552222;}#mermaid-svg-mGsAWRN70TGlXdiO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mGsAWRN70TGlXdiO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mGsAWRN70TGlXdiO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mGsAWRN70TGlXdiO .marker.cross{stroke:#333333;}#mermaid-svg-mGsAWRN70TGlXdiO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mGsAWRN70TGlXdiO p{margin:0;}#mermaid-svg-mGsAWRN70TGlXdiO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mGsAWRN70TGlXdiO .cluster-label text{fill:#333;}#mermaid-svg-mGsAWRN70TGlXdiO .cluster-label span{color:#333;}#mermaid-svg-mGsAWRN70TGlXdiO .cluster-label span p{background-color:transparent;}#mermaid-svg-mGsAWRN70TGlXdiO .label text,#mermaid-svg-mGsAWRN70TGlXdiO span{fill:#333;color:#333;}#mermaid-svg-mGsAWRN70TGlXdiO .node rect,#mermaid-svg-mGsAWRN70TGlXdiO .node circle,#mermaid-svg-mGsAWRN70TGlXdiO .node ellipse,#mermaid-svg-mGsAWRN70TGlXdiO .node polygon,#mermaid-svg-mGsAWRN70TGlXdiO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mGsAWRN70TGlXdiO .rough-node .label text,#mermaid-svg-mGsAWRN70TGlXdiO .node .label text,#mermaid-svg-mGsAWRN70TGlXdiO .image-shape .label,#mermaid-svg-mGsAWRN70TGlXdiO .icon-shape .label{text-anchor:middle;}#mermaid-svg-mGsAWRN70TGlXdiO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mGsAWRN70TGlXdiO .rough-node .label,#mermaid-svg-mGsAWRN70TGlXdiO .node .label,#mermaid-svg-mGsAWRN70TGlXdiO .image-shape .label,#mermaid-svg-mGsAWRN70TGlXdiO .icon-shape .label{text-align:center;}#mermaid-svg-mGsAWRN70TGlXdiO .node.clickable{cursor:pointer;}#mermaid-svg-mGsAWRN70TGlXdiO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mGsAWRN70TGlXdiO .arrowheadPath{fill:#333333;}#mermaid-svg-mGsAWRN70TGlXdiO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mGsAWRN70TGlXdiO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mGsAWRN70TGlXdiO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mGsAWRN70TGlXdiO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mGsAWRN70TGlXdiO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mGsAWRN70TGlXdiO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mGsAWRN70TGlXdiO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mGsAWRN70TGlXdiO .cluster text{fill:#333;}#mermaid-svg-mGsAWRN70TGlXdiO .cluster span{color:#333;}#mermaid-svg-mGsAWRN70TGlXdiO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mGsAWRN70TGlXdiO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mGsAWRN70TGlXdiO rect.text{fill:none;stroke-width:0;}#mermaid-svg-mGsAWRN70TGlXdiO .icon-shape,#mermaid-svg-mGsAWRN70TGlXdiO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mGsAWRN70TGlXdiO .icon-shape p,#mermaid-svg-mGsAWRN70TGlXdiO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mGsAWRN70TGlXdiO .icon-shape .label rect,#mermaid-svg-mGsAWRN70TGlXdiO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mGsAWRN70TGlXdiO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mGsAWRN70TGlXdiO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mGsAWRN70TGlXdiO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构建与部署(可选)
框架运行时
Tegg 模块层(默认 9 个插件)
入口协议层
HTTP Controller
REST API
MCP Controller
Tool / Prompt / Resource
Agent Controller
Thread / Run / SSE
依赖注入与作用域
Singleton / Context / MultiInstance
eggModule 模块边界
AccessLevel 可见性控制
AOP / DAL / ORM(Leoric) / EventBus / Schedule
@eggjs/core
加载器 / 生命周期 / 路由
@eggjs/koa
中间件体系
@eggjs/* 插件体系
配置合并
Manifest 发现缓存
Compile Cache 编译缓存
Bundle 模块图
V8 Startup Snapshot
配套的变化清单:
| 维度 | Egg 3.x | Egg 4 |
|---|---|---|
| Node 基线 | 8 / 12+ | 22.18.0+(快照恢复需 Node 24+) |
| 异步写法 | generator / async 混用 | 移除 generator,统一 async/await |
| 类型 | 靠 @types 补充 |
源码即 TypeScript,类型贯穿配置与插件入口 |
| 模块格式 | CJS 为主 | CJS + ESM 双发布,应用自由选 |
| 配置 | 手写 export default {} |
defineConfig / defineConfigFactory 类型化 |
| 插件 | egg-* 字符串 + enable/package |
@eggjs/* 命名空间 + 可导入的插件工厂 |
| 测试 | Mocha | Vitest (egg-bin test 入口不变) |
| 模块化 | 目录约定 | Tegg:DI + 模块边界 + 生命周期 |
| AI 能力 | 无 | MCP Controller / Agent Runtime 内置 |
| 冷启动 | 每次全量扫描 | Manifest / Compile Cache / Bundle / Snapshot |
三、上手:5 分钟跑起一个 Egg 4 应用
环境准备:Node.js ≥ 22.18.0(生产建议用当前仍受支持的 LTS 最新补丁)。脚手架直接给了一个 Tegg 模板:
bash
npx create-egg@beta --template tegg hackernews-tegg
cd hackernews-tegg
npm install
npm run dev
# 打开 http://localhost:7001
如果你更想看传统的「约定式」骨架,也可以手动搭一个最小项目,方便对照理解 Egg 的加载约定:
bash
mkdir egg-mini && cd egg-mini
npm init -y
npm i egg
npm i -D @eggjs/bin
json
{
"name": "egg-mini",
"type": "module",
"scripts": {
"dev": "egg-bin dev",
"test": "egg-bin test",
"cov": "egg-bin cov",
"typecheck": "tsc --noEmit"
}
}
ts
// app/controller/home.ts
import { Controller } from 'egg';
export default class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello Egg 4';
}
}
ts
// app/router.ts
import type { Application } from 'egg';
export default (app: Application) => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
ts
// config/config.default.ts
import { defineConfig } from 'egg';
export default defineConfig({
keys: 'replace-with-your-own-cookie-secret',
});
app/、config/、router 这些约定仍然有效------Egg 的核心优势「约定优于配置」没有被推翻,只是把类型和模块能力补上了。
四、五大核心变化逐个拆解
本章按大家最关心的五个变化逐一展开,每节配图与可跑代码。
4.1 全面拥抱 TypeScript + ESM

Egg 4 延续 TypeScript 重构:框架源码即 TypeScript,egg 包声明 type: module,同时发布 CJS 与 ESM 双格式 ,应用可以自由选择模块方案------官方仓库里同时保留 helloworld-commonjs 和 helloworld-typescript 两个示例。
类型化最直接的收益写在配置文件里。配置是应用最容易出低级错误的地方,Egg 4 提供三个「零运行时开销」的 helper(实现上只是原样返回入参,纯粹是类型边界):
ts
// config/config.default.ts ------ 对象用 defineConfig
import { defineConfig } from 'egg';
export default defineConfig({
logger: { level: 'INFO', consoleLevel: 'WARN' },
httpclient: { request: { timeout: 5000 } },
});
ts
// config/config.default.ts ------ 需要读应用信息时用工厂
import { defineConfigFactory } from 'egg';
export default defineConfigFactory((appInfo) => ({
logger: {
level: appInfo.env === 'local' ? 'DEBUG' : 'INFO',
consoleLevel: 'WARN',
},
}));
插件侧则用 definePluginFactory,把名称、启用状态、路径、依赖集中到一个可导入的入口,编辑器可以从 import 一路跳转到插件定义:
ts
// config/plugin.ts ------ 应用侧:展开工厂返回的配置
import redisPlugin from '@eggjs/redis';
import typeboxPlugin from '@eggjs/typebox-validate';
export default {
...redisPlugin({ env: ['local', 'unittest'] }),
...typeboxPlugin(),
};
ts
// 自定义插件侧:声明自己的元数据
import { definePluginFactory } from 'egg';
export default definePluginFactory({
name: 'myplugin',
enable: true,
path: import.meta.dirname,
});
⚠️ 两个容易搞混的概念要分清:redisPlugin() 决定插件是否加载 ,config.redis 决定插件如何工作。工厂调用选项是浅合并,覆盖数组时会整体替换,不会追加。
4.2 测试框架升级 Vitest

Egg 4 把测试从 Mocha 迁到了 Vitest 。对使用者来说入口没变------egg-bin test 和 egg-bin cov 依然可用,但底层换成了同一套现代工具链:TypeScript 用例直接跑、--watch 边改边测、覆盖率开箱即用:
bash
egg-bin test # 跑测试
egg-bin test --watch # 边改边测
egg-bin cov # 覆盖率
安装 @eggjs/mock 的应用会自动接入测试生命周期处理。工具链本身也在持续优化:官方 PR #5541 在接入 Vitest 后切换到 rolldown-vite,测试总耗时从 38.98 秒降到 28.56 秒,减少约 26.7%(官方口径)。
从 3.x 迁移测试时重点调整三件事:Mocha hooks 写法、旧的并行参数、覆盖率报告格式;并行用例记得按 worker 隔离数据库 / Redis / 数据目录,否则用例会互相污染。
4.3 冷启动性能暴涨 60%:Bundle + V8 Snapshot

Egg 4 的启动优化沿执行链分四层,对应四类启动成本:
| 层级 | 机制 | 缓存位置 | 解决的成本 |
|---|---|---|---|
| Manifest | 缓存文件发现、模块解析、Tegg 元数据 | .egg/manifest.json |
重复文件系统查询与 glob 扫描 |
| Compile Cache | 复用 V8 编译结果 | .egg/compile-cache |
JavaScript 编译 |
| Bundle | 构建期固化模块图,运行时从内联映射加载 | dist-bundle/ |
模块发现与组织 |
| V8 Snapshot | 保存「预加载完成 → configWillLoad」的堆状态 | snapshot.blob |
模块求值 + 部分启动初始化 |
命令基本可以照抄:
bash
# 1) 普通 bundle:先验证打包是否保留了应用行为
egg-bin bundle
cd dist-bundle && node worker.js
# 2) 单进程启动快照(构建与恢复都要用 Node 24+)
egg-bin snapshot build
egg-scripts start --snapshot-blob ./dist-bundle/snapshot.blob --port 7001
# 3) Cluster:app 与 agent 各自一份 blob
egg-bin snapshot build --cluster
egg-scripts start --bundle \
--app-snapshot-blob ./dist-bundle/app.snapshot.blob \
--agent-snapshot-blob ./dist-bundle/agent.snapshot.blob
官方给出的 cnpmcore 实测(Node 24.18.1 / Apple M1 Pro / prod,中位数、每种模式预热一次后交错测 10 次):
- 单进程 :947 ms → 379 ms,降低约 60.0%
- Cluster(1 agent + 2 app worker) :1356 ms → 591 ms,降低约 56.4%
注意两个口径不可直接横向比较:单进程是从 spawn Node 到开始监听,Cluster 是从 master 内部编排到 ready(不含 launcher 与 master 引导开销)。而且这组数字只衡量「同一应用启用快照前后」的冷启动耗时,不含框架升级与吞吐。如果你的启动时间大头花在外部服务连接或对象装配上,收益会完全不同------务必用自己的应用复测。
4.4 Monorepo 重构,插件生态整合

Egg 4 把框架仓库整体迁入由 utoo 管理的 Monorepo(native workspaces + catalogs):核心包、18 个官方插件、测试工具与 Tegg 在同一仓库共同维护。对使用者和插件作者,变化都很实际:
- 跨包改动一个 PR 完成:过去一次核心接口调整,要在多个仓库同步依赖、发布中间版本、再逐个验证插件;现在核心、插件与 Tegg 通过工作区依赖直接联调,在同一个 PR 里修改、测试和审查。
- 插件统一
@eggjs命名空间 :egg-redis→@eggjs/redis,配合 4.1 的插件工厂,名称、启用状态、路径、依赖集中在一个可导入的入口里。 - 应用侧零强制 :业务项目不需要跟着改成 Monorepo,继续用自己的包管理器即可,各包仍独立发布、独立安装。
4.5 Tegg 整合,竟然还支持 MCP!

这是 Egg 4 最值得投入学习成本、也最「出圈」的变化:模块边界、依赖注入和对象生命周期进入统一的应用模型,而 HTTP 只是业务能力的出口之一。Tegg 用带 eggModule 字段的 package.json 标识一个模块:
json
{
"name": "greeting-module",
"type": "module",
"eggModule": { "name": "greeting" }
}
模块内的装饰器类组成可装配的对象图,三种作用域对应不同的状态寿命:
ts
import { ContextProto, SingletonProto, MultiInstanceProto } from '@eggjs/tegg';
@ContextProto() // 每个请求上下文一个实例(放请求态数据)
export class HelloService {
hello(name: string): string {
return `hello, ${name}`;
}
}
@SingletonProto() // 应用生命周期内单例(必须是无可变请求状态的无状态组件)
export class ConfigReader {}
@MultiInstanceProto() // 同一个类对应多个实例(配合 Qualifier 做多实现选择)
export class ChannelAdapter {}
模块可见性是整套设计的灵魂 :服务默认只在模块内可见 ,确实需要跨模块调用时才用 AccessLevel.PUBLIC 显式公开。这与「所有类都 public」的做法是相反的取向------公开的接口越少,模块间依赖越容易在评审和测试中看清。
关键约束(官方反复强调,也是新人最容易踩的):单例里的可变字段会被所有请求共享,装饰器不会自动帮你解决并发读写问题。 装饰器的价值是让「作用域选择」变成一份可被审查的声明,而不是消除状态。
HTTP 入口用装饰器声明路由与注入,取代手写 router.js:
ts
import { HTTPController, HTTPMethod, HTTPMethodEnum, HTTPQuery, Inject } from '@eggjs/tegg';
import { HelloService } from './HelloService.ts';
@HTTPController({ path: '/hello' })
export class HelloController {
@Inject()
private readonly helloService: HelloService;
@HTTPMethod({ method: HTTPMethodEnum.GET, path: '/' })
async hello(@HTTPQuery({ name: 'name' }) name: string) {
return { message: this.helloService.hello(name ?? 'Egg') };
}
}
Tegg 由默认 9 个插件 承载:teggConfig、tegg、teggAjv、teggAop、teggController、teggDal、teggEventbus、teggOrm、teggSchedule。想整体关掉可以设 DISABLE_TEGG_PLUGINS=true(老应用兼容路径)。而 LangChain、MCP Client、MCP Proxy、DNS Cache 这些是按需显式接入的,不会因为框架里有就自动生效。
这里有个重要认知:Tegg 不是「推翻 Egg 的约定式」,而是叠加了一层。传统
Controller/Service仍然可用,团队完全可以从边界清晰的新业务模块开始试点,老代码慢慢迁。
上面 HTTP Controller 的装饰器写法已经看到了。真正的杀招在第二条出口------同一个 HelloService,一行不改,就能被 MCP Controller 暴露给大模型 (先在 plugin.ts 启用 MCP 代理插件):
ts
// config/plugin.ts ------ 先启用 MCP 代理插件
import type { EggPlugin } from 'egg';
const plugin: EggPlugin = {
mcpProxy: true,
};
export default plugin;
ts
import { MCPController, MCPTool, ToolArgs, ToolArgsSchema, MCPToolResponse } from 'egg';
import z from 'zod';
import { Inject } from '@eggjs/tegg';
import { HelloService } from './HelloService.ts';
export const ToolType = {
name: z.string({ description: 'npm package name' }),
};
@MCPController({ name: 'greeting' })
export class GreetingMCPController {
@Inject()
private readonly helloService: HelloService;
@MCPTool({ description: 'Return a greeting' })
async hello(
@ToolArgsSchema(ToolType) args: ToolArgs<typeof ToolType>,
): Promise<MCPToolResponse> {
return {
content: [{ type: 'text' as const, text: this.helloService.hello(args.name) }],
};
}
}
装饰器家族很简单:@MCPTool(工具)、@MCPPrompt(提示词模板)、@MCPResource(资源,支持 hitu://npm/{name}/{?version} 这类 URI 模板)。参数 schema 用 zod 声明,框架自动推导并注册 JSON Schema。长任务还能通过 @Extra() 注入的 ToolExtra.sendNotification() 向客户端推送进度。
测试 不必起真实 MCP 客户端,Egg 在测试环境暴露了 app.mcpClient():
ts
import { app } from 'egg-mock/bootstrap';
import assert from 'node:assert';
it('should expose mcp tool', async () => {
app.mockCsrf();
const client = await app.mcpClient();
const tools = await client.listTools();
assert.ok(tools.tools.some((t) => t.name === 'hello'));
});
再往上一层是 Agent Runtime :提供 thread / run / 取消 / 流式响应(支持 SSE,可按 lastSeq 重放事件)的统一运行模型。但要清醒地看它的边界------AgentRuntime 的活动任务存在进程内 Map ,流事件用本地 JSONL 文件 ,跨节点接管和分布式调度需要你自己补基础设施。应用仍需实现 createStore() 和 execRun(),决定状态存哪、模型怎么调。
还有一个更轻的形态:@eggjs/service-worker 提供 host-neutral 的 ServiceWorkerApp,能在 Node 里 serve,也能直接吃 Fetch Request 返回 Response,不需要启动完整 Egg Application。它保留了参数映射、DI 和请求对象生命周期,适合往 Cloudflare Workers 这类环境部署------但注意 Fetch 传输下 Cookie 只支持无签名读写,依赖完整 Egg 插件行为的应用需要评估。
五、选型对比:Egg 4 vs 主流 Node 后端方案
| 维度 | Egg.js 4 | NestJS | Midway 3 | Fastify / 纯 Koa |
|---|---|---|---|---|
| 设计取向 | 约定式 + 模块化平台 | 装饰器 + DI,Angular 风格 | 装饰器 + IoC,阿里系 | 极简 HTTP 层 |
| 进程模型 | 内置多进程(worker + agent) | 交给外部进程管理器 | 内置 | 交给外部进程管理器 |
| 类型支持 | 源码 TypeScript,类型化配置 | TypeScript 一等公民 | TypeScript 一等公民 | 取决于自己搭 |
| 多应用统一规范 | 框架定制(Framework 层)强 | 较弱 | 中等 | 无 |
| AI / MCP 原生 | 内置 MCP Controller + Agent Runtime | 需自行集成 | 需自行集成 | 无 |
| 冷启动优化 | Bundle + V8 Snapshot | 依赖构建工具 | 依赖构建工具 | 无内置 |
| 上手成本 | 中(约定需先理解) | 中高 | 中 | 低 |
| 生态成熟度 | 中(企业向,插件质量稳定) | 高 | 中高 | 极高 |
怎么选,我的建议是:
- 多个 Node 服务需要统一规范、由平台团队收口 → Egg 4 的「框架定制 + 插件体系」几乎没有替代品。
- 已有 Egg 3.x 中大型应用 → 4 是顺理成章的下一站,且迁移可以渐进。
- 想快速给既有业务加一层 AI 能力出口 → MCP Controller 的投入产出比目前最优。
- 只是写个小 API、团队没有平台诉求 → 别上 Egg,Fastify 甚至 Hono 更合适。加速启动也用不上,因为你的问题不在这里。
- 无法把运行时升到 Node 22.18+ → 这条路先别走。
六、从 3.x 升级到 4 的迁移路线
官方推荐「按现有使用面」逐步推进,我整理成一份可执行的清单:
第一步:依赖与入口(不动业务)
- 升级 Node 到 22.18.0+(要上快照则用 24+,并保持构建与部署运行时一致)
egg升到 4.x,显式安装的插件与测试工具一起升egg-*逐步换成@eggjs/*,注意包名变化(如@eggjs/tegg-aop-plugin→@eggjs/aop-plugin,配置名统一为teggAop)egg-bin升到 v8:不再内置 ts-node,需要自己安装选定的编译器
第二步:代码层适配
- 全文搜索 generator 写法,改成 async/await(这是 4.0 的破坏性变更)
- 检查 ESM/CJS 混用:
require、__dirname、相对导入扩展名 - 配置文件换成
defineConfig/defineConfigFactory - 插件配置换成工厂写法(旧的 enable/package 仍兼容,可逐项替换)
egg-ts-helper集成已移除 ,--declarations/--dts参数已废弃且无效果------原来靠它生成声明的流程要明确换成谁负责
第三步:测试迁移
- Mocha → Vitest,重点调整 hooks、旧并行参数、覆盖率报告格式
- 并行用例要按 worker 隔离数据库 / Redis / 数据目录,否则用例会互相污染(cnpmcore 迁移时就是这么做的)
第四步:可选优化
- 先跑通普通启动 → 再跑普通 bundle → 最后才上 snapshot
- 发布物记录:代码版本、Node 版本、external 依赖、资源清单,并保留普通 bundle 启动方式作为回退
七、踩坑清单
- 单例可变状态 :
@SingletonProto()里的可变字段被所有请求共享,函数式无状态才是正确姿势。 - 模块可见性默认私有 :跨模块调用必须
AccessLevel.PUBLIC,否则会遇到「找不到注入对象」。 - 快照暂停点在
configWillLoad:数据库连接、socket、watcher、后台 timer 必须放在configDidLoad及之后创建,否则会把构建机的活跃连接打进 blob。 - Web 全局对象的陷阱 :构建期
fetch/Request/Response可能是桩。模块顶层写const f = fetch或class X extends globalThis.Request,会把构建期绑定固化进快照------应在函数执行时再读globalThis.fetch。 - Tegg 默认启用 9 个插件 :担心与自定义加载逻辑冲突时,用
DISABLE_TEGG_PLUGINS=true整体关闭排查。 - MCP 工具是外部输入面:有 MCP 注册能力 ≠ 有安全能力。认证、授权、参数校验、执行限额,一样都不能省。
- Snapshot / Bundle 仍是预发布能力 :
@eggjs/egg-bundler为 0.x,接入前务必验证插件与原生依赖兼容性,尤其检查模块顶层副作用。 --declarations已废弃:别再照着老文章配置类型自动生成。
八、结论:什么时候该选 Egg.js 4
Egg 4 这次重构,本质上是把「企业级」三个字从约定与流程 ,扩展到了类型、模块边界与协议出口。它没有变成另一个 NestJS------它仍然相信「约定优于配置」,只是给约定加了类型,给目录加了边界,给业务能力加了第二条出口。
值得选:中大型后端、多服务统一规范、已有 Egg 3.x、以及需要一个「顺手就能把业务暴露给大模型」的服务端框架的团队。
不建议选:极小 API、团队无法推进 Node 22.18+、或者已经很满意现有框架且没有模块化/AI 诉求的项目。
务实的路线 是老新并存:新业务模块用 Tegg(eggModule + DI + 装饰器),老代码保持传统 Controller/Service,通过少量 AccessLevel.PUBLIC 的服务做桥接。这样迁移的收益可验证、回退成本也低------这比一次性重写所有目录要可靠得多。
至于「AI 原生」这个标签,我的看法是:MCP Controller 是真实可用的生产力,Agent Runtime 则还在早期(进程内状态、预发布 API)。把它当「省下协议层搭建工作」的加速器,而不是「开箱即用的 AI 平台」,预期会更准。
参考与延伸阅读
- Egg 官方文档 · Egg 4 发布说明:https://eggjs.org/zh-CN/releases/egg-v4
- Egg 官方文档 · Tegg 模块化:https://eggjs.org/zh-CN/releases/egg-v4-tegg
- Egg 官方文档 · TypeScript 与 ESM:https://eggjs.org/zh-CN/releases/egg-v4-typescript-esm
- Egg 官方文档 · 插件升级:https://eggjs.org/zh-CN/releases/egg-v4-plugins
- Egg 官方文档 · Bundle 与启动快照:https://eggjs.org/zh-CN/releases/egg-v4-bundle-snapshot
- Egg 官方文档 · MCP Controller:https://eggjs.org/zh-CN/basics/mcpcontroller
- Egg 官方文档 · 快速入门:https://eggjs.org/zh-CN/intro/quickstart
- 仓库与 Release 记录:https://github.com/eggjs/egg/releases
本文涉及的性能数字(cnpmcore 冷启动 947 ms → 379 ms 等)来自 Egg 官方发布说明中引用的实测报告,属于特定硬件与特定应用条件下的官方参考值 ,不代表所有项目的通用收益,请以自有应用复测结果为准。版本号与 API 以官方文档和
npm view egg dist-tags的实时输出为准。
欢迎点赞 + 收藏 + 关注三连,你的支持是我持续输出深度技术内容的动力。