关于 swc-plugin-compatibility
用于排查 `@lingui/swc-plugin` 与 SWC 宿主版本兼容问题的技能。它指导开发者确认宿主版本、选择并固定兼容插件,并验证 Next.js、Vite 和 Rspack 配置中的宏转换。
主要功能
- 01识别实际解析的 SWC 宿主
- 02选择兼容的插件版本
- 03检查 `@lingui/core` 对等依赖
- 04固定插件的精确版本
- 05Babel 回退方案说明
适用场景
- 01排查 SWC Wasm 插件构建失败
- 02宿主升级后选择插件版本
- 03检查构建中的 Lingui 宏输出
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lingui/ skills --skill swc-plugin-compatibility安装后在 Claude Code 中输入/swc-plugin-compatibility确认已加载。
详细介绍
SWC 插件兼容性
如果你在构建期间看到类似以下错误:
failed to invoke plugin on 'Some("/app/src/file.ts")'
failed to run Wasm plugin transform. Please ensure the version of `swc_core` used by the plugin is compatible with the host runtime.
swc_plugin_runner: <n>
: Failed to deserialize program received from host
Failed to execute SWC plugin (Next.js / Turbopack wording of the same failure)
RuntimeError: out of bounds memory access (older hosts)
LayoutError called Result::unwrap() (older hosts)
这不是你代码或 Lingui 中的 bug。 已安装的 @lingui/swc-plugin 是针对 SWC 宿主无法运行的某个 swc_core 版本构建的。
为什么会发生这种情况
每个 @lingui/swc-plugin 版本都是针对一个固定的 swc_core 版本编译的——插件的兼容性表列出了这些版本。宿主(Next.js 内置的 SWC,或 @vitejs/plugin-react-swc / Rspack 解析到的 @swc/core)接受一定范围内的 swc_core 版本。超出该范围时,Wasm 插件无法反序列化传入的 AST,导致每个使用宏的文件都构建失败。
随着时间推移,SWC 已稳定了插件 ABI,因此版本不匹配的情况比以前少了,但宿主和插件仍可能各自超前对方(可参见 lingui/swc-plugin #250 和 #253,分别对应一种情况)。请务必检查这组版本是否匹配,不要想当然。
如何修复
步骤 1:确定宿主及其解析出的版本
| 项目中的线索 | 宿主 | 从哪里读取版本 |
|---|---|---|
依赖中有 next,且没有 .babelrc | Next.js 内置的 SWC | npm ls next |
@vitejs/plugin-react-swc | @swc/core | npm ls @swc/core — 以 锁文件解析结果为准,而非插件声明的 caret 范围 |
Rspack / 直接使用 @swc/core | @swc/core | npm ls @swc/core |
Next.js 内嵌了 SWC,会忽略 package.json 中的任何 @swc/core;只有 Next.js 版本会决定其 swc_core。在 Vite 项目中,即使两个项目的 package.json 完全相同,解析出的 @swc/core 版本也可能不同——锁文件才是事实依据。
步骤 2:选择插件版本
- 查阅宿主接受的
swc_core版本范围。错误消息通常会打印一个https://plugins.swc.rs/versions/from-plugin-runner/<n>URL——在浏览器中打开它;对于 Next.js,在 https://plugins.swc.rs 上选择运行时next和解析出的版本。该网站由客户端渲染,因此直接获取会返回空页面;此时可参考宿主自身固定的版本:对于 Next.js,查看vercel/next.js标签(v<version>)根目录Cargo.toml中的swc_core;对于@swc/core,查看其 GitHub 发行说明。 - 在插件的兼容性表中,选取
swc_core落在该范围内的最新@lingui/swc-plugin。 - 检查其 peer 依赖:
npm view @lingui/swc-plugin@<version> peerDependencies。每个插件主版本都要求对应的@lingui/core主版本作为必需 peer 依赖(较新的插件主版本也可能兼容前一个 core 主版本)。如果候选版本的 peer 依赖不包含已安装的@lingui/core,它就不是可用候选版本——安装会因ERESOLVE失败,而不是构建失败。选择下一个最新且符合要求的版本,或者转到“如果没有兼容版本怎么办?”。
兼容性网站过去曾落后于版本发布。如果表格与网站不一致,或宿主版本高于两者所列的版本,请通过构建来判断:先尝试最新的插件,再逐行回退一个 swc_core 版本,直到使用宏的文件能够编译。
宿主各自独立演进,因此同一个代码仓库中的两个宿主(一个 Next.js 应用和一个 Vite 应用)完全可能需要两个不同的插件版本。
步骤 3:固定精确版本并确认
{
"devDependencies": {
"@lingui/swc-plugin": "<exact version>"
}
}
请使用精确版本(不带 ^ 或 ~),以免常规依赖升级改变它。当使用宏的文件构建通过,且渲染后的输出显示的是翻译文本而非原始宏源码时,即表示完成。
保持正常运行
- 精确锁定插件版本,并将其排除在 Renovate/Dependabot 自动合并之外;在检查兼容性表后,再有计划地升级。
- 将宿主升级视为插件升级。 宿主会在次要版本和补丁版本中更改
swc_core,因此升级 Next.js 或@vitejs/plugin-react-swc时也需要进行同样的检查——将这两个包放入同一更新组。 - 升级任何涉及 SWC 或插件的内容后都要构建。 构建结果,而非更新日志,才是确认依据。
@lingui/swc-plugin 的版本不会跟随其他 @lingui/* 版本;它按自己的节奏发布,因此除了 peer 字段外,它的版本号并不包含其与哪个 @lingui/core 配套的信息。
示例:Next.js 配置
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
swcPlugins: [
['@lingui/swc-plugin', {
// Plugin options
}],
],
},
};
export default nextConfig;
示例:.swcrc 配置
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"experimental": {
"plugins": [
["@lingui/swc-plugin", {}]
]
}
}
}
示例:Vite 配置
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { lingui } from "@lingui/vite-plugin";
export default defineConfig({
plugins: [
react({ plugins: [["@lingui/swc-plugin", {}]] }),
lingui(),
],
});
静默失效:错误的插件条目形式
每个 SWC 插件条目都必须是 [name, options] 元组,即使选项为空也一样。若将插件名称作为普通字符串传入,宏转换会静默停用:构建成功,但 <Trans> 不会被解析,消息会以原始宏输出的形式呈现。
// ❌ Silently does nothing
react({ plugins: ["@lingui/swc-plugin"] })
// ✅ Tuple with options object
react({ plugins: [["@lingui/swc-plugin", {}]] })
Next.js 的 swcPlugins 和 .swcrc 的 plugins 数组也同样如此。
如果没有兼容版本怎么办?
如果没有某个插件版本能与项目的 @lingui/core 一起安装,且又兼容宿主,请明确做出选择,而不是猜测:
- 迁移宿主版本,选择其
swc_core范围内存在匹配插件的版本(通常是升级:较新的宿主通常接受较新的插件) - 改用 Babel 转换(
@lingui/babel-plugin-lingui-macro)——请参阅下方注意事项 - 在 https://github.com/lingui/swc-plugin 提交 issue 或 PR
强制安装(--legacy-peer-deps)或为了满足旧插件的 peer 依赖而降级 @lingui/core 都不在此列:前者会掩盖不匹配问题,直到构建时才暴露;后者则是为了迁就构建工具而让运行时版本倒退。
Next.js 上的 Babel 回退方案
npm install -D @lingui/babel-plugin-lingui-macro babel-plugin-macros @babel/types
// .babelrc
{ "presets": ["next/babel"], "plugins": ["macros"] }
移除 experimental.swcPlugins 条目。Babel 路径需要具备以下三项,而 SWC 插件不需要:
- 显式安装
@babel/types。 Next 会捆绑自己的 Babel,但不会将其提供给插件;如果没有它,构建会因Cannot find package '@babel/types'而失败。 - 提供
lingui.config.ts(或.js/.mjs)。 Babel 宏会在编译时读取该配置;SWC 插件不会,因此之前无需配置文件也能运行的项目在这里会出问题。 - 接受
.babelrc会为整个项目关闭 Next 的 SWC 编译器——Next 会输出Disabled SWC as replacement for Babel because of custom Babel configuration,此后next.config中的compiler选项也会被忽略。构建速度会变慢;如果可以,优先迁移宿主版本。
Babel 回退注意事项:@vitejs/plugin-react 6
@vitejs/plugin-react@6 移除了内部的 Babel,并且彻底移除了 babel 选项。在 v6 及更高版本中,传统写法:
react({ babel: { plugins: ["@lingui/babel-plugin-lingui-macro"] } })
已不再有效——TypeScript 配置会因 TS2353(“babel does not exist in type...”)而失败;普通 JS 配置则会被静默忽略,宏不会被转换。
在 Vite 上,你有以下选择:
- 继续使用 SWC:搭配
@vitejs/plugin-react-swc和按照上述步骤选择并精确锁定的@lingui/swc-plugin(推荐) - 将宏作为独立的 Babel 阶段运行(Vite 8 使用
@rolldown/plugin-babel,Vite ≤ 7 使用vite-plugin-babel)——lingui-framework-setup skill 中介绍了接线配置 - 将
@vitejs/plugin-react@^5锁定到仍支持babel选项的版本