Express 5 迁移:app.del 直接抛错、通配符必须命名、async 抛错自动进错误中间件
Express 5 的 API 和 Express 4 基本一致,破坏性变更集中在两类:一类是用了就崩的移除项,一类是不崩但行为变了的地方。迁移前先看官方指南:Upgrade to Express v5。
安装与 codemod
Express 5 需要 Node.js 18 或更高版本。
npm install "express@5"
装完先跑一遍自动化测试,看哪些用例挂了。
codemod 可以批量处理大部分机械改写:
# 全部 recipe
npx codemod@latest @expressjs/v5-migration-recipe
# 指定单个 recipe
npx codemod@latest @expressjs/name-of-the-codemod
下面每一项都标了对应的 codemod 名,可以单独跑。
移除的方法和属性
这些 API 在 Express 5 里已经不存在,调用会让应用直接崩掉。
app.del() 已移除,改用 app.delete()。 当年用 del 是因为 delete 是保留字,ES6 之后已经允许它作为属性名。codemod:@expressjs/route-del-to-delete。
// 旧
app.del('/user/:id', (req, res) => { ... });
// 新
app.delete('/user/:id', (req, res) => {
res.send(`DELETE /user/${req.params.id}`);
});
app.param(fn) 已移除,这个签名从 v4.11.0 起就废弃了。router.param(fn) 同样移除。
复数化的方法名。 codemod:@expressjs/pluralize-method-names。
req.acceptsCharset()→req.acceptsCharsets()req.acceptsEncoding()→req.acceptsEncodings()req.acceptsLanguage()→req.acceptsLanguages()
app.param(name, fn) 的 name 带前导冒号会被静默忽略,实际使用去掉前缀后的名字。
req.param(name) 已移除。 这个方法同时从表单、路由、查询串里取值,容易被误用。现在必须明确指定来源,从 req.params、req.body 或 req.query 里取。codemod:@expressjs/explicit-request-params。
res.json(obj, status) / res.jsonp(obj, status) 已移除。
// 旧
res.json(obj, status);
res.jsonp(obj, status);
// 新
res.status(status).json(obj);
res.status(status).jsonp(obj);
res.redirect(url, status) 已移除,参数顺序反过来。 codemod:@expressjs/redirect-arg-order。默认 302 的用法不受影响。
// 旧
res.redirect('/dashboard', 301);
// 新
res.redirect(301, '/dashboard');
// 默认 302,不用改
res.redirect('/dashboard');
res.redirect('back') 和 res.location('back') 的魔术字符串已移除,改成显式读取 Referrer。codemod:@expressjs/back-redirect-deprecated。
// 旧
res.redirect('back');
// 新
res.redirect(req.get('Referrer') || '/');
res.send(body, status) 和 res.send(status) 已移除。
// 旧
res.send({ ok: true }, 201);
res.send(204);
// 新
res.status(201).send({ ok: true });
res.sendStatus(204);
// 要发数字,就当成字符串发
res.send('204');
res.sendfile() 换成驼峰命名的 res.sendFile()。 codemod:@expressjs/camelcase-sendfile。
res.sendFile() 的选项里 hidden 和 from 不再支持,改用 dotfiles 和 root。dotfiles 对隐藏目录同样生效:/var/www/app/.cache/index.html 现在需要 dotfiles: 'allow',即便 index.html 本身不是点文件。Express 4 会默认返回路径里的隐藏目录,Express 5 不显式开启就返回 404。检查只覆盖 send 实际求值的那段路径——传了 root 时,只检查相对 root 的部分。
express.static() 的选项里 hidden 和 from 不再支持,改用 dotfiles 和 root。from 从来没写进文档,只是作为 root 的别名被接受。dotfiles 默认值现在是 "ignore",GET /.well-known/acme-challenge/... 这类请求在 Express 4 默认能返回,Express 5 不带 dotfiles: 'allow' 就是 404。只影响相对 root 的路径部分,root 自身里藏着的目录不受影响。
express.static.mime 不再是导出属性,改用 mime-types 包。
// 旧
express.static.mime.lookup('json');
// 新
const mime = require('mime-types');
mime.lookup('json');
MIME 类型变化。 Express 4 使用 mime-db 1.52.0,Express 5 用的是更新的版本。最明显的一处是 .js 文件现在以 text/javascript 返回,而不是 application/javascript。只影响 express.static() 和 res.sendFile()。MIME 变化不算破坏性变更,可能在小版本或补丁版本之间继续变。
express:router 系列的 debug 日志搬到了独立的 router 依赖命名空间。
| 版本 | 命名空间 |
|---|---|
| v4 | express:router、express:router:layer、express:router:route |
| v5 | router、router:layer、router:route |
Express 5.1 之前这些日志并不存在。
# v4
DEBUG=express:* node index.js
# v5
DEBUG=express:*,router,router:* node index.js
行为变更
这些 API 还在,只是行为不一样。
路由 path 匹配语法
改动作用在 app.all、app.use、app.METHOD、router.* 的第一个参数上。
通配符 * 必须命名。 用 /*splat 代替 /*,命名规则和参数 : 一致(参数名本身也可以是合法 JS 标识符,或用 :"this" 这样的引号形式)。注意 *splat 不匹配根路径,要连根路径一起匹配就写 /{*splat}。
// 旧
app.get('/*', ...);
// 新
app.get('/*splat', ...); // 不匹配根路径
app.get('/{*splat}', ...); // 匹配根路径
可选字符 ? 不再支持,改用花括号。
// 旧
app.get('/:file.:ext?', ...);
// 新
app.get('/:file{.:ext}', ...);
正则字符不再支持。
// 旧
app.get('/[discussion|page]/:slug', ...);
// 新
app.get(['/discussion/:slug', '/page/:slug'], ...);
()[]?+! 这些字符在升级期间被保留,避免歧义,需要用时加 \ 转义。
rejected promise 会转给错误中间件
中间件和路由处理函数返回 rejected promise 时,现在会被当成一个 Error 转发给错误处理中间件。async 处理函数写起来更直接:async 函数里抛出的错误,或 await 了一个 rejected promise,都会像调用了 next(err) 一样进错误处理,参考错误处理文档。
// 旧
app.get('/user/:id', (req, res, next) => {
getUserById(req.params.id)
.then((user) => res.send(user))
.catch(next);
});
// 新
app.get('/user/:id', async (req, res) => {
const user = await getUserById(req.params.id);
res.send(user);
});
express.urlencoded 的 extended 默认值
extended 现在默认为 false。依赖嵌套对象的表单解析要显式打开。
// 旧
app.use(express.urlencoded());
// 新
app.use(express.urlencoded({ extended: true }));
express.static 的 dotfiles 默认值
dotfiles 现在默认 "ignore"。Express 4 默认会返回点文件,Express 5 里以点开头的目录(比如 .well-known)内的文件一律 404。即使之前从没用过 hidden 选项,也可能需要加上这一项。
// 旧
app.use(express.static('public'));
// 新
app.use(express.static('public', { dotfiles: 'allow' }));
改完再跑一遍测试,对照上面的清单逐项核对手工改动的部分。