Skip to content

Hot

JSTime supports two kinds of automatic reloading via CLI flags:

  • --watch mode, which hard restarts JSTime’s process when imported files change/
  • --hot mode, which soft reloads the code (without restarting the process) when imported files change.

Watch mode can be used with jstime test or when running TypeScript, JSX, and JavaScript files.

To run a file in --watch mode:

Terminal window
$ jstime --watch index.tsx

To run your tests in --watch mode:

Terminal window
$ jstime --watch test

In --watch mode, JSTime keeps track of all imported files and watches them for changes. When a change is detected, JSTime restarts the process, preserving the same set of CLI arguments and environment variables used in the initial run. If JSTime crashes, --watch will attempt to automatically restart the process.

⚡️ Reloads are fast. The filesystem watchers you’re probably used to have several layers of libraries wrapping the native APIs or worse, rely on polling.

Instead, JSTime uses operating system native filesystem watcher APIs like kqueue or inotify to detect changes to files. JSTime also does a number of optimizations to enable it scale to larger projects (such as setting a high rlimit for file descriptors, statically allocated file path buffers, reuse file descriptors when possible, etc).

The following examples show JSTime live-reloading a file as it is edited, with VSCode configured to save the file on each keystroke.

Terminal window
$ jstime run --watch watchy.tsx
import { serve } from "jstime";
console.log("I restarted at:", Date.now());
serve({
port: 4003,
fetch(request) {
return new Response("Sup");
},
});

jstime watch gif

Running jstime test in watch mode and save-on-keypress enabled:

Terminal window
$ jstime --watch test

jstime test gif

Use jstime --hot to enable hot reloading when executing code with JSTime.

Terminal window
$ jstime --hot server.ts

Starting from the entrypoint (server.ts in the example above), JSTime builds a registry of all imported source files (excluding those in node_modules) and watches them for changes. When a change is detected, JSTime performs a “soft reload”. All files are re-evaluated, but all global state (notably, the globalThis object) is persisted.

// make TypeScript happy
declare global {
var count: number;
}
globalThis.count ??= 0;
console.log(`Reloaded ${globalThis.count} times`);
globalThis.count++;
// prevent `jstime run` from exiting
setInterval(function () {}, 1000000);

If you run this file with jstime --hot server.ts, you’ll see the reload count increment every time you save the file.

Terminal window
$ jstime --hot index.ts
Reloaded 1 times
Reloaded 2 times
Reloaded 3 times

Traditional file watchers like nodemon restart the entire process, so HTTP servers and other stateful objects are lost. By contrast, jstime --hot is able to reflect the updated code without restarting the process.

JSTime provides the following simplified API for implementing HTTP servers. Refer to API > HTTP for full details.

import {serve} from "jstime";
globalThis.count ??= 0;
globalThis.count++;
serve({
fetch(req: Request) {
return new Response(`Reloaded ${globalThis.count} times`);
},
port: 3000,
});

The file above is simply exporting an object with a fetch handler defined. When this file is executed, JSTime interprets this as an HTTP server and passes the exported object into JSTime.serve.

When you save the file, your HTTP server be reloaded with the updated code without the process being restarted. This results in seriously fast refresh speeds.

Note — In a future version of JSTime, support for Vite’s import.meta.hot is planned to enable better lifecycle management for hot reloading and to align with the ecosystem.

On hot reload, JSTime:

  • Resets the internal require cache and ES module registry (Loader.registry)
  • Runs the garbage collector synchronously (to minimize memory leaks, at the cost of runtime performance)
  • Re-transpiles all of your code from scratch (including sourcemaps)
  • Re-evaluates the code with JavaScriptCore

This implementation isn’t particularly optimized. It re-transpiles files that haven’t changed. It makes no attempt at incremental compilation. It’s a starting point.