使用Rollup将Node.js API打包为CommonJS时,如何解决ERR_REQUIRE_ESM错误?
问题
我用Rollup打包采用ES Module(import/export)语法编写的Node.js API,目标输出格式为CommonJS(CJS)。打包完成启动应用后,遇到大量仅支持ESM的第三方模块问题,错误信息如下:
Error [ERR_REQUIRE_ESM]: require() of ES Module /api/node_modules/.pnpm/node-fetch@3.3.2/node_modules/node-fetch/src/index.js from /api/dist/route-DpPSLTSv.js not supported. Instead, change the require of index.js in /api/dist/route-DpPSLTSv.js to a dynamic import() which is available in all CommonJS modules.
我希望通过配置Rollup自动处理ESM转CJS的问题,不用手动把所有require()改成动态import()。当前Rollup配置如下:
import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' import resolve from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import nodeExternals from 'rollup-plugin-node-externals' import sentryRollupPlugin from 'rollup-plugin-sentry' const { SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN } = process.env /** * @type {import('rollup').RollupOptions} */ const config = { input: ['src/index.ts', 'instrument.js'], output: { dir: 'dist', format: 'cjs', sourcemap: true }, plugins: [ resolve({ extensions: ['.ts', '.js', '.tsx', '.jsx', '.json', '.mjs', '.cjs'] }), commonjs(), nodeExternals(), typescript(), json(), sentryRollupPlugin({ org: SENTRY_ORG, project: SENTRY_PROJECT, authToken: SENTRY_AUTH_TOKEN, include: ['dist'], ignore: ['node_modules'] }) ], onwarn(warning, handler) { if (warning.code === 'THIS_IS_UNDEFINED') { return } handler(warning) } } export default config
解决方案
1. 调整@rollup/plugin-node-resolve配置,适配Node.js环境并指定需处理的ESM依赖
修改resolve插件配置,添加Node.js环境专属参数,并指定需要转换的ESM模块:
resolve({ extensions: ['.ts', '.js', '.tsx', '.jsx', '.json', '.mjs', '.cjs'], preferBuiltins: true, // 优先使用Node.js内置模块 browser: false, // 明确为Node.js环境,而非浏览器 resolveOnly: ['node-fetch', /* 其他需要转换的ESM依赖 */] // 指定要处理的ESM模块 })
2. 增强@rollup/plugin-commonjs的转换能力
开启混合模块转换,让插件处理node_modules中的ESM模块:
commonjs({ esmExternals: false, // 不把ESM模块当作外部依赖处理 transformMixedEsModules: true, // 自动转换混合ESM/CJS的模块 include: /node_modules/ // 覆盖所有node_modules中的模块 })
3. 修改rollup-plugin-node-externals配置,排除需转换的ESM依赖
默认插件会把所有node_modules模块排除在打包外,导致ESM依赖直接被require引用。需将需要转换的模块从排除列表中移除:
nodeExternals({ exclude: ['node-fetch', /* 其他需要转换的ESM依赖 */] })
4. 可选:添加@rollup/plugin-legacy处理顽固ESM模块
如果某些ESM模块转换仍有问题,可添加该插件辅助转换:
先安装插件:
npm install @rollup/plugin-legacy --save-dev
再在配置中引入并使用:
import legacy from '@rollup/plugin-legacy' // 加入plugins数组 legacy({ 'node-fetch': 'default' // 指定模块的导出方式 })
完整修改后的配置示例
import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' import resolve from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import nodeExternals from 'rollup-plugin-node-externals' import sentryRollupPlugin from 'rollup-plugin-sentry' import legacy from '@rollup/plugin-legacy' const { SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN } = process.env /** * @type {import('rollup').RollupOptions} */ const config = { input: ['src/index.ts', 'instrument.js'], output: { dir: 'dist', format: 'cjs', sourcemap: true, interop: 'auto' // 自动处理ESM与CJS的互操作问题 }, plugins: [ resolve({ extensions: ['.ts', '.js', '.tsx', '.jsx', '.json', '.mjs', '.cjs'], preferBuiltins: true, browser: false, resolveOnly: ['node-fetch'] }), commonjs({ esmExternals: false, transformMixedEsModules: true, include: /node_modules/ }), nodeExternals({ exclude: ['node-fetch'] }), legacy({ 'node-fetch': 'default' }), typescript(), json(), sentryRollupPlugin({ org: SENTRY_ORG, project: SENTRY_PROJECT, authToken: SENTRY_AUTH_TOKEN, include: ['dist'], ignore: ['node_modules'] }) ], onwarn(warning, handler) { // 忽略无关警告 if (warning.code === 'THIS_IS_UNDEFINED' || warning.code === 'MODULE_LEVEL_DIRECTIVE') { return } handler(warning) } } export default config
关键说明
- 将需要转换的ESM依赖从externals中排除,让Rollup把它们打包进输出文件,再通过commonjs插件转为CJS格式。
interop: 'auto'会自动处理默认导出等互操作细节,无需手动调整导入语句。- 若有多个ESM依赖,只需在
resolveOnly、nodeExternals.exclude和legacy配置中添加对应模块名即可。
内容的提问来源于stack exchange,提问作者octavemirbeau
相关产品推荐
相关产品推荐

