1

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?

volume one
  • 6,800
  • 13
  • 67
  • 146

1 Answers1

1

The reason seems to be this modules: false setting for the @babel/preset-env, though the default value "auto" would produce the same error for me, but opting for modules: 'commonjs' fixes the issue.

Let's dig a bit into this https://babeljs.io/docs/en/babel-preset-env#modules

Enable transformation of ES module syntax to another module type. Note that cjs is just an alias for commonjs.

The key point is that it transforms from ES modules to something else (transpiles modern to compatible)

So it seems Babel can pick up import and export statements and convert them to other syntax like require and module.exports but it cannot do the reverse, like converting this module.exports = ExpressRouter; statement to export default ExpressRouter; it can only preserve the original code

If 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 the module.exports statements, or try to find a babel plugin that converts commonjs to es6

Otherwise you can switch to another module output like umd (Universal modules definition) or commonjs

It seems you're targeting Node and a commonjs output (libraryTarget: 'commonjs2') and you also have this comment: // Transpiles ES6-8 into ES5 so you can probably specify commonjs as the module type

kidroca
  • 3,480
  • 2
  • 27
  • 44