codemirror/website

Update the advice on which bundler to use to `esbuild` instead of `rollup`.

Closed this issue · 3 comments

I've been using esbuild pretty much since it came out, and the speed is just unmatched. If I use rollup, as mentioned as the preferred method over on https://codemirror.net/examples/bundle/ then the rebuild takes 400+ milliseconds on a high end M2 Macbook Pro, and 833 milliseconds on a desktop workstation with Win10 pro running on a Ryzen 5950X (a "traditional" cpu that predates the M2 by 2 years as well as an entire "real" generational leap).

If I run the same bundling using esbuild, targeting ESM instead of IIFE (because it's 2024) then the bundling times are 688ms on the desktop and 183 milliseconds on the mac. On the desktop, that's 25% faster. On the M2, that's 60% faster. No matter how you benchmark this, rollup is no longer the right tool to recommend, and switching the advice to using esbuild instead makes a ton of sense. Because these differences matter a lot: if you're doing a lot of watch-rebuilding, those extra hundreds of milliseconds really add up to a sense of frustration with rollup that just isn't there (or is drastically less) with esbuild.

Rollup was a great at showing how poorly Webpack (and Browserify) performed, but esbuild showed how fast bundling can actually be.

For concrete comparison, I'm using the following scripts in my own project:

{
  ...
  "scripts": {
    "build:rollup": "rollup script.js -f iife -o prebaked/editor.bundle.js -p @rollup/plugin-node-resolve",
    "build:esbuild": "esbuild --log-level=info --bundle --format=esm script.js > prebaked/editor.bundle.js",
    "start": "node server.js"
  },
  ...
}

With my server.js doing the watching, rather than relying on --watch flags:

...
const toWatch =  [`./script.js`, ...];

app.listen(8000, () => {
  console.log(`http://127.0.0.1:8000`);
  rebuild();
  toWatch.forEach((filename) => watch(filename, () => rebuild()));
});

...

function rebuild() {
  const start = Date.now();
  spawnSync(isWindows ? `npm.cmd` : `npm`, [`run`, `build:esbuild`], {
    stdio: `inherit`,
  });
  console.log(`Build took ${Date.now() - start}ms`);
}

So as an added benefit, we also don't need the additional rollup dependency either, it just compiles to ESM and we load our bundle using a modern <script> tag:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script src="./editor.bundle.js" async defer type="module"></script>
    ...
  </head>
  <body>
    ...
  </body>
</html>

People are free to use esbuild, but Rollup is still my tool of choice and the one I have the most experience with, as well as the one that Lezer has a plugin for, so I am not changing this.

Your call, but I would strongly recommend you start giving esbuild a try, especially if you use rollup for everything, like I used to. Especially since these examples are for everyone's benefit, and esbuild is objectively the better tool to recommend to people these days.

(And I can guarantee you it'll be a near-dropin replacement for Lezer, too, and it'll be much faster.)