65

I'm writing a web app using react and webpack as my module bundler. My jsx code is really light so far, the size of the entire folder is 25 kb.

My bundle.js created from webpack is 2.2 mb though. After running the optimization with the -p flag, it reduces the bundle to 700kb, which is still extremely big.

I have looked into the react.min.js file and its size is 130kb.

Is it possible that the webpack produces such big files or am I doing something wrong?

webpack.config.js

var path = require('path');
var webpack = require('webpack');

module.exports = {
  entry: './public/components/main.jsx',
  output: {
    path: __dirname + "/public",
    filename: 'bundle.js'
  },
  module: {
    loaders: [{
      test: /.jsx?$/,
      loader: 'babel-loader',
      exclude: /node_modules/,
      query: {
        presets: ['es2015', 'react']
      }
    }, {
      test: /\.css$/,
      loader: "style!css"
    }]
  }
};

EDIT

package.json:

{
  "name": "XChange",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "main": "./bin/www",
  "devDependencies": {
    "body-parser": "~1.13.2",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "express": "~4.13.1",
    "jade": "~1.11.0",
    "morgan": "~1.6.1",
    "serve-favicon": "~2.3.0",
    "react-dom": "~0.14.3",
    "react": "~0.14.3",
    "webpack": "~1.12.9",
    "babel-loader": "~6.2.0",
    "babel-core": "~6.2.1",
    "babel-preset-react": "~6.1.18",
    "babel-preset-es2015": "~6.1.18",
    "react-bootstrap": "~0.28.1",
    "material-ui": "~0.14.0-rc1",
    "history": "~1.13.1",
    "react-router": "~1.0.2",
    "style-loader": "~0.13.0",
    "css-loader": "~0.18.0"
  },
  "dependencies": {
    "express-validator": "~2.18.0",
    "mongoose": "~4.2.9",
    "kerberos": "~0.0.17",
    "bcrypt": "~0.8.5"
  }
}
itaied
  • 6,827
  • 13
  • 51
  • 86
  • Are your only dependencies `react` and `react-dom`? I have seen some dependencies pull in parts of node (such as assert), and that can get big. I dont know webpack very well but it looks good from here. Maybe try compiling a single script with just a single small component, no other dependencies, and see the bundle size – Matt Styles Dec 12 '15 at 13:10
  • webpack is going to bundle all your dependencies in your single `bundle.js` file, including CSS. Do you require other files than `react` and your own files? Could you post the `dependencies` section in your `package.json` file? How big are your CSS files? – dreyescat Dec 12 '15 at 13:28
  • My css files are negligible, and I am using `react-bootstrap` and `material-ui`. I didn't know webpack also packaging the dependencies in my `package.json`. I am using the `import` statements in my `jsx` files to use the components. @dreyescat – itaied Dec 12 '15 at 13:45
  • Webpack is not packaging the dependencies in your `package.json`. It was just to have an idea of what other dependencies other than `react` you had. However, if you are using `react-bootstrap` and `material-ui` (you require them) then yes. You are also packaging them in your bundle file. And this is why you get such a big file. – dreyescat Dec 12 '15 at 14:08

5 Answers5

114

According to your comments you are using material-ui and react-bootstrap. Those dependencies are bundled by webpack along with your react and react-dom packages. Any time you require or import a package it is bundled as part of your bundle file.

And here it comes my guess. You are probably importing the react-bootstrap and material-ui components using the library way:

import { Button } from 'react-bootstrap';
import { FlatButton } from 'material-ui';

This is nice and handy but it does not only bundles Button and FlatButton (and their dependencies) but the whole libraries.

One way to alleviate it is to try to only import or require what is needed, lets say the component way. Using the same example:

import Button from 'react-bootstrap/lib/Button';
import FlatButton from 'material-ui/lib/flat-button';

This will only bundle Button, FlatButton and their respective dependencies. But not the whole library. So I would try to get rid of all your library imports and use the component way instead.

If you are not using lot of components then it should reduce considerably the size of your bundled file.

As further explanation:

When you are using the library way you are importing all these react-bootstrap and all these material-ui components, regardless which ones you are actually using.

Niyaz
  • 53,943
  • 55
  • 151
  • 182
dreyescat
  • 13,558
  • 5
  • 50
  • 38
  • 1
    Thanks for the detailed explanation. I thought that importing a specific element from the library, like you just described, will bundle only the component and not the whole library. I will implement it this way now. Thanks again! – itaied Dec 12 '15 at 16:01
  • Lets say that I have a lot of components to import, is there a shorthand to write all of those imports? @dreyescat – itaied Dec 12 '15 at 16:11
  • 3
    @Candroid make a `mystuff` file that imports just the components you need in your app and reexports them. Then the rest of your app can just import `mystuff`. – Brandon Dec 12 '15 at 17:04
  • In the html file, we only include the script bundle.js. In order to make the bundle smaller, Can I exclude the react and react-dom from the bundle and include the CDN scripts for them? – Casper Jul 01 '16 at 09:03
  • 4
    Can anyone explain or link to an article as to why import { foo } from 'bar'; vs. import foo from 'bar/foo'; are different? I figured webpack would optimize unused code out – Steve Willard Aug 25 '16 at 03:10
  • @dreyescat, is there any clever way to optimize when your code splitting? because For me it doesnt matter if i import import { FlatButton } from 'material-ui' or import FlatButton from 'material-ui/lib/flat-button' My vendor.js bundle is always the same size. – jasan Sep 12 '16 at 07:34
  • How can I import only 3 components from a library? `import Line, Bar, Doughnut from 'react-chartjs-2';` gives an error. `import Line from 'react-chartjs-2'; import Bar from 'react-chartjs-2'; import Doughnut from 'react-chartjs-2';` only imports Doughnut. – Rado Nov 28 '16 at 16:06
  • I'm using webpack ~1.14 and I can't get the "component way" to work. I get a `Module not found` error. – Greg Dec 20 '16 at 20:01
  • 2
    @SteveWillard as far as I know it has to do with how the module has structured their exports. `{ foo } from 'bar'` is just syntax sugar for `require('bar').foo`, where the `bar` `module.exports = {foo: etc}`. `foo from 'bar/foo'` is equivalent to `require('bar/foo')`, where `bar/foo module.exports = etc`--ie, not nested in an object. – Brandon Jan 12 '17 at 20:18
  • Isn't this recommendation obsolete? From the docs (https://material-ui.com/guides/minimizing-bundle-size/#when-and-how-to-use-tree-shaking) `Tree-shaking of Material-UI works out of the box in modern frameworks. ... you can safely use named imports and still get an optimised bundle size automatically` I just did a test on a very simple site, importing modules directly actually made things worse in one instance?!? See also last answer on this: https://stackoverflow.com/a/52638942/9316077 – Will59 Mar 26 '21 at 12:42
31

01/2017 EDIT - I've since learned a little more about different Webpack Plugins, and wanted to update this. It turns out that UglifyJS has a littany of config options that don't seem to be very mainstream, but can have a dramatic effect on your bundle size. This is my current config w/ some annotations (docs on site are great):

 new webpack.optimize.UglifyJsPlugin({
      comments: false, // remove comments
      compress: {
        unused: true,
        dead_code: true, // big one--strip code that will never execute
        warnings: false, // good for prod apps so users can't peek behind curtain
        drop_debugger: true,
        conditionals: true,
        evaluate: true,
        drop_console: true, // strips console statements
        sequences: true,
        booleans: true,
      }
    })

I once encountered an obscure problem with uglify-ication of escaped unicode chars, so be mindful if you employ these transformations that edge-case things like that are possible.

You can read more about the specific options webpack supports in the webpack docs w/ some follow-on links to further reading.


(sidenote: I think your package.json is mixed-up... at least a few of those dev-dependencies are dependencies in every package.json I've seen (e.g., the react-starter-kit)

If you're preparing for production, there are a few more steps you should take to get your file size down. Here's a snip of my webpack.config.js:

 plugins: [


        new webpack.optimize.UglifyJsPlugin(),
        new webpack.optimize.DedupePlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }
        })
    ],

1) minifies/uglifies your code

2) replaces duplicate code to minimize file-size

3) tells webpack to omit some things it uses for node environment builds

Finally, if you use a source map (which you probably should), you'll want to add the appropriate line. Sentry wrote a nice blog post about this.

In my build, i use devtool: 'source-map' for production

Brandon
  • 7,736
  • 9
  • 47
  • 72
  • 3
    Webpack is some black magic. The last of the three plugins cuts down the bundle size from 218kb to 143kb when bundling react and react-dom. I would not expect it to affect bundle size at all. – Capaj Jul 08 '16 at 22:11
  • 3
    @Capaj there's no Webpack black magic here. React uses the `NODE_ENV` as a feature toggle to remove checks like PropTypes. See [this answer](http://stackoverflow.com/a/36285479/645663) for more information. – Nick Frezynski Jul 18 '16 at 16:33
  • 3
    As a heads up the DedupePlugin has been removed from Webpack, it should roughly do this by default now. – Chase Aug 01 '17 at 20:33
  • The reference to UglifyJSPlugin options in the answer is outdated, the correct one it now at https://webpack.js.org/plugins/uglifyjs-webpack-plugin/. – void Feb 08 '19 at 11:52
17

UPDATED 05/18 : update UglifyJsPlugin setting for better minification

I use below configuration for the minification in production code.

 plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        // This has effect on the react lib size
        'NODE_ENV': JSON.stringify('production'),
      }
    }),
    new ExtractTextPlugin("bundle.css", {allChunks: false}),
    new webpack.optimize.AggressiveMergingPlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      compress: {
        warnings: false, // Suppress uglification warnings
        pure_getters: true,
        unsafe: true,
        unsafe_comps: true,
        screw_ie8: true,
        conditionals: true,
        unused: true,
        comparisons: true,
        sequences: true,
        dead_code: true,
        evaluate: true,
        if_return: true,
        join_vars: true
      },
      output: {
        comments: false,
      },
      exclude: [/\.min\.js$/gi] // skip pre-minified libs
    }),
    new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]), 
    new CompressionPlugin({
      asset: "[path].gz[query]",
      algorithm: "gzip",
      test: /\.js$|\.css$|\.html$/,
      threshold: 10240,
      minRatio: 0
    })
  ],
Khalid Azam
  • 1,615
  • 19
  • 17
2

Have you looked at how you're scripts are being sent over the wire... I had some very simple react components that were up around 300kb each, and that was after the webpack optimizations. After they were gzipped they came down to 38kb. Still sizable - but that's what we get for using tomorrows features today. If you're using node/express to serve static resources, including your javascript - look at compression (https://github.com/expressjs/compression). I'd also suggest looking at the node best practices guide for production https://expressjs.com/en/advanced/best-practice-performance.html If you're not serving files through node, then apache (or other webserver) will have options for compressing text based files.

kimmiju
  • 161
  • 1
  • 3
1

I find it useful to mention the source-map-explorer utility that helps in knowing what exactly is in your bundle.js file. It can help you identify if there are any unnecessary things in bundle js. you can install the source-map-explorer from npm and use it like

source-map-explorer yourBundle.js

Besides this, as mentioned by @kimmiju , check if your server is using some compression.

Network Tab in Chrome

You can also try to asynchronously load routes (lazy loading in webpack) , so that your entire bundlejs file is not sent in one go , instead it is sent in chunks when user navigates to those routes.

Rahil Ahmad
  • 3,056
  • 1
  • 16
  • 21