Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.

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

Closed
thewilli opened this issue Oct 15, 2019 · 9 comments

Comments

@thewilli
Copy link

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 thewilli changed the title "Cannot read property '__expression'" when trying to add a minimizer Error when adding a new minimizer Oct 15, 2019
@edmorley
Copy link
Member

edmorley commented Oct 15, 2019

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?

@cainrus
Copy link

cainrus commented Oct 16, 2019

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.

@bosens-China
Copy link

bosens-China commented Dec 17, 2019

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

@edmorley
Copy link
Member

@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.

@nuochong
Copy link

me too

@edmorley edmorley changed the title Error when adding a new minimizer Unhelpful error message when adding minimizer ("TypeError: Cannot read property '__expression' of undefined") Jan 2, 2020
@edmorley
Copy link
Member

edmorley commented Jan 2, 2020

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 edmorley reopened this Jan 2, 2020
@edmorley edmorley self-assigned this Jan 2, 2020
edmorley added a commit that referenced this issue Jan 3, 2020
Since as of #84 (which was released in webpack-chain 5.0.0), the
`optimization.minimizer` entries in the data structure passed to
`.merge()` must match that of the other plugin-like configuration.

Refs #204.
edmorley added a commit that referenced this issue Jan 3, 2020
Since as of #84 (which was released in webpack-chain 5.0.0), the
`optimization.minimizer` entries in the data structure passed to
`.merge()` must match that of the other plugin-like configuration.

Refs #204.
edmorley added a commit that referenced this issue Jan 3, 2020
The data structure that is passed to `.merge()` is very similar to a
webpack configuration object, but has a few key differences.

The current docs don't make this clear enough, such that some users
have been trying to pass the output of `.toConfig()` back to `.merge()`,
which can cause errors:
#204 (comment)
edmorley added a commit that referenced this issue Jan 3, 2020
In #84 (released as part of webpack-chain 5.0.0) the syntax for using
`optimization.minimizer` was altered, so that it matched that for
`plugin`, `resolve.plugin` and `resolveLoader.plugin`.

Syntax in webpack-chain 4:

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

Syntax in webpack-chain 5+:

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

Currently if someone uses the old syntax with newer webpack-chain, then
the `minimizer()` call succeeds, but later when `.toConfig()` is called,
the following error is generated:

`TypeError: Cannot read property '__expression' of undefined`

This PR adds an explicit check to `optimization.minimizer()` which
ensures a clearer error message is shown at point of use, so that the
stack trace is more relevant, and root cause clearer.

Refs:
#204 (comment)
@edmorley
Copy link
Member

edmorley commented Jan 3, 2020

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 docs: Correct the .merge() example for optimization.minimizer #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 docs: Emphasise that merge() doesn't accept webpack config objects #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 added a commit that referenced this issue Jan 7, 2020
In #84 (released as part of webpack-chain 5.0.0) the syntax for using
`optimization.minimizer` was altered, so that it matched that for
`plugin`, `resolve.plugin` and `resolveLoader.plugin`.

Syntax in webpack-chain 4:

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

Syntax in webpack-chain 5+:

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

Currently if someone uses the old syntax with newer webpack-chain, then
the `minimizer()` call succeeds, but later when `.toConfig()` is called,
the following error is generated:

`TypeError: Cannot read property '__expression' of undefined`

This PR adds an explicit check to `optimization.minimizer()` which
ensures a clearer error message is shown at point of use, so that the
stack trace is more relevant, and root cause clearer.

Refs:
#204 (comment)
edmorley added a commit that referenced this issue Jan 28, 2020
)

The data structure that is passed to `.merge()` is very similar to a
webpack configuration object, but has a few key differences.

The current docs don't make this clear enough, such that some users
have been trying to pass the output of `.toConfig()` back to `.merge()`,
which can cause errors:
#204 (comment)
@edmorley
Copy link
Member

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 added a commit that referenced this issue Jul 19, 2020
Previously if `.toConfig()` was called when a plugin was only partially
configured, it raised with the unhelpful:

`TypeError: Cannot read property '__expression' of undefined`

Now the scenario results in an Error of form:

`Invalid plugin configuration: plugin('foo').use(<Plugin>) was not called to specify the plugin`

Or for minimizer plugins:

`Invalid optimization.minimizer configuration: optimization.minimizer('foo').use(<Plugin>) was not called to specify the plugin`

Fixes scenario 1 in:
#204 (comment)
edmorley added a commit that referenced this issue Jul 20, 2020
Previously if `.toConfig()` was called when a plugin was only partially
configured, it raised with the unhelpful:

`TypeError: Cannot read property '__expression' of undefined`

Now the scenario results in an Error of form:

`Invalid plugin configuration: plugin('foo').use(<Plugin>) was not called to specify the plugin`

Or for minimizer plugins:

`Invalid optimization.minimizer configuration: optimization.minimizer('foo').use(<Plugin>) was not called to specify the plugin`

Fixes scenario 1 in:
#204 (comment)
edmorley added a commit that referenced this issue Jul 20, 2020
Previously if `.toConfig()` was called when a plugin was only partially
configured, it raised with the unhelpful:

`TypeError: Cannot read property '__expression' of undefined`

Now the scenario results in an Error of form:

`Invalid plugin configuration: plugin('foo').use(<Plugin>) was not called to specify the plugin`

Or for minimizer plugins:

`Invalid optimization.minimizer configuration: optimization.minimizer('foo').use(<Plugin>) was not called to specify the plugin`

Fixes scenario 1 in:
#204 (comment)
@Popxie
Copy link

Popxie commented Dec 30, 2020

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
          })
}

@edmorley edmorley removed their assignment Feb 3, 2024
@edmorley edmorley closed this as completed Feb 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

6 participants