Rollup Config for React Component Library With TypeScript + SCSS

Introduction

In this article, I will try to cover the main areas that are crucial in making the Rollup configurations work in building a React component library, with TypeScript and SCSS in particular.

At the same time, I will put down some notes for the various Rollup plugins being used, so we know exactly which plugin is doing what.

I am by no means a master in Rollup nor this is a definitive guide in building a React Component Library. I am just sharing the current Rollup configurations that I use to build my own React component library – Codefee-Kit, serving as a note for myself and perhaps could be of help to a few souls out there.

The library is hosted on GitHub by the way – https://github.com/DriLLFreAK100/codefee-kit

Motivation

Prior to this, I was using Webpack to build the library. Had a lot of hustles in getting my first working version back then. I still remember that there was this property called “library” that you need to set explicitly, in order for Webpack to pick things up for a library. I was a fool and googled it quite a big bit before I understood the whole thing. Much time were invested in that entire trial and error process. I certainly did not enjoy the complexity of the configuration file there. Well, who does? Hahaha..

Even so, that working version back then wasn’t very optimized. By that, I mean that there weren’t any code-splitting being configured, and that the output of the build is always just an increasingly big index.js file.

Lately, I have finally gotten myself some free time again! And so, I decided to revisit this topic. Went through some hell digging through how to code-split in Webpack and I just find it too cumbersome.

In the end, I decided to put a full stop there once and for all, and hop over to the other popular choice for building library – Rollup! And man, it was way simpler to configure and it just saved me so much time! Oh God, why didn’t I hop over sooner?! Screw me!

There was a saying that goes like this, “Rollup for libraries, Webpack for apps”. And turns out, it is still pretty relevant at this point of time!

 Rollup for libraries, Webpack for apps

https://medium.com/@PepsRyuu/why-i-use-rollup-and-not-webpack-e3ab163f4fd3

Topics Coverage

Please feel free to skip to the sections of your interest.

  1. Rollup Configuration Files
  2. Rollup Code Splitting
  3. Notes for Rollup Plugins

1. Rollup Configuration Files

Here is the configuration file that works for me. Quite a few moving parts there but I tried to clean it up as much as I could, so that it doesn’t hurt the eyes too much haha… 😛

rollup.config.js

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';

import postcss from "rollup-plugin-postcss";
import visualizer from 'rollup-plugin-visualizer';
import { terser } from 'rollup-plugin-terser';
import { getFiles } from './scripts/buildUtils';

const extensions = ['.js', '.ts', '.jsx', '.tsx'];

export default {
  input: [
    './src/index.ts',
    ...getFiles('./src/common', extensions),
    ...getFiles('./src/components', extensions),
    ...getFiles('./src/hooks', extensions),
    ...getFiles('./src/utils', extensions),
  ],
  output: {
    dir: 'dist',
    format: 'esm',
    preserveModules: true,
    preserveModulesRoot: 'src',
    sourcemap: true,
  },
  plugins: [
    resolve(),
    commonjs(),
    typescript({
      tsconfig: './tsconfig.build.json',
      declaration: true,
      declarationDir: 'dist',
    }),
    postcss(),
    terser(),
    visualizer({
      filename: 'bundle-analysis.html',
      open: true,
    }),
  ],
  external: ['react', 'react-dom'],
};

As you can see, you just need to export a JSON object for Rollup to work. You can also opt to export an Array if you have multiple configs to fulfill, e.g. bundle for different targets like ‘umd’, ‘cjs’, etc.

There are 4 attributes I have configured here:

  1. input – Entry point(s) for the build. Can be a string or an array of strings
  2. output – The build output configs, e.g. output directory, enable sourcemap generation, etc.
  3. plugins – External packages invocation that helps manipulate, change build behaviors, e.g. for TypeScript, SCSS, etc.
  4. external – Packages that should not be in our bundle, normally are peerDependencies. In my case, it’s “react” and “react-dom” packages.

TypeScript Configs

The following are my tsconfig files. I have 2 of them, one is used by Rollup for build purpose, the other one is acting like base config, primarily used in development.

Reason behind, is that I want to exclude my Storybook stories files from being transpiled by TypeScript. It is only intended for development time, and I need the tsconfig for it. Hence, a separate config with additional “excludes” are being created for build.

Here it is:

tsconfig.build.json

{
  "extends": "./tsconfig.json",
  "exclude": [
    "node_modules",
    "build",
    "dist",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest",
    "src/stories/**"
  ]
}

tsconfig.json

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es5",
    "lib": [ "es6", "dom" ],
    "sourceMap": true,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDir": "src",
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "esModuleInterop": true,
    "baseUrl": "src/",
    "paths": {
      "common": ["src/common/*"],
      "components": ["src/components/*"],
      "hooks": ["src/hooks/*"],
      "utils": ["src/utils/*"]
    }
  },
  "exclude": [
    "node_modules",
    "build",
    "dist",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest"
  ],
  "types": ["typePatches"]
}

2. Rollup Code Splitting

In the configuration above, I was actually utilizing the code-splitting feature of Rollup.

If you notice, the input property in my exported JSON object is actually an array of strings, rather than a single string. This is effectively telling Rollup to treat each of these strings as a separate entry point for the build. Hence, code-splitting the build.

All these strings in the Array are actually the relative paths to those individual .ts and .tsx files that I have in my src folder. The getFiles method is a utility method that I’ve written to help me deep retrieve all those files with .js, .jsx, .ts and .tsx extensions.

Here is the code for the getFiles method

const fs = require('fs');

export const getFiles = (entry, extensions = [], excludeExtensions = []) => {
  let fileNames = [];
  const dirs = fs.readdirSync(entry);

  dirs.forEach((dir) => {
    const path = `${entry}/${dir}`;

    if (fs.lstatSync(path).isDirectory()) {
      fileNames = [
        ...fileNames,
        ...getFiles(path, extensions, excludeExtensions),
      ];

      return;
    }

    if (!excludeExtensions.some((exclude) => dir.endsWith(exclude))
      && extensions.some((ext) => dir.endsWith(ext))
    ) {
      fileNames.push(path);
    }
  });

  return fileNames;
};

3. Notes for Rollup Plugins

Here are the plugins that I have used in the config file above, and some of my lamen interpretation and understanding after using them.

@rollup/plugin-node-resolve

For Rollup to locate and bundle third-party dependencies in node_modules. The dependencies meant here are the dependencies listed in your package.json file.

@rollup/plugin-commonjs

For Rollup to convert CommonJS modules into ES6, so that they can be included in a Rollup bundle. It is typically used along with @rollup/plugin-node-resolve, to bundle the CommonJS dependencies.

@rollup/plugin-typescript

To help integrate with TypeScript in an easier fashion. Covers things like transpiling TypeScript to JavaScript.

rollup-plugin-postcss

To integrate with PostCSS. For my use case, it helps to build CSS and SCSS files into *.css.js and *.scss.js files respectively. These files are then injected into the HTML head tag (relies on style-inject) accordingly when the corresponding Components are being imported.

** In my current development, I have swapped over to use styled-components instead, in favor of trying out CSS-in-JS pattern.

rollup-plugin-visualizer

This plugin is used to help us analyze the bundle output. It will generate a cool visualization for our inspection. It is especially useful when doing bundle size optimizations, as it lets us visualize the individual sizes of our bundle files.

rollup-plugin-terser

It’s basically a plugin to integrate the usage of terser. It helps to minify and compress our output JavaScript files.

Conclusion

After using both Rollup and Webpack to build a React component library that uses TypeScript and SCSS… I’ve gotta say, just freakin use Rollup for this purpose. It’s much easier to configure as opposed to Webpack.

The config complexity alone, is a good enough reason for me to hop over. I would love to have a configuration file that I can easily reason about at any point of time, not something so verbose and complex that I’ll possibly forget what’s doing what in just a matter of few weeks time!

However, I might not necessarily feel the same for application development, as Webpack has really robust Hot Module Replacement capabilities. It is definitely a life saver for that and a must have for app development.

The landscape for build tools are ever changing and it might not be surprising if another de facto bundler was to appear and take the community by storm the next day! But at least for now, Rollup is my new lover 😛


Alright, coffee time! haha… Lately, I’m growing some deep love for Filter Coffee.

Ethiopia Yirgacheffe Filter Coffee in the house!

Resources

  1. https://github.com/rollup/plugins
  2. https://rollupjs.org/guide/en/
  3. https://marcobotto.com/blog/compiling-and-bundling-typescript-libraries-with-webpack/
  4. https://github.com/HarveyD/react-component-library
  5. https://github.com/rollup/rollup-plugin-commonjs