大数跨境

如何在 pnpm + React Native 中生存:我是如何最终阻止 Metro 报错的(@babel/runtime)

如何在 pnpm + React Native 中生存:我是如何最终阻止 Metro 报错的(@babel/runtime) 索引目录
2025-10-31
0
导读:关注「索引目录」公众号,获取更多干货。

关注「索引目录」公众号,获取更多干货。


Do-Not-Stop是一个不断发展的Web3 前端实验平台,使用Vite、 React、 TypeScript、 viemwagmi构建。

它旨在成长、适应并试验以太坊、 SolanaReact 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,请将此配置添加到你的应用中,然后继续构建🚀


关注「索引目录」公众号,获取更多干货。


【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读12
粉丝0
内容444