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.
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.
"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).
- ts-r
- Other tools
- ts-r
- Other tools
- ts-r
- Other tools
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.
- 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: 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