Skip to content

[Bug]: watcher.close() does not cancel an in-progress build, but it does in rollup #8937

@dumbmatter

Description

@dumbmatter

Reproduction link or steps

In this repo https://github.com/dumbmatter/rolldown-watch-close run node rollup.js and node rolldown.js and observe the difference. Basically we start watching a simple JS file, but with a plugin that makes it artificially slow. Then we call watcher.close() before the initial build has completed and observe the result.

rollup.js:

import { setTimeout } from "node:timers/promises";
import { watch } from "rollup";

function delayPlugin(ms) {
  return {
    name: "delay-plugin",

    async buildStart() {
      console.log(`[delay-plugin] sleeping ${ms}ms`);
      await setTimeout(ms);
    },
  };
}

const watcher = watch({
  input: "index.js",
  output: {
    file: "bundle.js",
    format: "esm",
  },
  plugins: [delayPlugin(2000)],
});

watcher.on("event", (event) => {
  console.log(event.code);
});

watcher.on("close", () => {
  console.log("close event");
});

await setTimeout(1000);

console.log("call watcher.close");
await watcher.close();
console.log("watcher.close promise resolves");

rolldown.js:

import { setTimeout } from "node:timers/promises";
import { watch } from "rolldown";

function delayPlugin(ms) {
  return {
    name: "delay-plugin",

    async buildStart() {
      console.log(`[delay-plugin] sleeping ${ms}ms`);
      await setTimeout(ms);
    },
  };
}

const watcher = watch({
  input: "index.js",
  output: {
    file: "bundle.js",
    format: "esm",
  },
  plugins: [delayPlugin(2000)],
});

watcher.on("event", (event) => {
  console.log(event.code);
});

watcher.on("close", () => {
  console.log("close event");
});

await setTimeout(1000);

console.log("call watcher.close");
await watcher.close();
console.log("watcher.close promise resolves");

What is expected?

I would expect that for both Rolldown and Rollup, calling watcher.close() cancels and in-progress build and does not result in a bundled file being written to disk some time later.

What is actually happening?

With Rollup, calling watcher.close() immediately fires the "close" event and resolves its returned promise, and any in-progress build is stopped (no bundle.js is created).

$ node rollup.js
START
BUNDLE_START
[delay-plugin] sleeping 2000ms
call watcher.close
close event
watcher.close promise resolves

With Rolldown, calling watcher.close() waits until the current in-progress build is done and written to disk before firing the "close" event and resolving the returned promise (bundle.js is created, and BUNDLE_END and END events are emitted which does not happen in rollup).

$ node rolldown.js
START
BUNDLE_START
[delay-plugin] sleeping 2000ms
call watcher.close
BUNDLE_END
END
close event
watcher.close promise resolves

System Info

System:
    OS: Linux 6.8 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (8) x64 Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
    Memory: 10.89 GB / 31.13 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 24.14.0 - /usr/bin/node
    npm: 11.9.0 - /usr/bin/npm
    pnpm: 10.32.1 - /home/jdscheff/.local/share/pnpm/pnpm
  Browsers:
    Brave Browser: 146.1.88.130
    Chrome: 146.0.7680.80
    Firefox: 149.0
    Firefox Developer Edition: 149.0
  npmPackages:
    rolldown: 1.0.0-rc.11 => 1.0.0-rc.11 

(Also comparing it against rollup 4.6.0)

Any additional comments?

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions