Cloudflare + MongoDB:如何修复“Error: Dynamic require of 'punycode/' is not supported”

发布: (2025年12月31日 GMT+8 21:50)
10 min read
原文: Dev.to

Source: Dev.to


Cloudflare + MongoDB:如何修复 “Dynamic require of ‘punycode’ is not supported” 错误

在使用 Cloudflare Workers 与 MongoDB 进行连接时,你可能会遇到以下错误:

Error: Dynamic require of 'punycode' is not supported

这个错误是因为 Cloudflare Workers 的运行时不支持 Node.js 的 require 动态加载,而 punycode 是某些依赖(如 mongodb)内部使用的模块。下面提供几种解决方案,帮助你在 Workers 环境中顺利使用 MongoDB。

方案一:使用官方的 mongodb-atlas Workers 绑定

Cloudflare 为 MongoDB Atlas 提供了官方的 Workers 绑定(binding),它已经处理了所有兼容性问题。步骤如下:

  1. 在 Cloudflare Dashboard 中添加绑定

    • 前往 Workers → Bindings → Add binding
    • 选择 MongoDB Atlas,填写你的 Atlas 集群信息(Cluster ID、Database Name、Username、Password)。
  2. 在代码中使用绑定

    export default {
      async fetch(request, env) {
        const { MONGODB } = env; // 这里的 MONGODB 是你在 Dashboard 中定义的绑定名称
        const db = MONGODB.getDatabase('myDatabase');
        const collection = db.collection('myCollection');
        
        const docs = await collection.find().toArray();
        return new Response(JSON.stringify(docs), {
          headers: { 'Content-Type': 'application/json' },
        });
      },
    };

提示:使用官方绑定可以免除手动打包 mongodb 驱动的麻烦,并且性能更佳。

方案二:使用 mongodb 驱动的浏览器版(mongodb-stitch

如果你不想使用官方绑定,另一种办法是使用 MongoDB 提供的浏览器/前端 SDK(旧称 Stitch)。它基于 HTTP/REST,完全兼容 Workers 环境。

  1. 安装 SDK(在本地项目中):

    npm install mongodb-stitch-browser-sdk
  2. 在 Workers 中引入并初始化

    import { Stitch, RemoteMongoClient } from 'mongodb-stitch-browser-sdk';
    
    const client = Stitch.initializeDefaultAppClient('your-app-id');
    const mongodb = client.getServiceClient(RemoteMongoClient.factory, 'mongodb-atlas');
    const db = mongodb.db('myDatabase');
    const collection = db.collection('myCollection');
    
    export default {
      async fetch(request) {
        const docs = await collection.find({}).toArray();
        return new Response(JSON.stringify(docs), {
          headers: { 'Content-Type': 'application/json' },
        });
      },
    };

注意:使用此方式需要在 MongoDB Atlas 中启用 Realm(原 Stitch)并创建相应的 App ID 与服务。

方案三:手动打包 mongodb 驱动(使用 esbuild/webpack)

如果你坚持使用原生的 mongodb Node.js 驱动,需要在打包阶段移除对 punycode 的动态 require。下面以 esbuild 为例:

  1. 安装依赖

    npm install mongodb esbuild
  2. 创建 esbuild 配置build.js):

    const esbuild = require('esbuild');
    
    esbuild.build({
      entryPoints: ['src/worker.js'],
      bundle: true,
      platform: 'browser', // 关键:告诉 esbuild 使用浏览器兼容的 polyfills
      external: ['punycode'], // 排除 punycode,防止它被动态 require
      outfile: 'dist/worker.js',
      define: {
        'process.env.NODE_ENV': '"production"',
      },
    }).catch(() => process.exit(1));
  3. 在代码中使用 mongodbsrc/worker.js):

    import { MongoClient } from 'mongodb';
    
    const uri = 'mongodb+srv://user:password@cluster0.mongodb.net/myDatabase?retryWrites=true&w=majority';
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    
    export default {
      async fetch(request) {
        await client.connect();
        const collection = client.db().collection('myCollection');
        const docs = await collection.find().toArray();
        await client.close();
    
        return new Response(JSON.stringify(docs), {
          headers: { 'Content-Type': 'application/json' },
        });
      },
    };
  4. 部署

    npm run build   # 运行上面的 build.js
    wrangler publish

关键点

  • platform: 'browser' 会让 esbuild 自动使用浏览器版的 polyfills,避免 Node‑only 模块。
  • external: ['punycode'] 告诉打包器不要把 punycode 包进 bundle,从而绕过动态 require

方案四:使用 cloudflare-worker-mongo 第三方库

社区已经提供了一个专门为 Workers 打造的轻量级 MongoDB 客户端——cloudflare-worker-mongo。它内部使用了 MongoDB Atlas 的 HTTP 接口,省去了所有 Node.js 依赖。

  1. 安装

    npm install cloudflare-worker-mongo
  2. 使用示例

    import { Mongo } from 'cloudflare-worker-mongo';
    
    const mongo = new Mongo({
      uri: 'mongodb+srv://user:password@cluster0.mongodb.net',
      db: 'myDatabase',
    });
    
    export default {
      async fetch(request) {
        const docs = await mongo.collection('myCollection').find().toArray();
        return new Response(JSON.stringify(docs), {
          headers: { 'Content-Type': 'application/json' },
        });
      },
    };

优点:几乎零配置,直接兼容 Workers。
缺点:功能相对官方 mongodb 驱动更受限(不支持事务、聚合管道的高级特性)。

常见错误排查

错误信息可能原因解决办法
Dynamic require of 'punycode' is not supported使用了不兼容的 Node.js 模块采用方案一或二,或在打包时排除 punycode
Cannot find module 'mongodb'未正确打包或未在 wrangler.toml 中声明确认 npm install mongodb 已执行,且 wrangler.tomltype = "javascript"(或 type = "webpack")配置正确
Authentication failedAtlas 连接字符串错误或绑定凭证不匹配检查用户名、密码、IP 白名单(Workers IP 需要在 Atlas 中放行)
Network request failedWorkers 无法访问外部网络确认已在 Workers 中启用 Outbound 网络访问(需要付费计划)

小结

  • 首选:使用 Cloudflare 官方提供的 MongoDB Atlas 绑定,最省事且性能最佳。
  • 次选:如果需要更灵活的客户端,考虑使用 mongodb-stitch-browser-sdk 或社区的 cloudflare-worker-mongo
  • 高级需求:自行打包原生 mongodb 驱动时,务必在构建阶段排除 punycode,并使用 platform: 'browser' 让打包工具提供相应的 polyfill。

希望这些方案能帮助你在 Cloudflare Workers 中顺利连接 MongoDB,摆脱 “Dynamic require of ‘punycode’ is not supported” 的困扰。祝开发愉快!

TL;DR

@cloudflare/vite-plugintr46 库(MongoDB Node.js 驱动的传递依赖)内部处理带有尾随斜杠的导入。当前的解决方案是对该导入进行补丁,直到正式修复发布。

Reproduction

创建一个最小项目来复现该问题。

# 1️⃣ 创建一个新的 React‑Router 应用
npm create cloudflare@latest -- my-react-router-app --framework=react-router
cd my-react-router-app

# 2️⃣ 安装 MongoDB 驱动
npm install mongodb --save

# 3️⃣ 在 Workers 入口点前添加导入语句
printf 'import { MongoClient } from "mongodb";\n%s' "$(cat workers/app.ts)" > workers/app.ts

# 4️⃣ 启用 Node.js 兼容性标志(SSR 所必需)
sed -i '' '/"compatibility_date": "2025-04-04"/a\
  "compatibility_flags": ["nodejs_compat"],' wrangler.jsonc

Run the app

npm run dev

你应该会看到类似如下的输出:

11:15:07 AM [vite] (ssr) Re-optimizing dependencies because vite config has changed
11:15:08 AM [vite] (ssr) ✨ new dependencies optimized: mongodb
11:15:08 AM [vite] (ssr) ✨ optimized dependencies changed. reloading
[vite] program reload
Error: Dynamic require of "punycode/" is not supported
    at null. (/.../node_modules/.vite/deps_ssr/chunk-PLDDJCW6.js:11:9)

Vite 报错称 动态 require punycode/(注意末尾的斜杠)不受支持。

Find the source

npm ls punycode

输出:

my-react-router-app@ /.../my-react-router-app
└─┬ mongodb@7.0.0
  └─┬ mongodb-connection-string-url@7.0.0
    └─┬ whatwg-url@14.2.0
      └─┬ tr46@5.1.1
        └── punycode@2.3.1

检查 tr46 的入口文件 (node_modules/tr46/index.js):

"use strict";

const punycode = require("punycode/"); // ← problematic line
const regexes = require("./lib/regexes.js");
const mappingTable = require("./lib/mappingTable.json");
const { STATUS_MAPPING } = require("./lib/statusMapping.js");
// …

末尾的斜杠导致 Vite 的打包器将该导入视为动态 require,而 Workers 运行时不支持这种方式。

已经针对 tr46 提交了 PR(#73),但维护者指出问题根源在 Vite,而非库本身。引入斜杠的提交(fef6e95)本意是为了消除对内置 punycode 模块的弃用警告。

修补依赖

我们可以使用 patch‑package 来规避此问题:

  1. 安装 patch-package

    npm install patch-package --save-dev
  2. 添加 postinstall 脚本(在每次安装后保持补丁生效,同时运行默认的 cf-typegen 脚本):

    npm pkg set scripts.postinstall="patch-package && npm run cf-typegen"
  3. 编辑有问题的文件,去掉末尾的斜杠:

    sed -i '' 's/require("punycode\/")/require("punycode")/g' node_modules/tr46/index.js
  4. 生成补丁

    npx patch-package tr46

    这将在你的仓库中生成 patches/tr46+5.1.1.patch 文件。

  5. 提交补丁(使其随代码库一起传播)。

以后每次运行 npm install 时,patch-package 都会自动重新应用此修复。

完整补丁脚本(复制‑粘贴)

# 1️⃣ Install patch‑package
npm install patch-package --save-dev

# 2️⃣ Add postinstall script (preserves existing cf-typegen)
npm pkg set scripts.postinstall="patch-package && npm run cf-typegen"

# 3️⃣ Fix the import in the installed package
sed -i '' 's/require("punycode\/")/require("punycode")/g' node_modules/tr46/index.js

# 4️⃣ Generate a patch file
npx patch-package tr46

运行上述步骤后,重新启动开发服务器:

npm run dev

您应该不再看到 “Dynamic require of “punycode/” is not supported” 错误,MongoDB 驱动将在您的 Cloudflare Worker 中正确加载。

接下来怎么办?

  • 关注上游 tr46 仓库——一旦他们发布不带尾部斜杠的版本,你就可以去掉补丁。
  • 如果在其他库中遇到类似问题,也可以使用相同的 patch-package 方法作为临时解决方案。

祝你玩得开心!

上述更改

npx patch-package tr46
# reinstall and apply patches
npm install

Summary

对瞬时依赖进行补丁以规避此类问题并非理想方案,但它为遇到此特定错误的用户提供了一条可行的路径。简要回顾如下:

  1. 安装 patch-package

    npm install patch-package
  2. 更新 package.json – 在任何已有的 postinstall 脚本前添加 patch-package 命令:

    {
      "scripts": {
        "postinstall": "patch-package && "
      }
    }
  3. 修改源码 – 编辑 node_modules/tr46/index.js,去掉 require("punycode/") 中末尾的 /

  4. 创建补丁

    npx patch-package tr46
  5. 确保补丁已应用

    npm install

希望上游问题能够得到干净的解决(已在 cloudflare/workers-sdk#11751 中报告),但在此期间,此方法可以让你的项目继续运行。

Back to Blog

相关文章

阅读更多 »