David East - Author
Web development, Firebase, and UI articles

Create smaller bundles with Firebase and Rollup

I have a new best practice for importing Firebase:

Never do this again

import * as firebase from 'firebase';

This one-liner includes every feature of Firebase by default. Which is as of version 3.6.6 is 96.7kb. This single asset on average loads in 1.1 seconds on "Regular 3G" in the Network panel in Chrome.

Fortunately, it doesn't have to be this way. With the help of Rollup, we can easily import only the features we need.

Do this instead

import * as firebase from 'firebase/app';

This statement imports only the firebase.app code, which is a small container that houses all Firebase features. It's 6.1kb and on "Regular 3G" loads in around 180ms.

Naked import the features you need

import * as firebase from 'firebase/app';
import 'firebase/storage';
// your code goes below
// make sure to use firebase.initializeApp()

Install Rollup

Rollup uses ES2015 modules to tree shake unneeded code from your final bundle.

npm i --save-dev rollup

Resolving the Firebase node module

Rollup works only with ES2015 modules. This isn't going to cut it with the commonjs Firebase node module.

Fortunately, there are two excellent Rollup plugins to help. There's rollup-plugin-node-resolve which tells Rollup to look in the node_modules folder, and rollup-plugin-commonjs which converts commonjs modules to ES2015 modules.

Install these modules, and then you'll write your config.

npm i --save-dev rollup-plugin-node-resolve rollup-plugin-commonjs

Rollup Config

Declare the plugins, and set your entry file, and output file.

const rollup = require('rollup');
const fs = require('fs');
const commonjs = require('rollup-plugin-commonjs');
const nodeResolve = require('rollup-plugin-node-resolve');

rollup.rollup({
  entry: 'dist/index.js',
  plugins: [
    nodeResolve({
      jsnext: true,
      main: true
    }),
    commonjs({
      include: 'node_modules/**'
    })
  ]
}).then((bundle) => {
  const result = bundle.generate({
    format: 'umd',
    moduleName: 'app'
  });

  return bundle.write({
    format: 'umd',
    dest: 'public/index.js',
    moduleName: 'app'
  });
});

Pro-tip: Measure the size

There's two nifty node modules you can use to log the gzipped size of the bundle after the build: gzip-size and pretty-size.

npm i --save-dev gzip-size pretty-size

Require them at the top of the config and then create another chain onto the promise.

 // continuing the snippet above
  return bundle.write({
    format: 'umd',
    dest: 'public/index.js',
    moduleName: 'app'
  });
}).then(_ => {
  const file = fs.readFileSync('public/index.js');
  const bytes = gzipSize.sync(file);
  console.log(`firebase.app & firebase.storage: ${psize(bytes, true)} gzipped`);
});

Now you can keep track of your bundle size as you build.

firebase.app & firebase.storage: 15.7 kB gzipped

Github sample repo

Check out this github repo for a sample. It uses TypeScript, but the idea is the same.