A lot has been written on the web about this, but I’m still going to write this down so I don’t forget.

I managed to get from a ~400kb bundle (the file was not served as gzipped) to a ~50kb bundle (gzipped), which is pretty amazing, in three steps (and some researching).

I mixed up the steps when writing this post, that’s why the file sizes I am reporting are not consistent. I actually went with Step 2, Step 3, and finally, Step 1.

Step 1: Enable gzip in Nginx / Apache (if you are hosting on your own VPS).

https://www.digitalocean.com/community/tutorials/how-to-add-the-gzip-module-to-nginx-on-ubuntu-14-04

Result: bundle file size served by Nginx reduced from ~175kb to ~48kb 😱

Step 2: Avoid importing whole libraries when you just need modules.

Instead of doing

import { times } from 'lodash'
import { format } from 'date-fns'

Which doesn’t import only the times and format modules (when using create-react-app), do

const times = require('lodash/times')
const format = require('date-fns/format')

(I think it has something to do with Webpack’s Tree Shaking feature not being used or I don’t know, I didn’t research this any further.)

Result: bundle size reduced from ~260kb (~75kb compressed) to ~175kb (~48kb compressed) 🎉

Step 3: If possible, switch to Preact.

https://medium.com/@rajaraodv/using-preact-instead-of-react-70f40f53107c

My experience when switching was the same as mentioned in the article above, but my app was also kind of small when I switched, and I did have a little issue.

Since this.setState() is async, I had to change when an event fired after updating the state of my app (using setState‘s callback). This is actually how I should have written it in the first place, so it’s my bad it didn’t work right away.

// inside a component
// somewhere at the top
private _messages: HTMLDivElement | null

async componentWillMount() {
  const self = this

  // ...

  // I had to rewrite this

  self.setState({ messages: messagesData.messages })
  if (this._messages) {
    this._messages.scrollTop = this._messages.scrollHeight
  }

  // Into this

  self.setState({ messages: messagesData.messages }, () => {
    if (this._messages) {
      this._messages.scrollTop = this._messages.scrollHeight
    }
  })
}

The issue was basically that the _messages <div> scrolled to the bottom before being populated with messages, which obviously resulted with the div not being scrolled at all.

Bonus: use Preact with create-react-app without ejecting:

Check out https://github.com/timarney/react-app-rewired/. It includes a package for Preact, so you won’t need to eject your app to use it.

Bonus 2: use Preact with create-react-app-typescript without ejecting:

You will have to manually add the alias part mentioned in the article above inside

  • /your_project/node_modules/react-scripts-ts/config/webpack.config.prod.js
  • /your_project/node_modules/react-scripts-ts/config/webpack.config.dev.js

Result: with Preact, the bundle size dropped from ~300kb to ~175kb (~86kb to ~38kb compressed) 🙈

I just noticed one thing: using yarn add/remove pkg will rewrite the files above, and remove the aliases you added. Using npm i --save pkg will not have this effect.

Hope this helps anybody.

Leave a Reply

Your email address will not be published. Required fields are marked *