I have files in my project which use both CJS require and ES import syntax, like such:
const multer = require('multer');
import multerS3 from './multer-s3-storage-engine.js';
const ExpressRouter = require('express').Router();
....
....
module.exports = ExpressRouter;
I know we should not mix these together, but I thought that was what babel for was which is to transpile everything to work.
In development, I use babel-node to start the dev version like such:
"dev": "babel-node -r dotenv/config src/server.js"
The above works fine and all my mixed CJS and ES files work. However when it comes to production build, Webpack is failing and throwing this error:
throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id);
Error: ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: 262
262 is referring to the module.exports = ExpressRouter; part in my code above.
The Webpack 5 config file for the build is like this:
const path = require('path')
const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals');
const utils = require('./utils');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
server: utils.resolve('/src/server.js')
},
target: 'node',
// This tells the server bundle to use Node-style exports
output: {
path: utils.resolve('/dist'),
filename: '[name].js',
sourceMapFilename: isProduction ? '[name].[hash].js.map' : '[name].js.map',
libraryTarget: 'commonjs2'
},
node: {
// Need this when working with express, otherwise the build fails
__dirname: false,
__filename: false,
},
externals: nodeExternals({
allowlist: [
/^vue-meta*/,
/\.(css|sass|scss|vue|html)$/,
]
}),
optimization: {
minimize: isProduction ? true : false,
minimizer:[
(compiler) => ({
terserOptions: {
compress: {drop_console: isProduction ? true : false},
format: {
comments: isProduction ? false : true
},
}
})
]
},
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.m?jsx?$/,
exclude: ['/node_modules/', /\bcore-js\b/, /\bwebpack\/buildin\b/, /@babel\/runtime-corejs3/],
use: {
loader: 'babel-loader',
options: {
babelrc: false,
sourceType: "unambiguous",
presets: [
["@babel/preset-env", {
modules: false,
corejs: {
version: "3.9.0",
proposals: true
},
}
]
],
}
}
},
]
}
};
In package.json I also have the following config:
"babel": {
"presets": [
"@babel/preset-env"
],
"sourceType": "unambiguous"
}
What is the reason for why it works with babel-node and not with babel-loader?
The reason seems to be this
modules: falsesetting for the@babel/preset-env, though the default value "auto" would produce the same error for me, but opting formodules: 'commonjs'fixes the issue.Let's dig a bit into this https://babeljs.io/docs/en/babel-preset-env#modules
The key point is that it transforms from ES modules to something else (transpiles modern to compatible)
So it seems Babel can pick up
importandexportstatements and convert them to other syntax likerequireandmodule.exportsbut it cannot do the reverse, like converting thismodule.exports = ExpressRouter;statement toexport default ExpressRouter;it can only preserve the original codeIf you're looking for a solution it depends on what you need, if you truly want to output es6 modules (hence
modules: false) then it seems you'd have to get rid of themodule.exportsstatements, or try to find a babel plugin that converts commonjs to es6Otherwise you can switch to another module output like
umd(Universal modules definition) orcommonjsIt seems you're targeting Node and a commonjs output (libraryTarget: 'commonjs2') and you also have this comment:
// Transpiles ES6-8 into ES5so you can probably specifycommonjsas the module type