Taro 4 项目从 npm 迁移到 pnpm 踩坑实录
最近将一个基于 Taro 4 + React + TDesign 的微信小程序项目从 npm 迁移到 pnpm,原本只是想利用 pnpm 的依赖缓存机制提升安装效率,减少磁盘占用。
没想到迁移过程中连续遇到了几个问题:先是开发环境无法启动,修复后又出现微信小程序上传缺少依赖,最后上传阶段不断在不同的 JS 文件上报 undefined 错误。
项目主要依赖版本如下:
| 依赖 | 版本 |
|---|---|
| Taro | 4.3.0 |
| pnpm | 12.18.0 |
| React | 18.3.1 |
| TDesign Miniprogram | 1.16.0 |
| @tarojs/plugin-mini-ci | 4.3.1 |
| miniprogram-ci | 1.9.17 |
| Webpack | 5.91.0 |
迁移后,首先使用 pnpm 安装项目依赖:
pnpm install
依赖安装过程没有明显异常,但运行微信小程序开发命令时:
pnpm dev:weapp
却发现项目无法正常启动。
为了排查问题,将终端日志重定向到文件:
lua
pnpm dev:weapp *> pnpm-error.log
打开 pnpm-error.log 后发现,Taro 生成的 Webpack 配置中,@tarojs/runtime 和 @tarojs/shared 的解析结果都是 null,导致 Webpack 无法正常启动。
这是迁移中遇到的第一个问题。
解决开发环境启动问题后,继续执行微信小程序上传命令:
arduino
pnpm run build:weapp:upload
这次又出现了新的错误:
请安装依赖:miniprogram-ci
补齐依赖后,构建可以正常完成,但上传阶段又陆续出现以下错误:
vbnet
done: app.js
processing: miniprogram_npm/tdesign-miniprogram/button/index.js
错误 上传失败
app.js: undefined
[ELIFECYCLE] Command failed with exit code 1.
再次执行,报错文件可能变成:
bash
错误 上传失败
miniprogram_npm/tdesign-miniprogram/badge/badge.js: undefined
甚至是:
javascript
错误 上传失败
common.js: undefined
这些错误并不是同一个文件固定报错,容易让人误以为是 TDesign 组件、Webpack 编译产物或者微信小程序上传工具本身出现了问题。
经过逐步排查,最终的处理方式是:显式补充 Taro 依赖、安装小程序 CI 上传依赖,以及调整 pnpm 的依赖布局模式。
解决方案
1. 解决 Webpack 无法解析 Taro 依赖的问题
迁移后,首先遇到的问题是:
pnpm dev:weapp
无法正常启动。
通过 PowerShell 将日志保存到文件:
lua
pnpm dev:weapp *> pnpm-error.log
在日志中发现,Taro 生成的 Webpack 配置里,两个模块的解析结果为 null:
less
@tarojs/runtime: null
@tarojs/shared: null
pnpm 默认采用严格的依赖隔离机制。与 npm 相对扁平的 node_modules 不同,pnpm 不会默认让所有间接依赖都能被项目直接访问。
如果项目或构建工具需要直接解析某些依赖,但项目没有显式声明,就可能出现模块解析失败。
针对这次遇到的问题,直接安装与当前 Taro 版本一致的依赖:
sql
pnpm add @tarojs/runtime@4.3.0 @tarojs/shared@4.3.0
安装完成后,重新启动:
pnpm dev:weapp
此时开发环境恢复正常。
需要注意的是,@tarojs/runtime 和 @tarojs/shared 应尽量与项目中的 @tarojs/taro 保持版本一致,避免引入额外的版本冲突。
2. 解决微信小程序上传缺少 miniprogram-ci 的问题
开发环境启动成功后,继续执行上传:
arduino
pnpm run build:weapp:upload
项目对应的脚本配置为:
json
{
"scripts": {
"dev:weapp": "taro build --type weapp --watch",
"build:weapp": "taro build --type weapp",
"build:weapp:upload": "taro build --type weapp --upload"
}
}
但执行时提示:
请安装依赖:miniprogram-ci
这是因为 Taro 的小程序 CI 上传流程需要使用 miniprogram-ci。
在 npm 环境中,某些依赖可能通过间接依赖的方式被访问到;迁移到 pnpm 后,这类未显式声明的依赖更容易暴露出来。
解决方式是将 miniprogram-ci 显式安装为开发依赖:
sql
pnpm add -D miniprogram-ci@1.9.17
这里使用的是本次项目的版本。其他项目应根据当前 Taro CI 插件的兼容要求选择对应版本。
安装完成后,再次执行:
arduino
pnpm run build:weapp:upload
这次不再提示缺少 miniprogram-ci,但又出现了新的上传错误。
3. 解决上传阶段 JS 文件报 undefined 的问题
安装 miniprogram-ci 后,项目已经能够正常编译,但在上传阶段出现:
javascript
错误 上传失败
app.js: undefined
随后多次尝试,错误文件又变成:
bash
miniprogram_npm/tdesign-miniprogram/badge/badge.js: undefined
或者:
javascript
common.js: undefined
这里有两个比较明显的特点:
- Taro 构建可以正常完成,问题集中在上传流程。
- 报错涉及不同的 JS 文件,并不是某个固定组件始终编译失败。
这时需要考虑 pnpm 与 npm 在依赖布局上的差异。
pnpm 默认使用 isolated 模式,通过虚拟存储目录及链接管理依赖,安装后通常可以看到:
kotlin
node_modules/
├── .pnpm/
│ ├── @tarojs+cli@4.3.0/
│ ├── miniprogram-ci@1.9.17/
│ └── ...
├── @tarojs/
└── tdesign-miniprogram/
这种机制可以严格管理依赖关系,但部分原本依赖传统扁平化 node_modules 结构的工具链,可能存在兼容性问题。
对于从 npm 迁移而来的老项目,可以尝试将依赖布局切换为 hoisted 模式。
本次使用的是 pnpm 12,需要在项目根目录使用 pnpm-workspace.yaml 管理对应配置。
首先执行:
arduino
pnpm config set --location=project nodeLinker hoisted
然后检查:
arduino
pnpm config get nodeLinker
期望得到:
hoisted
也可以直接打开项目根目录的 pnpm-workspace.yaml,添加:
makefile
nodeLinker: hoisted
如果文件已经存在,只需要添加对应字段,不要覆盖原有的 workspace 配置。
hoisted 模式会让 pnpm 使用更接近 npm 的扁平化依赖布局,从而减少部分工具链在依赖路径解析方面的兼容问题。
修改配置后,需要重新安装依赖才能应用新的布局。
在 Windows PowerShell 中执行:
Remove-Item -Recurse -Force node_modules
pnpm install
这里建议保留 pnpm-lock.yaml,避免因为重新解析依赖版本而引入其他变量。
完成后重新构建:
arduino
pnpm run build:weapp
再执行上传:
arduino
pnpm run build:weapp:upload
本次迁移中的上传问题最终通过调整 nodeLinker 为 hoisted 得到解决。
需要说明的是,这种处理方式说明依赖布局与问题有关,但不能仅凭 undefined 错误认定 miniprogram-ci 内部的具体故障原因。
4. 将 pnpm 配置保存到项目
为了避免其他开发者重新安装依赖后再次遇到相同问题,建议将项目级配置一并提交到 Git。
项目根目录保留:
makefile
# pnpm-workspace.yaml
nodeLinker: hoisted
同时可以在 package.json 中声明包管理器版本:
perl
{
"packageManager": "pnpm@12.18.0"
}
这样团队成员和 CI 环境可以尽量保持一致的 pnpm 版本及依赖安装行为。
需要注意,hoisted 并不是 pnpm 的默认推荐模式,也不是所有 Taro 项目的必需配置。对于新项目,仍然可以优先使用默认的依赖隔离模式。