Simple webpack build fails because of new ES syntax

Viewed 2245

I have the following file:

src/index.js

let foo = null ?? "waffles"

And the following webpack config:

import path from 'path'

const defaultConf = {
    name: 'default',
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    }
}

export default defaultConf

Build fails with:

ERROR in ./src/index.js 2:18
Module parse failed: Unexpected token (2:18)

Environment is:

node v14.8.0
webpack@4.44.1

Why does my build fail?
How can I debug what makes it fail?
How can I fix it?

Edit: Said syntax is supported by node (+all major browsers). The expectation is the build will succeed.

Edit: Based on Anthony's answer I can make the questions more specific:

  1. Based on the answer evidently, wepback is attempting to transpile that code. Why does webpack attempt to transpile that code? Is it operating on some default I'm unaware of? What is that default?
  2. If I'm tripping over some default, how can I find out what it is? Webpack docs say the following:

Out of the box, webpack won't require you to use a configuration file. However, it will assume the entry point of your project is src/index.js and will output the result in dist/main.js minified and optimized for production.

What other assumptions does it make? What does "optimized" mean? How does it minify the code? How can I inspect the default config? Potentially debug the build process (i.e. step-by-step debugging)?

  1. How can I prevent webpack from attempting to transpile this code? How can I set the language level? E.g. to es2020, of which nullish coalescing is part of.

Note: The point of this question is not to end up with a working piece of code. If I wanted that I'd just do foo = null ? null : "waffles" instead of null coalescing. The point is to demystify webpack.

2 Answers

------------Edit: 18 September 2020.

TL;DR. Typically, a webpack configuration file is written for Node, therefore it uses CommonJS "syntax" - recommended. If you want your webpack config to be written as an es6 module, you would require additional steps - either transpile the configuration file using Babel, or start Node with the flag --experimental-modules. If your code is also written as es6+ modules you need configure webpack to transpile your code (not configuration file) using babel.

Node.js uses Common.js by default. In order to run es6 modules (there are other module types - AMD, Require.js etc...) you need to convert those modules so that your version of Node can run those modules when you do node myscript.js or npm run xyz. So first Node needs to understand your webpack config.

In your version - v14.x - node experimental-modules is enabled by default so you can run your es6 config/scripts but you have to save your script file with the .mjs extension OR add { "type": "module" } in your package.json. See here and here.

For older node versions, you will need to run node with the flag --experimental-modules (it is not enabled by default)

To run webpack with a configuration using es6 modules, you either transpile the configuration in es6 to CommonJS OR write a script that calls webpack from Javascript (in es6). You could do something like:

//in webpack.config.mjs
import path from 'path'

const defaultConf = {
    name: 'default',
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve() + '/dist'
    },
    module: {
      rules: [
        {
          use: 'babel-loader', //if your code is in es6+ you still need this transpiled
          test: /\.js$/,
          exclude: /node_modules/
        }
      ]
    }
}

export default defaultConf

Then write a script that calls webpack using the above configuration file like so:

//webpack-runner.mjs
import webpack from 'webpack';
import defaultConf from './webpack.config.mjs';

const webpackRunner = webpack(defaultConf);

webpackRunner.run();

Then in your scripts section of your package.json "build": "node --experimental-modules webpack-runner.mjs. Note that node --experimental-modules is enabled in Node v14. For further reading.

End of Edit


In any case, your webpack configuration should be in CommonJS format js var x = require('some-module) or you need babel to transpile es6 code into CommonJs modules. If you do not do this, your webpack config won't be transpiled and node will complain when you try to build.

If you have done this in your webpack config - perhaps in a webpack.config.babel.js, it may simply be that webpack is not using your config file and is using standard defaults which will also not transpile your code.

You get this error "Unexpected token..." in either scenario.

The webpack config below worked for me and webpack, using babel-loader, transpiled the code and I was able to run your index.js.

const path = require('path');

module.exports = [{
    name: 'default',
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: { //added module section so that webpack uses babel-loader
          rules: [
            {
              use: 'babel-loader',
              test: /\.js$/,
              exclude: /node_modules/
            }
          ]
        }

},] //add other named configurations before closing the array.

If for some reason you definitely want webpack config in es6 syntax, then that is possible, but you need it (webpack config) to be transpiled first - take a look at this and this

Upgrade to Webpack 5 to enable the ?? syntax without fussy transpile issues.

Side issues I ran into:

  1. Webpack 5 includes the terser plugin, so no need to include it in your devDependencies. Terser's sourceMap option has been removed, so remove it from terser options if you used it.
  2. I upgraded webpack-node-externals plugin to the latest (mine went to 3.0.0). Its whitelist option has been renamed allowlist.
Related