ts-r-dev

Dev server with hot reload. Replaces the Vite dev server.

  • Pre-alpha
  • Works from source

ts-r-dev serves your app as unbundled ES modules, transforms only the file you save, and pushes the update to the browser. It speaks Vite’s hot-reload protocol, so code written against import.meta.hot works unchanged. It replaces vite dev for the dev loop, not vite build.

Save to page updated, p50Shorter is faster. Pre-release build, real app.
  • ts-r-dev8.6 ms
  • Vite 8.3.435 ms

4.1× faster hot reload

What switching looks like

Nothing is on npm yet. Once it is, switching looks like this: ts-r-dev reads the config you already have, so the change is to the command, not the project.

package.json
  "scripts": {
-   "dev": "vite",
+   "dev": "ts-r-dev",
  }

What works, and what doesn’t yet

Works today

  • Hot module replacement with Vite’s semantics: self-accepting modules, hot.accept, hot.decline, hot.invalidate, hot.data, hot.dispose and hot.on
  • TypeScript, TSX and JSX through ts-r’s own transform; JSON, assets and CSS, with CSS updates applied without a reload
  • React Fast Refresh when a React Refresh runtime is installed; top-level components keep their state across edits
  • Dependencies pre-bundled in one code-split batch at startup, cached per lockfile
  • An error overlay for transform and resolve errors; the server never crashes on a bad file
  • pnpm workspace packages treated as source and hot-updated like app code

Not yet

  • No vite.config loading and no Vite plugin API
  • No --open, HTTPS, proxy, CORS headers or compression; HTTP/1.1 only
  • CSS @import and CSS modules aren’t handled
  • Inline module scripts in HTML and non-literal import(expr) aren’t rewritten
  • Windows is untested

Measured

Every run, on the same reference app as the home page, with the command and commit that recorded it.

ts-r-dev against Vite 8 on excalidraw

excalidraw (about 500 TypeScript modules served from source, 47 dependency entries) served by ts-r-dev and by Vite 8.3.4 with @vitejs/plugin-react 6.1.2. Start is process spawn until the canvas and welcome screen are in the page; cold means an empty ts-r cache and no Vite dependency cache. Both kept React state through all 53 edits with 0 full reloads. SCSS is replaced by an empty stylesheet for both servers (ts-r-dev does not compile it yet).

Start to first render, medianShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-dev, warm538 ms
  • ts-r-dev, cold939 ms
  • Vite 8.3.4, warm1,032 ms
  • Vite 8.3.4, cold1,107 ms
Save to page updated, p50Shorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-dev8.6 ms
  • Vite 8.3.435 ms
Dev server memory after the editsShorter is faster, in MB
  • ts-r
  • Other tools
  • ts-r-dev153 MB
  • Vite 8.3.4364 MB
How we measured
Measured
Headless Chrome (a fresh one per page load) drives both servers: 5 cold and 5 warm starts (median), then 50 measured edits of the welcome-screen heading after 3 warm-ups, timed in the browser. RSS is the whole server process group.
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-dev release build; Vite 8.3.4 with @vitejs/plugin-react 6.1.2 pinned in bench/package.json; both servers capped at 4 threads
Input
excalidraw at the commit pinned in bench/repos.toml, with the harness in bench/dev/excalidraw (index.html, main.tsx rendering excalidraw-app/App, vite.config.mjs with its source aliases)
Command
cargo xtask bench --fetch-repos --repos excalidraw && scripts/limit.sh --mem-mb 1024 --time-s 1800 -- node bench/dev/dev.mjs --app excalidraw --server both --mode bench
Recorded in
commit 9c3a371

ts-r-dev: hot reload latency as the app grows

Time from saving a file to the browser having re-imported the changed module and run its callbacks, on generated apps. The plan's target is under 30 ms at p50, staying flat as the project grows. Vite has not been measured on these generated apps yet.

Save to browser updated, p50, as the app growsLower is faster, in ms. Generated apps from 1,000 to 50,000 modules.
Target: under 30 ms
  • 4.9 ms at 1,000 modules
  • 4.8–5.0 ms at 10,000 modules
  • 5.4 ms at 50,000 modules
How we measured
Measured
Browser-side timing in headless Chrome: 47 measured edits per size after 3 warm-up edits. Ranges are across runs; the bar shows the upper end.
Machine
Apple M-series Mac, macOS arm64, release build (the exact chip was not recorded)
Software
ts-r-dev release build, headless Chrome
Input
Generated JavaScript apps of 1,000, 10,000 and 50,000 modules; one self-accepting module under a 100-wide import tree is edited. The first save of a file goes through FSEvents, later saves through the kqueue fast path.
Command
TS_R_LATENCY_SIZES=1000,10000,50000 cargo test --release -p ts_r_dev --test latency -- --ignored --nocapture
Recorded in
commit 9ed299f

Command line

An excerpt of ts-r-dev --help. You can also run it through the launcher as ts-r dev.

Next to Vite

Use ts-r-dev for the dev loop and keep Vite, or anything else, for production builds. It reads your index.html and package.json. The client runtime is served at both /@ts-r/client and /@vite/client and its messages match Vite’s, so framework code that listens to vite: events keeps working.

ts-r-dev --help
ts-r-dev: dev server with hot module replacement (replaces vite dev)

Usage: ts-r-dev [root] [options]

  --port <n>         Port to listen on (default 5173)
  --host <addr>      Address to bind (default 127.0.0.1)
  --no-cache         Keep transform results in memory only
  --force            Pre-bundle dependencies again
  --no-react-refresh Turn off React Fast Refresh