Create-react-app: Build produces incorrect JS for specific switch-case block

Created on 15 Jan 2020  路  9Comments  路  Source: facebook/create-react-app

Describe the bug

This is most probably a bug in one of the build tools used by react-scripts, or some misconfiguration. I recently ran into unexpected functionality in production build. https://github.com/AriPerkkio/asus-merlin-simple-vpn-client-setup/issues/1
The code works just fine on development mode but breaks on production build. I've traced the bug into a switch-case block which produces incorrect results after transpile process. I could debug this further but don't have much time for this now. I've prepared a minimal reproduce branch for this, see below.

Did you try recovering your dependencies?

Yes, I've ran rm -rf node_modules/ yarn.lock.

Which terms did you search for in User Guide?

Read it many times, great documentation!

Environment

Environment Info:

System:
OS: Linux 4.19 Debian GNU/Linux 9 (stretch) 9 (stretch)
CPU: (4) x64 06/8e
Binaries:
Node: 13.6.0 - /usr/bin/node
Yarn: 1.21.1 - /usr/bin/yarn
npm: 6.13.4 - /usr/bin/npm
Browsers:
Chrome: Not Found
Firefox: Not Found
npmPackages:
react: ^16.12.0 => 16.12.0
react-dom: ^16.12.0 => 16.12.0
react-scripts: 3.3.0 => 3.3.0
npmGlobalPackages:
create-react-app: Not Found

Steps to reproduce

$ git clone https://github.com/AriPerkkio/asus-merlin-simple-vpn-client-setup.git
$ cd asus-merlin-simple-vpn-client-setup
$ git checkout transpile-bug
$ yarn # Install dependencies
$ yarn build # Builds minimal setup and runs prettier on it
# Check build/ui/static/js/main.<hash>.chunk.js

Expected behavior

Input:

case 'B': {
    const { key } = action;
    const keys = state.keys.map(_key =>
        _key.id === key.id ? key : _key
    );

    return { ...state, keys };
}

Unexpected output in build:

case 'B':
    t.key;
    var i = e.keys.map(function(e) {
        return e.id, e.id, e;
    });
    return c({}, e, { keys: i });

Rename _key

case 'B': {
    const { key } = action;
    const keys = state.keys.map(_someKey =>
        _someKey.id === key.id ? key : _someKey
    );

    return { ...state, keys };
}

Output is as expected:

case 'B':
    var i = t.key,
        u = e.keys.map(function(e) {
            return e.id === i.id ? i : e;
        });
    return c({}, e, { keys: u });

Actual behavior

See above.

Reproducible demo

See steps to reproduce.

bug report needs investigation needs triage

All 9 comments

This issue seems to be caused by babel-preset-react-app. Minimal repo without CRA https://github.com/AriPerkkio/babel-preset-react-app-transpile-bug.

I have the same issue.

babel-plugin-transform-react-remove-prop-types is used with removeImport: true option. By setting it to false the issue seems to be fixed. @oliviertassinari Any ideas what's going on here?

      isEnvProduction && [
        // Remove PropTypes from production build
        require('babel-plugin-transform-react-remove-prop-types').default,
        {
          removeImport: true,
        },
      ]

This setup is enough to reproduce this:

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "entry",
                "corejs": 3,
                "modules": false
            }
        ]
    ],
    "plugins": [
        [
            "babel-plugin-transform-react-remove-prop-types",
            { "removeImport": true }
        ]
    ]
}

Some more debugging details. The cause still seems to be babel-plugin-transform-react-remove-prop-types.

Babel output when removeImport is true.

case 'B': {
    var _key = action.key;

    var _keys = state.keys.map(function(_key) {
        return _key.id === _key.id ? _key : _key;
    });

    return _objectSpread({}, state, {
        keys: _keys,
    });
}

Conflicting var key and map(function(_key) makes terser go crazy:

case 'B':
    action.key;
    var _keys = state.keys.map(function(_key) {
        return _key.id, _key.id, _key;
    });
    return _objectSpread({}, state, { keys: _keys });

Output when removeImport is set false;

// Babel
case 'B': {
    var _key2 = action.key;

    var _keys = state.keys.map(function(_key) {
        return _key.id === _key2.id ? _key2 : _key;
    });

    return _objectSpread({}, state, {
        keys: _keys,
    });
}

// Terser
case 'B':
    var _key2 = action.key,
        _keys = state.keys.map(function(_key) {
            return _key.id === _key2.id ? _key2 : _key;
        });
    return _objectSpread({}, state, { keys: _keys });

More details. I've checked plugins from preset-env one-by-one and found incompatibility between @babel/plugin-transform-block-scoping and babel-plugin-transform-react-remove-prop-types.

This setup is enough for reproducing;

{
    "plugins": [
        "@babel/plugin-transform-block-scoping",
        [
            "babel-plugin-transform-react-remove-prop-types",
            { "removeImport": true }
        ]
    ]
}

I've been studying this using this playground repo.
As before, setting removeImport: false fixes the issue. Next I'll try to debug babel but I'm not familiar with AST and compilers.

@AriPerkkio if you need assistance or anything, feel free to ping us over at https://github.com/babel/babel and/or open an issue!

I found that scope.crawl() method used by babel-plugin-transform-react-remove-prop-types is causing this. By commenting it out the bug disappeared. I searched for other plugins using the same method and found babel-plugin-remove-debug. The issue can now be reproduced without babel-plugin-transform-react-remove-prop-types.

{
    "plugins": [
        "@babel/plugin-transform-block-scoping",
        "babel-plugin-remove-debug"
    ]
}

Bug opened to babel: https://github.com/babel/babel/issues/11057

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

Last comments before the bot closes this issue.

This bug is still valid. If you encounter this try renaming your variables and check how the code transpiled from the minified bundle.
Unit tests and development environment cannot be trusted 100%. Integration and manual tests against production build are the only ways to validate the actual functionality.

Projects where react-scripts has been ejected can flip the removeImport flag - and hope that no other plugin will cause this in the future.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JimmyLv picture JimmyLv  路  3Comments

stopachka picture stopachka  路  3Comments

xgqfrms-GitHub picture xgqfrms-GitHub  路  3Comments

fson picture fson  路  3Comments

barcher picture barcher  路  3Comments