How to Bundle TailwindCSS using RollupJS (Step-by-Step)

Updated

5 min read

How to Bundle TailwindCSS using RollupJS (Step-by-Step)

I recently published react-google-reviews, a library for integrating Google reviews with ReactJS.

And in publishing this library, I originally wanted to use TailwindCSS for styling the components in the library.

So here's a step-by-step guide on bundling a React library that uses TailwindCSS using Rollup.js. Let's dive right in!

Setting up the project

Create a new project folder:

bashmkdir rollup-tailwind
cd rollup-tailwind

Initialize npm package:

bashnpm init -y

For this tutorial, we'll have a simple folder structure:

bash- package.json
    - src
        - components
            - index.ts
            - Button.tsx
        - index.ts

Our Button.tsx will use Tailwind classes like so:

jsximport React from "react";

function Button() {
    return (
        <button className="outline bg-blue-500 text-white">
            Hello World
        </button>
    );
}

In components/index.ts we'll export our Button:

javascriptexport { default as Button } from "./Button";

In src/index.ts, our entry file, we will similarly export the Button:

javascriptimport { Button } from "./components";

export {
    Button
};

Lastly, let's install some dependencies:

bashnpm i -D react react-dom rollup tailwindcss typescript @types/react @types/react-dom @rollup/plugin-typescript @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-terser rollup-plugin-postcss postcss tslib autoprefixer rollup-plugin-dts

We'll be using several Rollup plugins, as well as installing TailwindCSS and PostCSS.

Configuring Rollup.js

Now let's create our rollup.config.js file:

javascriptimport commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import terser from "@rollup/plugin-terser";
import typescript from "@rollup/plugin-typescript";
import postcss from "rollup-plugin-postcss";
import dts from "rollup-plugin-dts";

const packageJson = require("./package.json");

export default [
  {
    input: "src/index.ts",
    output: [
      {
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
      },
      {
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
      },
    ],
    plugins: [
      resolve({
        ignoreGlobal: false,
        include: ['node_modules/**'],
        skip: ['react', 'react-dom'],
      }),
      commonjs(),
      typescript({ tsconfig: "./tsconfig.json" }),
      postcss({
        extract: true, 
        minimize: true,
      }),
      terser(),
    ],
  },
  {
    input: "dist/esm/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts.default()],
    external: [/\.css$/],
  },
];

It's a simple setup. We define our entry point, and the output files will point to our package.json.

We also install several plugins that will be necessary, most importantly rollup-plugin-postcss.

Avoiding React Dependency Conflicts

It's important to skip react-dom and react in the node resolve plugin to avoid errors like "Cannot read properties of null (reading 'useRef')".

Similary, since we're building a React library, we should move react-dom and react to peerDependencies in our package.json:

json{
    "peerDependencies": {
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
    }
}

By setting react and react-dom as externals in our rollup configuration, we avoid these errors caused by having duplicate React versions.

Now let's get back to the project.

Configure package.json

Our rollup.config.js references our main and module properties of our package.json.

So let's modify our package.json scripts like so:

json"scripts": {
    "rollup": "rollup -c --bundleConfigAsCjs"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": [
    "dist"
],

We've added a script to run Rollup and also defined two output files for CommonJS and ES Modules, respectively.

Configure tsconfig.json

Since we're using Typescript, let's initialize a tsconfig.json:

json{
    "compilerOptions": {
      "target": "es5", 
      "esModuleInterop": true, 
      "forceConsistentCasingInFileNames": true,
      "strict": true, 
      "lib": ["dom", "dom.iterable", "esnext"],
      "skipLibCheck": true,
      "jsx": "react", 
      "module": "ESNext",  
      "declaration": true,
      "declarationDir": "types",
      "sourceMap": true,
      "outDir": "dist",
      "moduleResolution": "node",
      "allowSyntheticDefaultImports": true,
      "emitDeclarationOnly": true,
    },
    "include": ["src"],
  }

Pretty simple setup, but note that our outDir points to our dist directory.

Add TailwindCSS

Now we're ready to integrate TailwindCSS into our Rollup bundle process.

You should already have Tailwind installed from previously, but if not:

bashnpm i -D tailwindcss postcss autoprefixer

Then initialize TailwindCSS:

bashnpx tailwindcss init

Modify your tailwind.config.js like normally:

javascript/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.tsx"
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

And now we'll create a main.css located in src/styles/main.css:

css@tailwind base;
@tailwind components;
@tailwind utilities;

We're almost done, now let's finish up our Rollup config.

Integrate Tailwind + PostCSS with Rollup.js

The final step is to modify our rollup.config.js by adding this object to our exported array:

javascript{
input: "src/styles/main.css",
output: [{ file: "dist/index.css", format: "es" }],
plugins: [
    postcss({
        extract: true,
        minimize: true,
    }),
],
},

Here we are pointing to our main.css file and using PostCSS to bundle a dist/index.css file which will contain our compiled TailwindCSS styles.

If you run Rollup now:

npm run rollup

You should see a dist folder be generated as follows:

bash- dist
    - cjs
    - esm
    - index.css

In our Button.tsx component, we had used three classes: outline, bg-blue-500 and text-white.

We can verify whether Rollup bundled our Tailwind styles by opening up dist/index.css:

css.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}
.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}
.outline{outline-style:solid}

And it worked!

We can confirm this further by running npm pack and installing the package onto a blank NextJS app.

Then we import and use the component like so:

jsximport { Button } from "rollup-tailwind";
import "rollup-tailwind/dist/index.css";

export default function Home() {
    return (
        <div style={{ padding: "20px" }}>
            <Button></Button>
        </div>
    );
}

Remember to import the index.css file.

And we see that our Tailwind classes have been bundled using Rollup:

Bundle TailwindCSS Classes with Rollup

Wrapping Up

Integrating TailwindCSS with Rollup isn't too challenging, but requires some knowledge of configuration.

We use PostCSS and Rollup plugins to bundle our Tailwind styles into our package for distribution.

Hope this helps!

Ryan Chiang

Meet the Author

Ryan Chiang

Hello, I'm Ryan. I build things and write about them. This is my blog of my learnings, tutorials, and whatever else I feel like writing about.
See what I'm building →.

Thanks for reading! If you want a heads up when I write a new blog post, you can subscribe below:

2024

2023

© 2023 Ryan Chiang|ryanschiang.com