josemi
projects notes
React ESM bundle
Creating ESM bundles of React and ReactDOM to use in the browser with Import Maps.
April 20, 2025
Introduction
React and ReactDOM are distributed as CommonJS modules by default. But what if you want to use them directly in the browser, with native ES modules and an importmap? In this note, I will show you how to create a clean, minified ESM bundle of React and ReactDOM using Rollup - and do it programmatically using Rollup's JavaScript API.
Step 1: Set Up Your Project
First, initialize your project:
npm init -y
Install the required dependencies:
npm install react react-dom
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
Step 2: Entry Files for React and ReactDOM
Since we want to treat React as a module, we will create two small entry files:
// react.js
import * as React from "react";
export * from "react";
export default React;
// react-dom.js
import * as ReactDOM from "react-dom";
export * from "react-dom";
export default ReactDOM;
These files serve as the input points for our Rollup bundles.
Step 3: Rollup JavaScript API Script
Create a file named bundle-react.js:
import fs from "node:fs";
import path from "node:path";
import {rollup} from "rollup";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";

// bundles to generate
const bundles = [
    {
        input: "react.js",
        output: "dist/react.esm.js",
    },
    {
        input: "react-dom.js",
        output: "dist/react-dom.esm.js",
    },
];

// generate all bundles
const allBundlesPromises = bundles.map(bundle => {
    const config = {
        input: bundle.input,
        plugins: [
            resolve(),
            commonjs(),
        ],
    };
    return rollup(config)
        .then(result => {
            return result.write({
                file: bundle.output,
                format: "esm",
            });
        })
        .then(() => {
            console.log(`✅ Built ${bundle.output}`);
        });
});

Promise.all(allBundlesPromises).catch(error => {
    console.error(error);
});
Step 4: Run the Script
Add this to your package.json:
"scripts": {
    "build:esm": "node bundle-react.js"
}
And then run:
npm run build:esm
Step 5: Use with Import Maps in the Browser
Here is how you can use your ESM bundles in a browser-native environment:
<!DOCTYPE html>
<html lang="en">
<head>
    <script type="importmap">
        {
            "imports": {
                "react": "/dist/react.esm.js",
                "react-dom": "/dist/react-dom.esm.js"
            }
        }
    </script>
</head>
<body>
    <div id="root"></div>
    <script type="module">
        import React from "react";
        import ReactDOM from "react-dom";

        const App = () => React.createElement("h1", null, "Hello React ESM!");
        ReactDOM.createRoot(document.getElementById("root"))
            .render(React.createElement(App));
    </script>
</body>
</html>