Patrick
Patrick

Reputation: 847

React Js - how to keep bundle.min.js small?

I'm pretty new to React and javascript dev. I'm used to java build tools, so now using NPM i've got a new landscape of build tools to learn. I am just getting going into my project and I noticed that my minified, uglified bundle is still ~275kb and I'm wondering how this could scale to a large size app. My raw source code itself is only 34kb, but of course I have to pull in all those frameworks and whatnot.

So - how can I keep the size of my app small as my app grows? It's a bit hard for me to follow stuff I read online because many folks seem to be using Grunt, but i'm just using npm start and npm run build on the package below.

Should I be managing my requires() in different ways to prevent duplicate packaging? I'm not sure where to start...

Here's my package.json:

{
  "name": "someapp",
  "version": "0.0.1",
  "description": "foo",
  "repository": "",
  "main": "js/app.js",
  "dependencies": {
    "classnames": "^2.1.3",
    "flux": "^2.0.1",
    "jquery": "^2.2.0",
    "keymirror": "~0.1.0",
    "object-assign": "^1.0.0",
    "react": "^0.12.0"
  },
  "devDependencies": {
    "browserify": "^6.2.0",
    "envify": "^3.0.0",
    "jest-cli": "^0.4.3",
    "reactify": "^0.15.2",
    "uglify-js": "~2.4.15",
    "watchify": "^2.1.1"
  },
  "scripts": {
    "start": "watchify -o js/bundle.js -v -d js/app.js",
    "build": "browserify . -t [envify --NODE_ENV production] | uglifyjs -cm > js/bundle.min.js",
    "test": "jest"
  },
  "author": "Some Guy",
  "browserify": {
    "transform": [
      "reactify",
      "envify"
    ]
  },
  "jest": {
    "rootDir": "./js"
  }
}

Upvotes: 4

Views: 5087

Answers (1)

Grgur
Grgur

Reputation: 7382

I was able to achieve pretty good results with Webpack. I wrote about this in Optimizing Webpack Prod Build for React + ES6 Apps

Here's my Webpack config:

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

var nodeEnv = process.env.NODE_ENV || 'development';
var isProd = nodeEnv === 'production';

module.exports = {
  devtool: isProd ? 'cheap-module-source-map' : 'eval',
  context: path.join(__dirname, './client'),
  entry: {
    jsx: './index.js',
    html: './index.html',
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, './static'),
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      {
        test: /\.html$/,
        loader: 'file?name=[name].[ext]'
      },
      {
        test: /\.css$/,
        loaders: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loaders: [
          'react-hot',
          'babel-loader'
        ]
      },
    ],
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    root: [
      path.resolve('./client')
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      sourceMap: false
    }),
    new webpack.DefinePlugin({
      'process.env': { NODE_ENV: JSON.stringify(nodeEnv) }
    })
  ],
  devServer: {
    contentBase: './client',
    hot: true
  }
};

Two key points to consider:

devtool: isProd ? 'cheap-module-source-map' : 'eval',

This one will output minimal sourcemaps, and will use external files for that, which is good for your final bundle size.

    plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      sourceMap: false
    }),
    new webpack.DefinePlugin({
      'process.env': { NODE_ENV: JSON.stringify(nodeEnv) }
    })
  ],

Uglify - well you probably know what it does. Coupled with the process.env setting will weed out quite a bit of dev code from React lib.

CommonsChunkPlugin will allow you to bundle libraries (or other chunks per your liking) to separate build files. This is particularly awesome as it allows you to set up different caching patterns for vendor libraries. E.g. you can cache those more aggressively than your business logic files.

Oh, and if you care to see my package.json that matches this webpack config:

"scripts": {
    "start": "webpack-dev-server --history-api-fallback --hot --inline --progress --colors --port 3000",
    "build": "NODE_ENV=production webpack --progress --colors"
  },
  "devDependencies": {
    "babel-core": "^6.3.26",
    "babel-loader": "^6.2.0",
    "babel-plugin-transform-runtime": "^6.3.13",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "file-loader": "^0.8.4",
    "webpack": "^1.12.2",
    "webpack-dev-server": "^1.12.0",
    "webpack-hot-middleware": "^2.2.0"
  }

Edit: Tree shaking is a shiny new version expected in Webpack 2 (currently in beta). Coupled with the config above, it will be a killer feature that will minify your final bundle significantly.

Edit 2: Webpack 2 I modified an existing sample app to use Webpack 2 config. It resulted in additional 28% savings. See the project here:Webpack 2 sample config project

Upvotes: 1

Related Questions