# tsc-watch: a watch cycle that can act on every build result

> tsc-watch starts the TypeScript compiler you already have in watch mode and adds hooks that run a command when a compilation succeeds, fails, starts, completes, or emits a file. The value is real, the trade-offs are sharp, and the docs have one hole worth knowing about.

**gilamran/tsc-watch** — The TypeScript compiler with --watch and a new onSuccess argument

- Repository: https://github.com/gilamran/tsc-watch
- Stars: 719 · Forks: 63
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/gilamran-tsc-watch

## tsc-watch wraps the compiler you already installed

tsc-watch is a thin layer around the TypeScript compiler that lives in your project. Its stated purpose is narrow: it starts the installed compiler, tsc, with the watch parameter and adds the ability to react to compilation status. The project compares itself to nodemon, and the intended workflow is the same one nodemon serves for JavaScript, restarting a Node server as source files change.

What that framing hides is how little the tool replaces. The manifest lists cross-spawn, node-cleanup, ps-tree and string-argv as runtime dependencies, and typescript as a peer dependency with a wildcard range. There is no bundled compiler to keep in sync, and no alternative type checker. The watch behaviour, the error output and the emit rules all come from the tsc build sitting in your node_modules, so a change in your TypeScript version changes what the watcher reports.

The package ships a binary named tsc-watch that points at dist/lib/tsc-watch.js, plus a client entry point, a type declaration file and a small example directory. The binary is the whole product for most users. The client exists for the smaller set of teams that want to subscribe to compilation events from JavaScript instead of passing shell commands.

## Six hooks decide what runs after each compilation

The argument table is the real documentation of this project, and it is worth reading as a state machine rather than a list. There are six command hooks. onSuccess runs on every successful compilation. onFirstSuccess runs only on the first one. onFailure runs on every failed compilation. onCompilationStarted runs at the start of every compilation, both the initial run and later incremental ones. onCompilationComplete runs on every compilation regardless of outcome. onEmit runs a debounced command on every emitted file, ignoring unchanged files and disregarding whether the compilation succeeded.

The remaining flags shape output rather than behaviour. Colors are added by default, green for success and red for failure, and noColors turns that off. In watch mode the screen is cleared before results are reported, and noClear prevents it. silent stops any message on stdout. signalEmittedFiles runs the compiler with the list-emitted-files option but hides the file lines, which is how the file_emitted event gets enabled without flooding the terminal. maxNodeMem calls node with a specific memory limit for projects that need it. The compiler path option is marked deprecated, replaced by compileCommand.

The distinction that matters most is between onSuccess and onEmit. One of them follows the build result, the other follows the files that came out of it.

## Installing tsc-watch and running a first watch

Installation is an npm install, and the project documents two variants depending on whether you want the command available in your shell or wired into a package script.

```sh
npm install tsc-watch --save-dev
```

For command line use, the compiler itself is installed globally alongside it.

```sh
npm install -g typescript tsc-watch
```

With that in place, the smallest useful session watches a project and starts the built server once the first clean compile finishes.

```sh
tsc-watch --onSuccess "node ./dist/server.js"
```

A single file works too, and the output directory is passed through to the compiler.

```sh
tsc-watch server.ts --outDir ./dist --onSuccess "node ./dist/server.js"
```

The failure path is handled with the same shape, which is useful when you want a sound rather than a log line.

```sh
tsc-watch --onFailure "echo Beep! Compilation Failed"
```

The expected behaviour is that the server process starts only after a successful compilation, and that the watcher keeps running while the server child process is alive.

## onEmit runs even when the build breaks

The onEmit hook has different rules from every other command hook, and those rules are stated in the project's own notes. The command is debounced, so it does not fire once per emitted file, which prevents a large build from spawning a process for every single output. It ignores unchanged files, so touching a source file that produces identical output does not trigger it.

The other three notes are where this hook becomes its own thing. It will not run if the compilation succeeded but no file changed, unless that compilation was the first success. It will run even if the compilation failed, provided that changed files were still emitted. And unlike onSuccess, it is not tied to the build result at all.

That is a genuinely useful property for some setups, because a failing build can still leave usable JavaScript on disk, for instance when a type error appears in a file that does not affect the entry point. It is also a trap for anyone who assumes a command attached to a build only runs when the build works. A test command hung off onEmit will happily execute against half compiled output.

The debounce delay is configurable, with a default of 300 milliseconds, through the onEmitDebounceMs option. The right value depends on how long your command takes to notice that the output changed.

## Driving the watcher from Node with the client

The package exposes a client that mirrors the command hooks as an event emitter, so a JavaScript process can react to compilation results without shelling out. It is imported from the tsc-watch/client path, and the type definition file is published alongside it. The example in the repository creates the client, subscribes to events, and then calls start on it.

```javascript
const watch = new TscWatchClient();
watch.on('started', () => {
  console.log('Compilation started');
});
watch.start('--project', '.');
```

The documented event names are started for the initial and incremental compile starts, first_success for the first clean compile, subsequent_success for every later clean compile, compile_errors for each failing compile, and file_emitted for each transpiled file when the list-emitted-files option is in use. The client is killed with a kill call, and the example wraps that in a catch block for fatal errors.

Here is the documentation problem to check before you rely on the client. The example code subscribes to a success event, and that name does not appear in the list of emitted events the same page gives. The list names first_success and subsequent_success instead. One of the two is out of date, and nothing in the project files says which. If your process depends on a plain success event, verify the behaviour in your own checkout before shipping it, and do not assume the example and the event list agree.

## Pointing the watcher at tsgo instead of tsc

The compileCommand option is the escape hatch that matters most for anyone on a newer toolchain. It replaces both the deprecated compiler path option and the memory limit option, and it takes any command you can run from a shell.

The project notes that tsc-watch supports tsgo, the native compilation path, and gives two forms for it: `tsc-watch --compileCommand tsgo` and `tsc-watch --compileCommand "yarn tsgo"`.

The tradeoff is that the watcher was built around the text output of tsc. The signal that a compilation succeeded is read from that output, and signalEmittedFiles works by asking the compiler to list emitted files and then hiding the lines. A native compiler that formats its output differently may not produce a result the watcher can interpret, and the project does not document a fallback output format. The type checking and the error text also change when you replace the compiler, so a build that looked clean under tsc may not look the same under tsgo.

For a package script, the watcher fits the same slot as any other dev dependency.

```json
"scripts": {
  "dev": "tsc-watch --compileCommand tsgo"
}
```

## Where tsc-watch is the wrong tool

The limits are stated plainly in the project notes, and several of them are not what a newcomer expects. A command is a single command, not a multi command. A value like script1.sh and script2.sh joined by a shell operator is not supported, so any hook that needs two steps has to be moved into a script file first. Every child process is killed on exit using a termination signal, and any running child is terminated before a new one is created, which means a long running command will be restarted rather than left in place.

There is a naming trap as well. The project calls itself the nodemon for TypeScript, but nodemon watches files and restarts a process, while tsc-watch watches the compiler. If your project is plain JavaScript, or if your server reads files that the compiler never touches, this watcher has no reason to restart anything. It is also tied to the compiler's build graph, so a project that bundles with a different pipeline, or that resolves modules outside the compiler's view, will not get restarts from the events this tool watches.

The memory option and the no watch option are the two escape valves. Passing the watch argument as false also stops the watch, and the screen clearing and colour flags exist because the default output is meant for a terminal, not for a log file that a build server keeps.

## MIT terms and a version line ahead of the tags

The project is released under the MIT licence, so the code carries the usual short permission and warranty disclaimer, and the manifest names a single author. Anyone embedding the client in an internal tool can read the published terms, and the licence does not impose copyleft obligations on a product that links the package. This is a summary of the terms and not legal advice.

On upkeep, the repository is not archived and the last push was on 2026-10-17, so the project is still receiving commits. The version trail is less tidy. The package manifest declares version 7.2.1, and the release notes that come with the project name v6.2.0 dated 2024-03-28 for a prepublish change, v6.1.0 dated 2024-03-27 for the noEmit option, and v6.0.5 dated 2024-03-27 for a fix to error TS6369. The manifest requires node 12.12.0 or newer and builds against TypeScript 5.8.3, with jest and ts-jest covering the test run.

Nothing in the files states a release schedule, so a new adopter has no way to tell from the version numbers alone how quickly patches land. The type checking behaviour, the error text and the emit rules all come from whichever TypeScript version the project pins, so upgrading that dependency is the upgrade step that matters here, not the watcher version.

## Conclusion

tsc-watch fits TypeScript projects that need a server restarted after a clean build, especially where the compiler is already the source of truth for the build. It does not fit plain JavaScript projects, pipelines that need multi step commands, or teams that want the compiler replaced outright. Before adopting it, check the event name the client example uses against the list of emitted events, confirm your own command is a single command rather than a shell chain, and verify the TypeScript version in the project works with the watch mode you already rely on.

## FAQ

### How do I restart a Node server after each successful TypeScript compile with tsc-watch?

Pass the server command to the onSuccess argument, for example tsc-watch --onSuccess "node ./dist/server.js". The command runs on every successful compilation, and it does not run when the compilation failed.

### What is the difference between onSuccess and onEmit in tsc-watch?

onSuccess runs on every successful compilation, while onEmit runs a debounced command on every emitted file and disregards whether the compilation succeeded or failed. The onEmit command is debounced by default at 300 milliseconds, configurable with onEmitDebounceMs.

### Can tsc-watch use the native tsgo compiler instead of tsc?

Yes. The project states that tsc-watch supports tsgo through the compileCommand option, with the forms tsc-watch --compileCommand tsgo and tsc-watch --compileCommand "yarn tsgo". The compileCommand option replaces the deprecated compiler path option and the maxNodeMem option.

## Sources

- [gilamran/tsc-watch on GitHub](https://github.com/gilamran/tsc-watch)
- [Issues](https://github.com/gilamran/tsc-watch/issues)
- [License: MIT](https://github.com/gilamran/tsc-watch/blob/master/LICENSE)
- [README](https://github.com/gilamran/tsc-watch/blob/master/README.md)
- [Releases](https://github.com/gilamran/tsc-watch/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gilamran-tsc-watch
