Skip to content
This repository has been archived by the owner on Dec 16, 2021. It is now read-only.

Webpack 4 "--mode production" breaks #484

Closed
EdmundMai opened this issue Jul 5, 2018 · 6 comments
Closed

Webpack 4 "--mode production" breaks #484

EdmundMai opened this issue Jul 5, 2018 · 6 comments

Comments

@EdmundMai
Copy link

For some reason, when I start the server with

cross-env NODE_ENV=production webpack --config='config/webpack.config.js' --mode production

it breaks and ReactDOM.render(...) returns <undefined></undefined>. Changing it to --mode development fixes it.

cross-env NODE_ENV=production webpack --config='config/webpack.config.js' --mode development

Webpack config:

  module.exports = merge(common, {
    bail: true,
    devtool: shouldUseSourceMap ? "source-map" : false,
    devtool: "cheap-module-source-map",
    entry: paths.appSrc + "/script.js",
    output: {
      path: paths.appBuild,
      filename: `static/js/oyster-webnode-${APP_VERSION}.min.js`,
      chunkFilename: "static/js/[name].chunk.js",
      publicPath: publicPath,
      devtoolModuleFilenameTemplate: info =>
        path
          .relative(paths.appSrc, info.absoluteResourcePath)
          .replace(/\\/g, "/")
    },
    resolve: {
      modules: ["node_modules", paths.appNodeModules].concat(
        process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
      ),
      extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"],
      alias: {
        react: "preact-compat",
        "react-dom": "preact-compat"
      },
      plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])]
    },
    plugins: [
      new BundleAnalyzerPlugin({
        generateStatsFile: generateStatsFile
      }),
      new HtmlWebpackPlugin({
        inject: true,
        template: paths.appHtml,
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true
        }
      }),
      new InterpolateHtmlPlugin(env.raw)
    ]
  });

@nehaabrol87
Copy link

@developit This is the same issue I was facing 3 weeks ago.
As I mentioned on the issue there are 5 plugins that mode = production automatically adds
I bet its one of those 5

Provides process.env.NODE_ENV with value production. Enables FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin and UglifyJsPlugin

@tadeuszwojcik
Copy link

tadeuszwojcik commented Jul 18, 2018

Happens for me as well, in production mode, VNode lacks properties defined via Object.defineProperty, this is what I got:

{
  "children": [...],
  "attributes": {...},
  "preactCompatUpgraded": true,
  "preactCompatNormalized": true
}

There is no type of props there. Everything looks fine in dev mode.

Narrowed it down to UglifyJsPlugin plugin. Turning it off helps, except bundle is size is much larger.
I've noticed that when minified preact-compat createElement isn't called at all.
Setting unused : false seems to be the temp solution at least for now.

@wongjn
Copy link

wongjn commented Jul 25, 2018

Analysis

I looked into this further and it seems (at least in preact.ems.js) the initial VNode class declaration gets mangled into the h() function when run through the UglifyJSPlugin with default options:

Before:

/** Virtual DOM Node */
function VNode() {}

// Skipped code

function h(nodeName, attributes) {
	// Skipped code

	var p = new VNode();
	// Skipped code

	return p;
}

After (interpreted by me):

/*** No class declaration here ***/

function h(nodeName, attributes) {
	// Skipped code

       // `function VNode() {}` becomes the below anonymous class
	var p = new function() {};
	// Skipped code

	return p;
}

As a side-node, even renaming it to anything but still kept the in top-level scope would still work:

/** Virtual DOM Node */
function fooBar() {}

// Skipped code

function h(nodeName, attributes) {
	// Skipped code

	var p = new fooBar();
	// Skipped code

	return p;
}

So it seems the class declarations is being used as a global somewhere or there is some identity conditional that never evaluates to true (since a new instance is created in the mangled form, instead of re-using the top-level declaration).

Workaround

A work around for this is to use reduce_funcs which stops the function getting mangled inside the h() function. The above comment about unused: false also works because again, it keeps the function in the global scope.

Here is a webpack config that can be merged into an existing config (I use WebPack merge to do this). It splits out preact into its own chunk file so that only it gets applied the slightly differing Uglify options (this is because Uglify exclude/include options matches filenames after concatenation, not source chunk names). If you wanted to keep preact in your main bundle, then you can simply change the include/exclude options to the chunk with preact in and get rid of the preact cacheGroup and the chunkFilename setting.

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  output: {
    // Add chunk name to filename so Uglify's include/exclude can target it.
    chunkFilename: '[chunkhash].[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        // Create a separate chunk for preact to apply slightly different
        // Uglify options on it.
        preact: {
          name: 'preact',
          chunks: 'all',
          minSize: 0,
          test: /[\\/]preact[\\/]/,
          priority: 99,
        },
      },
    },
    minimizer: [
      // Prevent function reduction in preact for preact-compat to work.
      new UglifyJsPlugin({
        include: /preact\.js$/,
        uglifyOptions: {
          compress: {
            reduce_funcs: false,
          },
        },
      }),
      // Normal uglifying for everything else.
      new UglifyJsPlugin({
        exclude: /preact\.js$/,
        cache: true,
        parallel: true,
      }),
    ],
  },
};

@gerhardsletten
Copy link

I did also had this problem where I was using preact in production. But found that I would need the reduce_vars setting for UglifyJSPlugin to making react-router work:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// and this config
optimization: {
  minimizer: [
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          reduce_vars: false // see https://github.com/developit/preact/issues/961
        }
      }
    })
  ]
}

And with another package redux-connect there was a problem because it in the transpiled files was require babel-runtime/helpers/jsx instead of react

var _jsx2 = require('babel-runtime/helpers/jsx');

I am no babel-expert but it looks to me like babel-runtime is a deprecated method to use, so I upgraded the package and after switching to this my project works fine with preact-compat and webpack 4

@developit
Copy link
Member

developit commented Aug 1, 2018

Sorry for the absence. I believe we may have merged a workaround for this Uglify bug in Preact itself, which will fix the issues described here. Sorry for the really difficult debugging!

Here's the PR to Preact:
preactjs/preact#1153

We'll release it as a patch update shortly. As mentioned by @gerhardsletten, a temporary workaround is to disable reduce_funcs which contains the constructor miscategorization issue.

@developit
Copy link
Member

This is fixed in Preact 8.3.0. Update and enjoy!

rfreitas added a commit to onfido/onfido-sdk-ui that referenced this issue Feb 21, 2019
On the lazy component loading:
Even though the component was ES6, it was still necessary to import by calling default. Ref: https://webpack.js.org/migrate/4#import-and-commonjs

Disabled the web pack mode behaviour of setting the nodeEnv based on the set mode.
This is because we expect nodeEnv to have more than two possible values.

Upgraded preact compat. This was in hope to fix the lazy loading, but it probably didn’t have much effect. Still an upgrade should be better. Ref: preactjs/preact-compat#484

Updated uglify JS for the same reason that updated preact compat.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants