Koa3.1.2 迁移, 持续更新中

0 阅读5分钟

Koa 2 → Koa 3 迁移指南

基于 Koa 3.1.2 源码分析,适用于生产项目迁移参考


版本概览

Koa 2        Koa 3
────────     ────────────────────────────────
Node 7.6+    Node 18+(LTS 强制要求)
CJS only     CJS + ESM 双模式
generator    generator 支持完全移除
redirect()   ctx.back() 替代 redirect('back')
querystring  URLSearchParams(标准 API)
无内置 ALS   内置 AsyncLocalStorage 支持

破坏性变更(Breaking Changes)

1. Node.js 最低版本:>= 18

Koa 2Koa 3
最低版本Node.js >= 7.6.0Node.js >= 18.0.0
原因仅需 async/await使用 Fetch API、Blob、ReadableStream 等原生标准
# 迁移前检查
node --version   # 必须 >= 18.0.0

2. Generator 中间件彻底移除

Koa 1.x 风格的 generator 中间件在 Koa 2 中已废弃(需 koa-convert),Koa 3 直接删除,不再报错也不再兼容

// ❌ Koa 1/2(generator 风格)—— Koa 3 中完全失效
app.use(function *(next) {
  yield next;
});

// ✅ Koa 3 唯一支持的写法
app.use(async (ctx, next) => {
  await next();
});

迁移动作:全局搜索 function *,全部改为 async/await,移除 koa-convert 依赖。


3. ctx.redirect('back') 移除 → 改用 ctx.back()

Koa 3 移除了 redirect('back') 的魔法字符串,新增更安全的 ctx.back() 方法(内置同源校验,防止开放重定向攻击)。

// ❌ Koa 2
ctx.redirect('back');

// ✅ Koa 3 —— 必须提供 fallback 路径
ctx.back('/');
ctx.back('/dashboard');

ctx.back() 的安全机制

  • Referer 头 → 检查是否同源,同源才重定向
  • Referer 或跨域 → 重定向到 alt 参数(默认 /

4. ctx.origin 语义变更

// 请求头:Host: example.com, Origin: https://app.example.com

// Koa 2:返回当前服务器的 protocol + host
ctx.origin  // → 'https://example.com'

// Koa 3:返回请求中的 Origin 头(无 Origin 头时返回 null)
ctx.origin  // → 'https://app.example.com'

影响场景:CORS 检查、日志记录中用到 ctx.origin 的逻辑需要重新评估。


5. ENOENT 错误不再自动处理为 404

// Koa 2:fs 抛出 ENOENT 错误会被自动转为 404 响应
// Koa 3:ENOENT 会穿透到全局错误处理,需要手动判断

// ✅ Koa 3 迁移写法(在 error-handler 中)
if (err.code === 'ENOENT') {
  ctx.status = 404;
  ctx.body = { message: 'Not Found' };
  return;
}

6. ctx.body = null 行为变更

// Koa 2
ctx.body = null;  // → 204 No Content,无 Content-Length

// Koa 3
ctx.body = null;  // → 设置 Content-Length: 0,状态码取决于上下文
                  // 若 Content-Type 是 application/json → body 写为字符串 "null"

7. QueryString 解析模块替换

底层从 Node.js 废弃的 querystring 模块切换到标准 URLSearchParams,解析行为有细微差异:

// 差异示例
// 输入:?a=1&a=2

// Koa 2(querystring)
ctx.query  // → { a: ['1', '2'] }

// Koa 3(URLSearchParams)
ctx.query  // → { a: '2' }   ← 重复键只保留最后一个

影响场景:使用同名多值查询参数(如 ?ids=1&ids=2)的接口需要改用 ctx.request.querystring 手动解析。


新增特性

1. 内置 AsyncLocalStorage 支持

Koa 3 原生集成 AsyncLocalStorage,无需第三方库即可在任意位置访问当前请求上下文。

import Koa from 'koa';

// 启用内置 ALS
const app = new Koa({ asyncLocalStorage: true });

// 在任意模块(service、logger 等)获取当前请求 ctx
const ctx = app.currentContext;

本项目已在 src/core/context-storage.ts 中手动实现了同等功能,Koa 3 内置方案可作为替代。


2. 现代 Web 标准 Body 类型

ctx.body 现在支持 Fetch API 原生类型,无需手动转换:

// ✅ Koa 3 新增支持

// Blob
ctx.body = new Blob(['hello'], { type: 'text/plain' });

// ReadableStream(流式响应)
ctx.body = new ReadableStream({
  start(controller) {
    controller.enqueue('chunk1');
    controller.close();
  }
});

// Fetch API Response(直接代理上游响应)
const upstream = await fetch('https://api.example.com/data');
ctx.body = upstream;   // 直接转发,含状态码和 headers

实际价值:做 BFF(Backend for Frontend)或 API 网关时,可以直接 ctx.body = await fetch(...) 转发上游响应,极大简化代码。


3. ESM 原生支持

// koa/package.json exports 字段
{
  "exports": {
    ".": {
      "require": "./lib/application.js",  // CJS
      "import":  "./dist/koa.mjs"         // ESM
    }
  }
}
// ✅ 直接使用 ESM import(本项目已采用此方式)
import Koa from 'koa';

4. HTTP/2 改进

// Koa 3 自动处理 HTTP/2 的 :authority 伪头
// request.js 内部逻辑:
if (req.httpVersionMajor >= 2) {
  host = this.get(':authority');  // HTTP/2
} else {
  host = this.get('Host');        // HTTP/1.1
}

// response.js:HTTP/2 不发送 statusMessage(协议不支持)
if (req.httpVersionMajor < 2) {
  res.statusMessage = statuses.message[code];
}

TypeScript 类型变更

ctx.body 类型从 any 改为 unknown

// Koa 2 @types/koa
body: any;

// Koa 3 @types/koa
body: unknown;

这意味着直接读取 ctx.body 的代码需要类型断言或类型守卫:

// ❌ Koa 3 中会报类型错误
const id = ctx.body.id;

// ✅ 使用泛型 ParameterizedContext 约束响应类型
import type { ParameterizedContext } from 'koa';

type MyCtx = ParameterizedContext<
  DefaultState,
  DefaultContext,
  { id: number; name: string }   // ResponseBodyT
>;

迁移检查清单

环境准备

  • Node.js 升级到 >= 18.0.0
  • 更新 package.jsonengines 字段
  • 更新 koa^3.0.0
  • 更新 @types/koa^3.0.0
  • 移除 koa-convert(不再需要)

代码改造

  • 搜索 function *,全部改为 async/await
  • 搜索 redirect('back'),改为 ctx.back('/fallback')
  • 检查 ctx.origin 的使用,确认语义是否变化
  • 检查 ENOENT 错误处理,在 error-handler 中补充 404 逻辑
  • 检查同名多值查询参数(?a=1&a=2)的解析逻辑
  • 检查 ctx.body = null 的处理逻辑
  • 修复 ctx.body 类型从 any 改为 unknown 带来的 TS 错误

依赖检查

  • 检查所有 Koa 中间件是否支持 Koa 3(重点检查 koa-router@koa/router
  • 检查是否有中间件内部使用了 koa-convert

中间件生态兼容性

中间件Koa 3 状态说明
@koa/router✅ 兼容官方维护,推荐替代 koa-router
koa-bodyparser✅ 兼容继续可用
koa-static⚠️ 注意需手动处理 ENOENT → 404
koa-cors / @koa/cors✅ 兼容-
koa-router(非官方)⚠️ 停止维护迁移到 @koa/router
koa-convert❌ 移除Koa 3 不再需要
koa-compose✅ 兼容Koa 3 内部依赖

变更速查表

特性Koa 2Koa 3操作
Node.js 版本>= 7.6>= 18升级环境
Generator 中间件废弃警告完全移除改写代码
redirect('back')支持移除ctx.back()
ctx.origin服务器 originRequest Origin 头检查逻辑
ENOENT → 404自动手动补充处理
ctx.body = null204取决于 Content-Type检查逻辑
QueryString 重复键数组最后一个值检查接口
ctx.body 类型anyunknown修复 TS
Blob/ReadableStream不支持原生支持按需使用
AsyncLocalStorage需第三方内置按需迁移
ESM有限原生按需使用
HTTP/2基础增强按需使用