Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack config for Code splitting not working for production build

Building a ReactJS application with Webpack. Recently interested in using code splitting to reduce app size.

I've tried implementing a custom HOC that wrapped System.import():

/* async/index.tsx */
... at a very high level looked like...
class Async extends React ... {
    componentWillMount() {
        this.props.load.then(c => {
            this.component = c;
            this.setState({loaded:true});
        }
    }
    render() {
        return this.component ? <this.component.default {...this.props} /> : <span />;
    }
}

/* async/foo/index.tsx */
import Async from 'async';
const Foo = (props) => (
    <Async
      {...props}
      load={System.import('async/foo/body')}
    />);

class Foo ... {
    ...
    render = () => <Foo myProp={'bar'} />;
}

Currently I'm trying react-loadable (https://github.com/thejameskyle/react-loadable), a package that does essentially the same thing but with some extra bells n whistles.

Problem

Both methods work fine locally, but do not work when deployed. Webpack configurations are derived from React-Starter-App, from early March 2017. My gut tells me that the webpack config is the source of the problem, but I'm not sure how to debug this.

Dev Config (works)

/* relevant configs */
module.exports = {
  entry: [
      require.resolve('react-dev-utils/webpackHotDevClient'),
      require.resolve('./polyfills'),
      paths.appIndexJs
  ],
  output: {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/bundle.js',
    publicPath: publicPath
  },
  ...
  plugins: [
    new ExtractTextPlugin({filename: 'style.css', allChunks: true}),
    new InterpolateHtmlPlugin(env.raw),
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
    }),
    new BundleAnalyzerPlugin(),
    new webpack.DefinePlugin(env.stringified),
    new webpack.HotModuleReplacementPlugin(),
    new CaseSensitivePathsPlugin(),
    new webpack.LoaderOptionsPlugin({
      debug: true
    }),
    new WatchMissingNodeModulesPlugin(paths.appNodeModules)
  ]
}

Staging Config (not working)

module.exports = {
  bail: true,
  devtool: 'source-map',
  entry: [
    require.resolve('./polyfills'),
    paths.appIndexJs
  ],
  output: {
    path: paths.appBuildStaging,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: publicPath,
  },
  resolve: {
    modules: ['node_modules', paths.appNodeModules].concat(paths.nodePaths).concat(paths.appSrc),
    extensions: ['.ts', '.tsx', '.scss', '.js', '.json', '.jsx']
  },
  ...
  plugins: [
    new InterpolateHtmlPlugin(env.raw),
    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 webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module) {
        // this assumes your vendor imports exist in the node_modules directory
        return module.context && module.context.indexOf('node_modules') !== -1;
      }
    }),
    //CommonChunksPlugin will now extract all the common modules from vendor and main bundles
    new webpack.optimize.CommonsChunkPlugin({ 
      name: 'manifest' //But since there are no more common modules between them we end up with just the runtime code included in the manifest file
    }),
    new BundleAnalyzerPlugin(),
    new webpack.DefinePlugin(env.stringified),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true, //. React doesn't support IE8
        warnings: false,
        drop_console: false,
      },
      mangle: {
        screw_ie8: true,
      },
      output: {
        comments: false,
        screw_ie8: true,
      },
      sourceMap: true,
    }),
    new ExtractTextPlugin({
      filename: cssFilename,
    }),
    new ManifestPlugin({
      fileName: 'asset-manifest.json',
    }),
  ]
}

Errors: react-loadable swallows all errors (palm + face) so I'm not able to provide useful errors from that code. My custom component would throw an error in the bootstrap loader bootstrap 6a12c6c…:54 Uncaught (in promise) TypeError: Cannot read property 'call' of undefined

In the network traffic for my custom HOC, the extra bundle is not loaded. In the network traffic for react-loadable, the bundle is loaded, but it is never processed.

like image 980
sean.hudson Avatar asked Jun 05 '17 19:06

sean.hudson


People also ask

How do you code split in webpack?

There are three general approaches to code splitting available: Entry Points: Manually split code using entry configuration. Prevent Duplication: Use Entry dependencies or SplitChunksPlugin to dedupe and split chunks. Dynamic Imports: Split code via inline function calls within modules.

Is webpack used in production?

Webpack v4+ will minify your code by default in production mode . Note that while the TerserPlugin is a great place to start for minification and being used by default, there are other options out there: ClosureWebpackPlugin.

What does webpack mode production do?

Providing the mode configuration option tells webpack to use its built-in optimizations accordingly.


1 Answers

So after all this time, after upgrading Typescript and Webpack, it turns out that using the CommonsChunk plugin was screwing it up somehow.

Have not yet investigated why, but commenting out the following worked:

// new webpack.optimize.CommonsChunkPlugin({
//   name: 'vendor',
//   minChunks: function (module) {
//     return module.context && module.context.indexOf('node_modules') !== -1;
//   }
// }),
like image 89
sean.hudson Avatar answered Nov 08 '22 18:11

sean.hudson