关注「索引目录」公众号,获取更多干货。
Do-Not-Stop是一个不断发展的Web3 前端实验平台,使用Vite、 React、 TypeScript、 viem和wagmi构建。
它旨在成长、适应并试验以太坊、 Solana和React Native开发的最新技术——这是一个持续发展的项目,探索如何在 Web3 领域中,使用单一的现代代码库来跨越 Web 和移动端。
我自然而然地使用了pnpm workspaces。它们速度快、代码简洁,非常适合在多个项目之间共享代码。
直到 React Native 的打包工具 Metro 似乎对我的配置不满意为止。
🏗 设置
do-not-stop/
├─ frontend/ ← web (Vite + React + wagmi)
├─ mobile/ ← React Native
├─ packages/shared-auth/ ← shared logic between web & mobile
└─ pnpm-workspace.yaml
目标很简单:同时导入packages/shared-auth到网页和移动端。
一切看起来都很顺利——pnpm install速度飞快,依赖项也已去重——直到 Metro 抛出了这个问题:
Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault
即使它mobile/node_modules/@babel/runtime显然存在。
🧩一切分崩离析的地方
pnpm 的特点在于:它不像 npm 或 yarn 那样“扁平化”安装包。
相反,它会在底层构建一个虚拟仓库.pnpm/,并通过符号链接将所有内容关联起来。
例子:
mobile/node_modules/@babel/runtime
→ ../../node_modules/.pnpm/@babel+runtime@7.x/node_modules/@babel/runtime
Node 可以很好地执行此操作,但Metro 的解析器通常不行。
它会一直向上遍历目录直到找到目标文件node_modules,因此最终会从工作区根目录加载内容,而不是从移动应用的本地副本加载。
🧠 我先尝试了所有方法
当你开始在谷歌上搜索“pnpm react native @babel /runtime”时,最终会发现 GitHub issues 和 Stack Overflow 帖子中充斥着不完整的解决方案。
我尝试禁用提升功能:
hoistPattern: []
然后分离连接子:
node-linker=isolated
然后是核选项:
shamefully-hoist=true
每次都会出现其他问题——要么是移动端构建出了问题,要么是共享包停止链接,要么是 Metro 根本不配合。
什么方法都解决不了这个问题。
⚙️ 真正的解决方案——让 Metro 理解 pnpm
问题不在于 pnpm,
而在于Metro 的假设。
它期望的是一个扁平化的node_modules世界,但 pnpm 构建的是一个智能的、符号链接的树状结构。
因此,我没有强制 pnpm 的行为像 npm 一样,而是让Metro 理解 pnpm。
metro.config.js以下是我文件夹中最终的工作内容mobile/:
const path = require('path');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/**
* Metro configuration for pnpm monorepos
* (do-not-stop project)
*
* - Lets Metro follow pnpm's symlinks
* - Always prefers the mobile app's own node_modules
* - Watches the monorepo root so shared packages rebuild correctly
*/
const defaultConfig = getDefaultConfig(__dirname);
const config = {
watchFolders: [path.resolve(__dirname, '..')],
resolver: {
...defaultConfig.resolver,
unstable_enableSymlinks: true,
unstable_enablePackageExports: true,
extraNodeModules: new Proxy(
{},
{
get: (_target, name) =>
path.join(__dirname, 'node_modules', String(name)),
}
),
},
};
module.exports = mergeConfig(defaultConfig, config);
🧪 额外福利:使用共享套餐
因为watchFolders包含了仓库根目录,Metro 可以检测到其中packages/shared-auth/(或任何共享包)的更改。
如果您想要更精细的控制:
watchFolders: [
path.resolve(__dirname, '..', 'packages', 'shared-auth'),
path.resolve(__dirname, '..', 'packages', 'utils'),
],
🧠 要点总结
-
pnpm 的布局并没有出错——它只是比 Metro 预期的更智能。 -
禁用提升或压平安装只会掩盖问题。 -
真正的解决方案在于Metro 的解析器配置,而不是你的包管理器。 -
在同一个代码库中混合使用 Web 和移动端时,请配置工具以使其共存,而不是相互竞争。
🎉 结论
此设置现在支持永不停歇功能:
-
一个 React Web 应用( frontend/) -
一个 React Native 应用( mobile/) -
共享代码 packages/shared-auth/ -
所有功能均由 pnpm 管理
Do-Not-Stop不再仅仅是一个游乐场——它是一个不断发展的多平台 Web3 技术栈,与以太坊、Solana 和现代 React 开发的最新进展同步成长。
在与提升、符号链接和 Babel 错误斗争了一周之后,我不再与 pnpm 作斗争,而是让 Metro 变得更智能。
如果你在 pnpm monorepo 中运行 React Native,请将此配置添加到你的应用中,然后继续构建🚀
关注「索引目录」公众号,获取更多干货。

