webpack-contrib/terser-webpack-plugin

Minifying moves await keyword inside inlined non-async function

mikko-ahlroth-vincit opened this issue · 2 comments

Bug report

What is the current behavior?

Repro code (main.js):

export const plus = (something, anything) => {
    return something + anything;
};

class _Foo {
    async bar(something) {
        const anything = await new Promise(() => {});
        return plus(something, anything);
    }
}

export const Foo = new _Foo();

This is nonsense code, but it's minimised from a real case.

Running webpack 5.90.1 (or 5.88.2) on this in production mode produces the following code (added line breaks and indentation for clarity):

(()=>{
  "use strict";
  new class{
    async bar(s){
      return((s,a)=>s+await new Promise((()=>{})))(s)
    }
  }
})();

As can be seen, the function plus is inlined inside bar, and the await is moved to be inside that inlined function, instead of being passed in as an argument. But since the inlined anonymous function is not async, there can be no await there, and the code will not parse.

I tried reproducing this with Terser, but I have not found the correct flags to produce it, which is why I'm filing an issue here as I don't know if this plugin or webpack does some other transformations too. Please don't hesitate to ask me to move it elsewhere. :)

Terser test (Terser 5.27.0):

zsh 1841  (git)-[develop]-% node_modules/.bin/terser -c 'passes=2' -m --module ./main.js
export const plus=(s,o)=>s+o;export const Foo=new class{async bar(s){const o=await new Promise((()=>{}));return plus(s,o)}};

If the current behavior is a bug, please provide the steps to reproduce.

Save the example code in main.js and run npx webpack --mode production ./main.js, or create an empty NPM project with npm init, install webpack and webpack-cli as (dev) dependencies, then run node_modules/.bin/webpack --mode production ./main.js.

What is the expected behavior?

The await should not be moved inside the inlined function, it should be passed in as an argument from the async bar function, like in the Terser output.

Workaround

By changing the original code to using the then API, valid code is generated. Something like

class _Foo {
    bar(something) {
        return (new Promise(() => {})).then(anything => plus(something, anything));
    }
}

Other relevant information:

System:
OS: Linux 5.14 Red Hat Enterprise Linux 9.2 (Plow)
CPU: (4) x64 12th Gen Intel(R) Core(TM) i7-12700H
Memory: 7.93 GB / 15.62 GB
Binaries:
Node: 16.20.1 - /usr/bin/node
npm: 8.19.4 - /usr/bin/npm
Packages:
webpack: 5.90.1 => 5.90.1
webpack-cli: 5.1.4 => 5.1.4

There are no problems in terser-webpack-plugin, you can reproduce it using:

import { minify } from "terser";

// Code generated by webpack
const code = `/******/ (() => { // webpackBootstrap
/******/        "use strict";
/******/        // The require scope
/******/        var __webpack_require__ = {};
/******/
/************************************************************************/
/******/        /* webpack/runtime/define property getters */
/******/        (() => {
/******/                // define getter functions for harmony exports
/******/                __webpack_require__.d = (exports, definition) => {
/******/                        for(var key in definition) {
/******/                                if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/                                        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/                                }
/******/                        }
/******/                };
/******/        })();
/******/
/******/        /* webpack/runtime/hasOwnProperty shorthand */
/******/        (() => {
/******/                __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/        })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
/* unused harmony exports plus, Foo */
const plus = (something, anything) => {
        return something + anything;
};

class _Foo {
        async bar(something) {
                const anything = await new Promise(() => {});
                return plus(something, anything);
        }
}

const Foo = new _Foo();

/******/ })()
;
`;

const result = await minify(code, {
  compress: {
    passes: 2,
  },
  module: false,
  ecma: 2015
});

console.log(result);

and looks like iife is the problem here, because if you remove it, everything is fine, webpack runtime code doesn't change nothing

I will open an issue in terser

Let's close in favor - terser/terser#1489, thank you for your report