DEV Community

Alex Spinov
Alex Spinov

Posted on

Rollup Has a Free Plugin API That Powers Every Modern Bundler

Rollup is the bundler behind Vite, and its plugin API is the de facto standard. If you've written a Vite plugin, you've used Rollup's API.

The Rollup Plugin Interface

// rollup.config.js
export default {
  input: "src/index.js",
  output: [
    { file: "dist/bundle.cjs.js", format: "cjs" },
    { file: "dist/bundle.esm.js", format: "es" },
    { file: "dist/bundle.umd.js", format: "umd", name: "MyLib" },
  ],
  plugins: [myPlugin()],
};
Enter fullscreen mode Exit fullscreen mode

Writing a Plugin: Full Lifecycle

function jsonPlugin() {
  return {
    name: "json",

    // Resolve custom module IDs
    resolveId(source) {
      if (source === "virtual:config") return source;
      return null; // Defer to other plugins
    },

    // Load content for resolved IDs
    load(id) {
      if (id === "virtual:config") {
        return `export default ${JSON.stringify({ version: "1.0" })}`;
      }
      return null;
    },

    // Transform file contents
    transform(code, id) {
      if (!id.endsWith(".json")) return null;
      const parsed = JSON.parse(code);
      return {
        code: `export default ${JSON.stringify(parsed)};`,
        map: null,
      };
    },

    // Modify the final bundle
    generateBundle(options, bundle) {
      for (const [fileName, chunk] of Object.entries(bundle)) {
        if (chunk.type === "chunk") {
          console.log(`${fileName}: ${chunk.code.length} bytes`);
        }
      }
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

Code Splitting

export default {
  input: {
    main: "src/index.js",
    admin: "src/admin.js",
    worker: "src/worker.js",
  },
  output: {
    dir: "dist",
    format: "es",
    chunkFileNames: "chunks/[name]-[hash].js",
    manualChunks(id) {
      if (id.includes("node_modules")) {
        if (id.includes("react")) return "vendor-react";
        if (id.includes("lodash")) return "vendor-lodash";
        return "vendor";
      }
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Watch Mode API

import { watch } from "rollup";

const watcher = watch({
  input: "src/index.js",
  output: { file: "dist/bundle.js", format: "es" },
  plugins: [/* ... */],
});

watcher.on("event", (event) => {
  switch (event.code) {
    case "START": console.log("Build starting..."); break;
    case "BUNDLE_END":
      console.log(`Built in ${event.duration}ms`);
      event.result.close();
      break;
    case "ERROR": console.error(event.error); break;
  }
});
Enter fullscreen mode Exit fullscreen mode

Programmatic API

import { rollup } from "rollup";

async function build() {
  const bundle = await rollup({
    input: "src/index.js",
    plugins: [nodeResolve(), commonjs(), terser()],
  });

  // Generate multiple outputs
  await bundle.write({ file: "dist/index.cjs", format: "cjs" });
  await bundle.write({ file: "dist/index.mjs", format: "es" });
  await bundle.write({ file: "dist/index.umd.js", format: "umd", name: "MyLib" });

  await bundle.close();
}
Enter fullscreen mode Exit fullscreen mode

Tree Shaking: The OG

Rollup invented tree shaking for JavaScript:

// utils.js
export function used() { return "I stay"; }
export function unused() { return "I get removed"; }

// index.js
import { used } from "./utils";
console.log(used());
// unused() is completely removed from bundle
Enter fullscreen mode Exit fullscreen mode

Building reusable libraries? My Apify tools show how to build and distribute production packages.

Custom tooling? Email spinov001@gmail.com

Top comments (0)