MERN项目CommonJS适配query-string的报错解决方法
问题描述
遇到 ERR_REQUIRE_ESM 错误,提示无法使用 require() 导入ES模块 query-string,需要改为动态 import()。
错误信息
C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\controllers\instructor.js:1 Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\node_modules\query-string\index.js not supported. Instead change the require of index.js in null to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\controllers\instructor.js:2:21) at Generator.next (<anonymous>) at Object.<anonymous> (C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\routes\instructor.js:1) at Generator.next (<anonymous>) at C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\server.js:33:52 at Array.map (<anonymous>) at Object.<anonymous> (C:\Users\MRPla\Downloads\CourseGuccho\edemy\server\server.js:33:25) at Generator.next (<anonymous>) { code: 'ERR_REQUIRE_ESM' } [nodemon] app crashed - waiting for file changes before starting...
相关代码文件
server>controllers>instructor.js
import User from "../models/user"; import stripe from "stripe"; import queryString from "query-string"; export const makeInstructor = async (req, res) => { try { // 1. find user from db const user = await User.findById(req.user._id).exec(); // 2. if user dont have stripe_account_id yet, then create new if (!user.stripe_account_id) { const account = await stripe.accounts.create({ type: "express" }); // console.log('ACCOUNT => ', account.id) user.stripe_account_id = account.id; user.save(); } // 3. create account link based on account id (for frontend to complete onboarding) const accountLink = await stripe.accountLinks.create({ account: user.stripe_account_id, refresh_url: process.env.STRIPE_REDIRECT_URL, return_url: process.env.STRIPE_REDIRECT_URL, type: "account_onboarding", }); // console.log(accountLink) // 4. pre-fill any info such as email (optional), then send url resposne to frontend accountLink = Object.assign(accountLink, { "stripe_user[email]": user.email, }); // 5. then send the account link as response to fronend res.send(`${accountLink.url}?${queryString.stringify(accountLink)}`); } catch (err) { console.log("MAKE INSTRUCTOR ERR ", err); } };
server>routes>instructor.js
import express from "express"; const router = express.Router(); // middleware import { requireSignin } from "../middlewares"; // controllers import { makeInstructor } from "../controllers/instructor"; router.post("/make-instructor", requireSignin, makeInstructor); module.exports = router;
server>server.js
import express from "express"; import cors from "cors"; import { readdirSync } from "fs"; import mongoose from "mongoose"; import csrf from "csurf"; import cookieParser from "cookie-parser"; const morgan = require("morgan"); require("dotenv").config(); const csrfProtection = csrf({ cookie: true }); // create express app const app = express(); // db mongoose .connect(process.env.DATABASE, { useNewUrlParser: true, useFindAndModify: false, useUnifiedTopology: true, useCreateIndex: true, }) .then(() => console.log("**DB CONNECTED**")) .catch((err) => console.log("DB CONNECTION ERR => ", err)); // apply middlewares app.use(cors()); app.use(express.json()); app.use(cookieParser()); app.use(morgan("dev")); // route readdirSync("./routes").map((r) => app.use("/api", require(`./routes/${r}`))); // csrf app.use(csrfProtection); app.get("/api/csrf-token", (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); // port const port = process.env.PORT || 8000; app.listen(port, () => console.log(`Server is running on port ${port}`));
服务端package.JSON文件
{ "name": "server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "nodemon -r esm server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "aws-sdk": "^2.1285.0", "bcrypt": "^5.1.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "csurf": "^1.11.0", "dotenv": "^8.6.0", "esm": "^3.2.25", "express": "^4.18.2", "express-jwt": "^8.2.1", "jsonwebtoken": "^8.5.1", "mongoose": "^5.13.15", "morgan": "^1.10.0", "nodemon": "^2.0.20", "query-string": "^8.1.0", "shortid": "^2.2.16", "stripe": "^11.5.0" } }
提问
若需将index.js的require改为所有CommonJS模块都支持的动态import(),请问具体该如何操作实现?
解决方案
方案一:使用动态import()替换静态导入
query-string@8.x是纯ES模块,无法在CommonJS环境下用静态import或require()导入,需要在使用它的函数内部动态导入:
修改server>controllers>instructor.js:
import User from "../models/user"; import stripe from "stripe"; // 移除顶部的静态import queryString from "query-string" export const makeInstructor = async (req, res) => { try { // 动态导入query-string模块 const queryStringModule = await import("query-string"); // 注意:ES模块的默认导出需要通过.default获取 const queryString = queryStringModule.default; // 1. 查询数据库中的用户 const user = await User.findById(req.user._id).exec(); // 2. 如果用户没有stripe_account_id,创建新账户 if (!user.stripe_account_id) { const account = await stripe.accounts.create({ type: "express" }); user.stripe_account_id = account.id; await user.save(); // 添加await确保数据保存完成 } // 3. 创建账户链接供前端完成开户流程 const accountLink = await stripe.accountLinks.create({ account: user.stripe_account_id, refresh_url: process.env.STRIPE_REDIRECT_URL, return_url: process.env.STRIPE_REDIRECT_URL, type: "account_onboarding", }); // 4. 预填用户邮箱信息(可选) const modifiedAccountLink = Object.assign({}, accountLink, { "stripe_user[email]": user.email, }); // 5. 拼接链接并返回给前端 res.send(`${modifiedAccountLink.url}?${queryString.stringify(modifiedAccountLink)}`); } catch (err) { console.log("MAKE INSTRUCTOR ERR ", err); } };
方案二:降级query-string到CommonJS兼容版本
query-string@7.x及以下版本支持CommonJS语法,无需修改代码,直接降级安装:
npm uninstall query-string npm install query-string@7.1.1
方案三:统一项目为ES模块系统
修改package.json添加"type": "module",让Node.js将所有.js文件当作ES模块处理,即可正常使用静态import:
{ "name": "server", "version": "1.0.0", "type": "module", "description": "", "main": "index.js", "scripts": { "start": "nodemon server.js" // 移除esm,Node.js原生支持ES模块 }, // 其他依赖保持不变 }
同时修改以下文件的CommonJS语法:
server.js:
// 替换const morgan = require("morgan"); import morgan from "morgan"; // 替换require("dotenv").config(); import dotenv from "dotenv"; dotenv.config(); // 替换路由中的require readdirSync("./routes").map(async (r) => { const routeModule = await import(`./routes/${r}`); app.use("/api", routeModule.default); });
routes/instructor.js:
// 替换module.exports = router; export default router;
内容的提问来源于stack exchange,提问作者Sadi Mohammad Kabbo
相关产品推荐
相关产品推荐

