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-dev7.7 ms
  • Vite 8.3.477.5 ms

10× 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 a real app

private-app's web app (a Next.js app; the harness serves its React client components with Fast Refresh) served by ts-r-dev and by Vite 8.3.4 with @vitejs/plugin-react 6.1.2. Start is process spawn until the header, form and footer are in the page; cold means an empty ts-r cache and no node_modules/.vite. Both kept the form's state through all 53 edits with 0 full reloads.

Start to first render, medianShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-dev, warm214 ms
  • ts-r-dev, cold278 ms
  • Vite 8.3.4, warm414 ms
  • Vite 8.3.4, cold433 ms
Save to page updated, p50Shorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-dev7.7 ms
  • Vite 8.3.477.5 ms
Dev server memory after the editsShorter is faster, in MB
  • ts-r
  • Other tools
  • ts-r-dev26 MB
  • Vite 8.3.4202 MB
How we measured
Measured
Headless Chrome drives both servers: 5 cold and 5 warm starts (median), then 50 measured edits of sign-in-form.tsx after 3 warm-ups, timed in the browser. RSS is the whole server process group.
Machine
Apple M-series Mac, macOS arm64, release build (the exact chip was not recorded)
Software
ts-r-dev release build; Vite 8.3.4 with @vitejs/plugin-react 6.1.2; both servers capped at 4 threads
Input
A scratch copy of private-app's apps/web with its dependencies installed
Command
scripts/limit.sh --mem-mb 1024 --time-s 1800 -- node bench/private-app/dev.mjs --app /tmp/pp/apps/web --server both --mode bench
Recorded in
commit e71a460

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