mcpField
S
swc-plugin-compatibility

swc-plugin-compatibility

github.com/lingui开发10280 次安装1 周前更新
代码仓库

关于 swc-plugin-compatibility

用于排查 `@lingui/swc-plugin` 与 SWC 宿主版本兼容问题的技能。它指导开发者确认宿主版本、选择并固定兼容插件,并验证 Next.js、Vite 和 Rspack 配置中的宏转换。

主要功能

  1. 01识别实际解析的 SWC 宿主
  2. 02选择兼容的插件版本
  3. 03检查 `@lingui/core` 对等依赖
  4. 04固定插件的精确版本
  5. 05Babel 回退方案说明

适用场景

  1. 01排查 SWC Wasm 插件构建失败
  2. 02宿主升级后选择插件版本
  3. 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,且没有 .babelrcNext.js 内置的 SWCnpm ls next
@vitejs/plugin-react-swc@swc/corenpm ls @swc/core — 以 锁文件解析结果为准,而非插件声明的 caret 范围
Rspack / 直接使用 @swc/core@swc/corenpm ls @swc/core

Next.js 内嵌了 SWC,会忽略 package.json 中的任何 @swc/core;只有 Next.js 版本会决定其 swc_core。在 Vite 项目中,即使两个项目的 package.json 完全相同,解析出的 @swc/core 版本也可能不同——锁文件才是事实依据。

步骤 2:选择插件版本
  1. 查阅宿主接受的 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 发行说明。
  2. 在插件的兼容性表中,选取 swc_core 落在该范围内的最新 @lingui/swc-plugin。
  3. 检查其 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>"
  }
}

请使用精确版本(不带 ^ 或 ~),以免常规依赖升级改变它。当使用宏的文件构建通过,且渲染后的输出显示的是翻译文本而非原始宏源码时,即表示完成。

保持正常运行

  1. 精确锁定插件版本,并将其排除在 Renovate/Dependabot 自动合并之外;在检查兼容性表后,再有计划地升级。
  2. 将宿主升级视为插件升级。 宿主会在次要版本和补丁版本中更改 swc_core,因此升级 Next.js 或 @vitejs/plugin-react-swc 时也需要进行同样的检查——将这两个包放入同一更新组。
  3. 升级任何涉及 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 一起安装,且又兼容宿主,请明确做出选择,而不是猜测:

  1. 迁移宿主版本,选择其 swc_core 范围内存在匹配插件的版本(通常是升级:较新的宿主通常接受较新的插件)
  2. 改用 Babel 转换(@lingui/babel-plugin-lingui-macro)——请参阅下方注意事项
  3. 在 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 插件不需要:

  1. 显式安装 @babel/types。 Next 会捆绑自己的 Babel,但不会将其提供给插件;如果没有它,构建会因 Cannot find package '@babel/types' 而失败。
  2. 提供 lingui.config.ts(或 .js/.mjs)。 Babel 宏会在编译时读取该配置;SWC 插件不会,因此之前无需配置文件也能运行的项目在这里会出问题。
  3. 接受 .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 选项的版本

评论

支持 Markdown