← Debugging Guides

阅读中文版本

Node.js — Cannot Find Module for a Platform-Specific Optional Dependency

Fix Cannot find module errors like @swc/core-linux-x64-gnu by understanding optionalDependencies install behavior and locking the platform artifact in CI.

intermediatedebuggingnodejsnpmnative-modules

Symptoms

  • Error: Cannot find module '@swc/core-linux-x64-gnu' on Linux CI while macOS builds succeed
  • The failure occurs inside a native-accelerated toolchain: swc, lightningcss, rolldown, or esbuild shims
  • node_modules/@swc/core exists, but the platform binary package beneath it does not

Possible causes

  • The platform binary is an optionalDependency that npm skips when the CPU, libc, or OS marker does not match the installing machine
  • The lockfile was generated on a different platform and platform-specific optional entries were pruned
  • A package manager flag (omit optional or a strict pnpm config) disabled optional dependency installation
  • The deployment image installs with a different architecture than the build stage, for example arm64 build and amd64 runtime

Diagnosis steps

Check which platform variants are present

ls node_modules/@swc/ 2>/dev/null; npm ls @swc/core-linux-x64-gnu 2>&1 | head -5

Interpretation

A present core package with missing or mismatched platform subpackage confirms the optional artifact was skipped, not that the package is broken.

Verify the marker against the runtime platform

node -p "process.platform + ' ' + process.arch + ' ' + (process.report.getReport().header.glibcVersionRuntime || 'musl')"

Interpretation

The triple (platform, arch, libc) must match the variant the toolchain tries to require; glibc versus musl mismatches are the classic Alpine case.

Inspect install flags and lockfile entries

grep -n "omit" .npmrc package.json; grep -n "@swc/core-linux" package-lock.json | head -5

Interpretation

omit=optional in config or a lockfile missing the linux variant explains why the binary never installs.

Root cause

Native-accelerated packages declare one platform binary per target as optionalDependencies so a single package.json can serve every OS. The required() module exists only when the matching platform artifact was installed, and the installing environment skipped it because of a marker, lockfile, or architecture mismatch.

Fix

Install on the target platform with the same package manager and lockfile, keep build and runtime stages on the same architecture, remove omit-optional flags, and for Alpine-based images use the glibc-compatible variant or the musl build explicitly.

Risk note

Pinning the binary as a hard dependency makes every other platform install fail. Keep the optional pattern and fix the environment instead.

Verify the fix

Run npm ci inside the exact runtime image and require the native entry directly: node -e "require('@swc/core-linux-x64-gnu')" must exit zero before the toolchain is trusted.

Symptom walkthrough

The module name itself contains the platform triple, which makes the diagnosis faster than most:

Error: Cannot find module '@swc/core-linux-x64-gnu'
Require stack:
- /app/node_modules/@swc/core/index.js

The require stack shows the wrapper asked for the platform artifact. Inspect what actually landed:

ls node_modules/@swc/
# core            <- wrapper, present
# core-darwin-arm64? no, this is linux CI
# (missing the linux-x64-gnu artifact)

The wrapper is present but the artifact it requires is not, which is an install-time decision, not a code bug.

Fix in practice

Align the install environment with the runtime environment. In a Dockerfile, install inside the runtime stage or copy node_modules built on the same triple:

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

Clear the common blockers: remove omit=optional from .npmrc and environment, regenerate the lockfile on Linux if it lost platform entries, and never build on arm64 for an amd64 runtime. On Alpine, the libc suffix matters — either use a glibc-based image or ensure the -musl variant installs.

Verification

In the exact runtime image, run the most direct possible assertion: node -e “require(‘@swc/core-linux-x64-gnu’)” exiting zero proves the artifact resolved. Then run one real build through the toolchain. Add this require probe to CI so a base image change (for example slim switching libc) fails loudly at install time rather than at first compile.