npm link
核心论点:pnpm link 验证的是本地源码,不是发布产物——两套验证面必须各走各的。
- link 视角:指向本地源仓库,src/ 里有什么就能解析什么。
- publish 视角:files 白名单外的目录全不进 tarball。
- CI 视角:装的是 npm 上的发布包,看不到未列进 files 的源码。
- 缺失验证:
pnpm pack解包 grep,文件齐全才算发布面 OK。 - 踩坑信号:link 全绿、CI 找不到 module,99% 是 files 漏了。
症状:本地 link 全绿,CI 一跑就挂
我新加了两个目录 src/plugin-root/ 和 src/postcss-root-scope-plugin/,exports 路径写好,pnpm link @your-org/your-pkg,下游工程跑测试全过。
推到 CI 一跑:
Error: Cannot find module '@your-org/your-pkg/plugin-root'
懵了 5 分钟——链接能解析,本地一切正常,CI 怎么就找不到?
pnpm link 到底验证了什么
pnpm link 在下游工程的 node_modules 里建一个软链,指向本地的源仓库目录:
node_modules/@your-org/your-pkg -> /path/to/local/your-pkg
软链指向的是源仓库,仓库里所有文件都在——src/、tests/、docs/、.editorconfig、刚加的 src/plugin-root/、还有 src/postcss-root-scope-plugin/,一个不少。
所以 link 通过 = 本地源仓库里能找到这个模块路径,仅此而已。
CI 拿到的是发布包,不是源码
CI 不是从 Git 拉源码跑的,它装的是 npm registry 上 的包。npm publish 干了什么?
- 读
package.json的files字段 - 把仓库里白名单内的文件打个 tarball
- 上传到 registry
剩下的全扔掉——tests/、docs/、.github/、你新加但没列进 files 的 src/plugin-root/,一个不剩。
我当时的 files 长这样:
{
"files": [
"dist",
"README.md"
]
}
src/plugin-root/ 和 src/postcss-root-scope-plugin/ 都在仓库里,但 files 没列。结果:
- 本地 link:通过(指向源仓库)
- publish/install:挂(tarball 里压根没有这两个目录)
这不是 link 的 bug,是两套验证面
link 验证的是源仓库视角:
- exports 路径写法对不对
- 本地源码是否可解析
publish 验证的是产物视角:
- files 白名单覆盖了哪些路径
- 打包后产物是否真的带上这些文件
我之前 link 全过就以为万事大吉,漏掉了发布面那一半。
缺失的一步:pnpm pack
正确的发布前验证应该补一步:
pnpm pack
tar -tzf *.tgz | grep -E '(plugin-root|postcss-root-scope-plugin)'
pnpm pack 在仓库根目录生成一个 .tgz,完全模拟 npm publish 会打的包。解出来看一眼 tarball 里到底有哪些文件:
package/
package/dist/
package/dist/index.js
package/README.md
package/package.json
# 没有 package/src/plugin-root/
# 没有 package/src/postcss-root-scope-plugin/
一目了然——files 白名单漏了,立刻补,不用等到 CI 跑挂。
本地 link 全绿、CI 一跑就 Cannot find module 且模块路径你确定写过——99% 是 files 白名单漏了,别去翻 exports 别去翻 tsconfig,第一时间 pnpm pack 解包验证。
修复方案
补 files 白名单:
{
"files": [
"dist",
"src",
"README.md"
]
}
如果你构建产物是 dist/,那发布的是 dist/ 而不是 src/。把 src 列进去等于发布未编译源码,下游装完还要走你的 build pipeline 才能用——不是不能用,是非常反直觉。
更稳妥的做法:把运行时需要的路径显式列出来:
{
"files": [
"dist",
"src/plugin-root",
"src/postcss-root-scope-plugin",
"README.md"
]
}
把 pack 验证加进 CI
link 测试 + pack 测试,两条腿走 路:
# .github/workflows/release.yml
- name: Pack check
run: |
pnpm pack
tar -tzf *.tgz | grep -q "package/dist/plugin-root" || \
(echo "files whitelist missing plugin-root" && exit 1)
或者挂个 prepublishOnly script:
{
"scripts": {
"prepublishOnly": "pnpm pack && tar -tzf *.tgz | grep -E 'plugin-root|postcss-root-scope-plugin'"
}
}
prepublishOnly 会在 publish 前自动跑,漏配 files 直接 publish 失败。
教训总结
link 是开发体验工具,不是发布验证工具。它给你一个"本地能跑"的假象,但本地源仓库和发布产物是两回事。
发布前的最小验证清单:
pnpm link—— 验证 exports 路径和源码可解析pnpm pack+ 解包 grep —— 验证 files 白名单pnpm publish --dry-run—— 模拟完整 publish 流程
本地 link 通过 ≠ 可以发布。两个验证面必须各走一遍:link 看源码,pack 看产物。缺任何一半,CI 都会替你补上,但补上的方式是 Module not found。
References
- npm pack 文档 —— npm 官方
- package.json files 字段 —— npm 官方