webpack-chain icon indicating copy to clipboard operation
webpack-chain copied to clipboard

Unhelpful error message when adding minimizer ("TypeError: Cannot read property '__expression' of undefined")

Open thewilli opened this issue 4 years ago • 9 comments

I want to explicitly add (in order to configure) the Terser Plugin as minimizer to a webpack config with webpack-chain v6.0.0.

The code

const TerserPlugin = require('terser-webpack-plugin');
config.optimization
.minimizer('terser')
.use(TerserPlugin);

results in the error

TypeError: Cannot read property '__expression' of undefined
    at Object.toConfig (/some/path/node_modules/webpack-chain/src/Plugin.js:56:38)
    at clean.Object.assign.minimizer.minimizers.values.map.plugin (/some/path/node_modules/webpack-chain/src/Optimization.js:39:66)
    at Array.map (<anonymous>)
    at module.exports.toConfig (/some/path/node_modules/webpack-chain/src/Optimization.js:39:45)
    at module.exports.toConfig (/some/path/node_modules/webpack-chain/src/Config.js:130:41)

Any idea what the problem might be?

thewilli avatar Oct 15 '19 08:10 thewilli

Hi! Thank you for filing an issue.

I'm not able to reproduce using:

// package.json
{
  "dependencies": {
    "terser-webpack-plugin": "2.1.3",
    "webpack": "4.41.1",
    "webpack-chain": "6.0.0"
  }
}
// test.js
const Config = require('webpack-chain');
const config = new Config();

const TerserPlugin = require('terser-webpack-plugin');
config.optimization
  .minimizer('terser')
  .use(TerserPlugin);

console.log(config.toConfig());
console.log(config.toString());
$ node --version
v12.11.1
$ node test.js
{ optimization: { minimizer: [ [TerserPlugin] ] } }
{
  optimization: {
    minimizer: [
      /* config.optimization.minimizer('terser') */
      new TerserPlugin()
    ]
  }
}

Could you create a reduced testcase that shows the error?

edmorley avatar Oct 15 '19 11:10 edmorley

I had same issue (with TerserPlugin). My problem was that I used vue-cli-plugin-nativescript-vue with vue-cli 4.x They have different interface usage(4 vs 6) within webpack-chain plugin.

cainrus avatar Oct 16 '19 07:10 cainrus

The same problem has arisen

// Please ignore the above references
import Config from "webpack-chain";
import common from "./webpack.common";
import TerserPlugin from "terser-webpack-plugin";
const config = new Config();
config.mode("production");
config.optimization.minimizer("terser").use(TerserPlugin);
export default common.merge(config.toConfig()).toConfig();

package.json

{
    "webpack": "^4.41.3",
    "terser-webpack-plugin": "^2.3.0",
    "webpack-chain": "^6.2.0",
}

error

      const constructorName = plugin.__expression
                                     ^
TypeError: Cannot read property '__expression' of undefined

bosens-China avatar Dec 17 '19 08:12 bosens-China

@bosens-China Hi! Please can you create a fully contained testcase that demonstrates the issue? And file as a new issue? I can't use the above, since webpack.common is missing.

edmorley avatar Dec 18 '19 09:12 edmorley

me too

nuochong avatar Dec 27 '19 11:12 nuochong

My problem was that I used vue-cli-plugin-nativescript-vue with vue-cli 4.x They have different interface usage(4 vs 6) within webpack-chain plugin.

Thank you for this clue. Checking out that vue plugin, I can see it's still using the old webpack-chain <= 4 syntax for optimization.minimizer: https://github.com/nativescript-vue/vue-cli-plugin-nativescript-vue/blob/e13ae2c02b66d8ac68daddc27782374163be7f01/index.js#L327-L347

The solution is either to downgrade to webpack chain 4 (by downgrading Vue CLI to v3) or else fork that plugin and update it to be compatible with webpack 5+ that comes with newer Vue CLI.

This seems like a common enough problem that it would be worth adding some additional error checking to webpack-chain so we can display a more user-friendly error message (even though this is not a webpack-chain bug itself).

edmorley avatar Jan 02 '20 21:01 edmorley

I've dug into this more yesterday/today. This error message is shown when the plugin configuration is incomplete, which can happen in a variety of ways.

Whilst this isn't a webpack-chain bug, showing unhelpful error messages is not ideal and we should add additional error checking at other locations, to make it easier to work out what is invalid about the configuration, so that users can fix it themselves.

Scenario 1

In its simplest form, this error message can be triggered like so:

const webpackChain = require('webpack-chain');
const config = new webpackChain();
config.plugin('name');
// We forgot to call .use() before .toConfig()!
// config.plugin('name').use(SomePlugin);
config.toConfig();
$ node test.js
.../node_modules/webpack-chain/src/Plugin.js:56
      const constructorName = plugin.__expression
                                     ^
TypeError: Cannot read property '__expression' of undefined

The nature of webpack-chain's API for adding plugins, means it's a multi-part process, and if the .use() is forgotten, then the plugin isn't defined so .toConfig() will fail.

To handle this case, we could add a check inside Plugin's .toConfig() that throws a more user-friendly Error in this case. However this error message would still have to be fairly generic, since at the time .toConfig() is called, it's too late for us to know how we ended up in this state.

Scenario 2:

Using the legacy optimization.minimizer syntax that was valid in webpack-chain 4:

config.optimization.minimizer([
  new WebpackPluginFoo(),
  new WebpackPluginBar(arg1, arg2),
]);

...rather than the new syntax from webpack-chain 5+:

config.optimization.minimizer('foo').use(WebpackPluginFoo);
config.optimization.minimizer('bar').use(WebpackPluginBar, [arg1, arg2]);

This can happen when eg using Vue CLI 4 with a Vue plugin that's not yet been made compatible with it. This case we can handle more specifically, by checking whether an array was passed to optimization.minimizer and if so, displaying an error message that explains the syntax change. I've opened PR #226 for this.

Scenario 3:

Trying to pass a webpack-chain style configuration object into .merge() even though it doesn't support it.

eg:

const webpackChain = require('webpack-chain');
const config1 = new webpackChain();
const config2 = new webpackChain();
class TestPlugin {}

config2.optimization.minimizer("terser").use(TestPlugin);

// This is not valid usage of `.merge()` and isn't expected to work!
config1.merge(config2.toConfig()).toConfig();

There are a few issues here:

  1. The docs for .merge() weren't correct for optimization.minimizer, which I've fixed in #224
  2. .merge() accepts a configuration object this is similar to but not identical to the webpack configuration schema, and whilst this is mentioned in the docs, it should really be made more obvious given it's counter-intuitive. I've opened #225 for this.
  3. Given the counter-intuitive behaviour of .merge() we should really add some additional validation to try and warn people when they pass webpack-config objects to .merge() without first transforming them.

Personally I'm not a big fan of .merge() -- judging from comments above and ones I've seen in other issues, it feels like it's being used in cases where webpack-chain's API should be used directly instead. It's also missing functionality (eg #210). It also seems like if we added a true "clone webpack chain object" functionality (#212), then people might not need it?

edmorley avatar Jan 03 '20 15:01 edmorley

v6.3.1 has just been released, which includes some improvements to the error message shown in some of the scenarios outlined above, which should make it easier to self-debug what's broken in project's usage of webpack-chain.

I still want to improve the error message for scenario 1 and scenario 3.3, so leaving this open for now :-)

edmorley avatar Jan 28 '20 11:01 edmorley

answer

i tried, it working when NODE_ENV === 'production' dist/js/app.js no consoles

but when NODE_ENV !== 'production' dist/js/app.js has console

chainWebpack: config => {
    config.optimization
          .minimizer('terser')
          .tap(args => {
            if (process.env.NODE_ENV === 'production') {
              args[0].terserOptions.compress.drop_console = true
              args[0].terserOptions.compress.drop_debugger = true
              }
            return args
          })
}

Popxie avatar Dec 30 '20 15:12 Popxie