← 中文资源

简体中文版本

Node.js — 平台专属可选依赖报 Cannot Find Module

理解 optionalDependencies 的安装行为并在 CI 锁定平台产物,修复 @swc/core-linux-x64-gnu 之类的 Cannot find module 错误。

阅读英文原文

症状

模块名本身就包含平台三元组,这让诊断比大多数错误快:Cannot find module ‘@swc/core-linux-x64-gnu’。require stack 显示是包装层在请求平台产物,而检查 node_modules/@swc/ 目录会发现 wrapper 存在、它要 require 的产物却不在——这是安装期的决策问题,不是代码缺陷。macOS 构建成功、Linux CI 失败,或 arm64 构建产物跑到 amd64 运行时镜像里,都是同一机制的变体。

诊断思路

<arg_value>先列出平台变体实际安装了哪些、npm ls 缺哪个;再输出本机三元组 process.platform + process.arch + libc 版本,与工具链要 require 的变体严格比对——glibc 与 musl 不匹配是 Alpine 上的经典坑;最后检查 .npmrc 与 package-lock.json,omit=optional 配置或锁文件丢失 linux 变体条目都解释了二进制为何从未装上。锁文件是在别的平台上生成并被裁剪过的,也会导致同样结果。

根因

原生加速包为每个目标平台把一个二进制声明为 optionalDependencies,让单一 package.json 服务所有操作系统。被 require 的模块只有在匹配的平台产物装上后才存在,而安装环境因为标记、锁文件或架构不匹配跳过了它。

修复与验证

<arg_value>让安装环境与运行环境对齐:Dockerfile 里在运行时镜像同三元组阶段执行 npm ci 或从相同架构的 deps 阶段复制 node_modules;清理 .npmrc 与环境变量里的 omit=optional;锁文件丢了平台条目就在 Linux 上重新生成;Alpine 镜像要么换 glibc 兼容基础镜像,要么确保 -musl 变体被安装。验证时在最终运行镜像里执行 node -e “require(‘@swc/core-linux-x64-gnu’)”,退出码为零才说明产物解析成功,再跑一次真实构建。把这个 require 探针加进 CI,基础镜像悄悄换 libc 时会在安装阶段报错而不是首次编译时。注意:把平台二进制钉成硬依赖会让其他平台的安装全部失败,正确做法是修环境而不是改依赖结构。

代码示例

Error: Cannot find module '@swc/core-linux-x64-gnu'
Require stack:
- /app/node_modules/@swc/core/index.js
ls node_modules/@swc/
# core            <- wrapper, present
# core-darwin-arm64? no, this is linux CI
# (missing the linux-x64-gnu artifact)
FROM node:22-bookworm-slim AS deps
RUN npm ci                # lockfile includes linux-x64-gnu variant

FROM node:22-bookworm-slim AS runtime
COPY --from=deps /app/node_modules ./node_modules