为何在module.exports内使用async会触发TypeError: exports is read-only
Let's break down what's happening here and how to fix that frustrating error you're seeing when adding async to your exported function.
The Problem
You've got a module using module.exports with an async arrow function, and as soon as you add the async keyword, you hit TypeError: "exports" is read-only. Remove async, and everything works fine—so the issue is definitely tied to how async functions are being transformed in your Babel/Webpack setup.
Your Code for Reference
First, let's recap the code you're working with to make sure we're on the same page:
Exported Module (./lib/eCommerceLogic.js)
function consoletest(){ console.log("HelloRequire!") } function commonAJAXCall(){ return $.get('https://jsonplaceholder.typicode.com/todos/1', { }).then((response) => { response = JSON.stringify(response) console.log(response) console.log("AJAX happened") return response }) } module.exports = { doStuff: async () => { //await commonAJAXCall() consoletest() } }
Importing Code
let eventListeners = require('./lib/eCommerceLogic') $("#AJAXproductnames").on("click", function(){ eventListeners.doStuff() })
Why This Happens
This error usually pops up when there's a clash between Babel's transformation of async functions and Webpack's handling of CommonJS modules. Here's the breakdown:
- When Babel transforms an async function, it adds helper code under the hood. In some cases, this helper code can accidentally try to reassign
module.exports, which Webpack locks as read-only in certain module modes. - Another common cause is a misconfigured Babel setup—if you're missing plugins that handle async/await properly (like
@babel/plugin-transform-runtime), or your@babel/preset-envisn't tuned to your target environment, you can run into this conflict.
Solutions to Try
1. Switch to ES Module Syntax (Cleanest Fix)
Instead of using CommonJS module.exports, use ES module export syntax. This plays much nicer with Babel's async transformations and avoids the read-only conflict entirely:
Update your exported module:
// ./lib/eCommerceLogic.js function consoletest(){ console.log("HelloRequire!") } function commonAJAXCall(){ return $.get('https://jsonplaceholder.typicode.com/todos/1', { }).then((response) => { response = JSON.stringify(response) console.log(response) console.log("AJAX happened") return response }) } export const doStuff = async () => { // await commonAJAXCall() consoletest() }
Then update your import:
import { doStuff } from './lib/eCommerceLogic' $("#AJAXproductnames").on("click", function(){ doStuff() })
Webpack handles ES modules seamlessly out of the box, so this should resolve the conflict immediately.
2. Add @babel/plugin-transform-runtime to Your Setup
If you want to stick with CommonJS syntax, adding this plugin will fix the async transformation issue by using reusable helpers from @babel/runtime instead of inlining code that might mess with module.exports.
First install the required packages:
npm install --save-dev @babel/plugin-transform-runtime npm install @babel/runtime
Then update your Babel config (either .babelrc or the config in your webpack.config.js):
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-transform-runtime"] }
3. Check Your Webpack Output Config
Double-check that your webpack config isn't using an incompatible output.libraryTarget setting. If you've set it to umd or amd, it might conflict with CommonJS exports when combined with async functions. Stick to commonjs or let webpack use its default for your project.
Verify the Fix
After applying one of these changes, re-enable the await commonAJAXCall() line and test your code. The read-only error should be gone, and your async function should execute as expected.
内容的提问来源于stack exchange,提问作者Narktor

