Skip to content

Hot Module Replacement (HMR)

KickJS uses Vite's HMR to provide zero-downtime reloading during development. When you save a file, the Express handler is rebuilt and swapped on the existing HTTP server. Database pools, Redis connections, and port bindings survive across reloads.

How It Works

The kick dev command starts a Vite dev server using the native RunnableDevEnvironment API. Vite watches your source files and triggers module re-execution when changes are detected.

The bootstrap() Function

The bootstrap() function from @forinda/kickjs handles the entire HMR lifecycle:

ts
import { bootstrap } from '@forinda/kickjs'
import { modules } from './modules'

bootstrap({ modules })

On the first call, bootstrap() creates the application, registers error/shutdown handlers, and starts the HTTP server. On subsequent calls (triggered by HMR), it rebuilds the Express app and swaps the request handler on the existing server — no restart needed.

What Is Preserved

Preserved across HMRRebuilt on each reload
http.Server instanceExpress app
Port bindingMiddleware stack
TCP connectionsRoute table
Database connection poolsDI container singletons
Redis clientsController instances
Socket.IO serverService instances

The HTTP server is created once and never recreated. Only the request handler is swapped, so existing connections and listeners remain intact.

Configuring Vite

A minimal vite.config.ts for HMR support:

ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    target: 'node20',
    ssr: true,
    rollupOptions: {
      input: 'src/index.ts',
    },
  },
})

The kick dev command uses Vite's Environment Runner which reads this config automatically. No additional HMR configuration is needed.

Errors Surface on Save

After an invalidation (a token change or a module-file add/remove), the dev server eagerly re-evaluates virtual:kickjs/app instead of waiting for the next HTTP request. A broken save — syntax error, failed import, bootstrap throw — prints immediately:

text
[vite] [kickjs] app failed to reload after HMR invalidation (1 token): x Expected ',', got ':'

The next successful save heals it; the dev loop never dies mid-edit.

Custom HMR Events

Dev tools (the DevTools dashboard, Swagger UI, custom overlays) can subscribe to the channel KickJS broadcasts on:

EventPayloadFired when
kickjs:hmr{ tokens, timestamp }A batch of DI tokens was invalidated
kickjs:typegen-error{ message, timestamp }A watch-mode typegen pass failed (types may be stale)
kickjs:typecheck{ ok, output, durationMs }A kick dev --typecheck run finished (full diagnostics inside)
ts
import.meta.hot?.on('kickjs:typecheck', (data) => {
  if (!data.ok) overlay.show(data.output)
})

Graceful Shutdown

When the process receives SIGINT or SIGTERM, bootstrap() calls app.shutdown() which:

  1. Runs all adapter shutdown() methods concurrently via Promise.allSettled
  2. Closes the HTTP server
  3. Exits the process

Adapter shutdown failures are logged but do not prevent other adapters from cleaning up.

Troubleshooting

Raw JSON logs instead of colored output (Pino provider)

The default logger is console-based and needs no setup. This only applies if you've opted into the Pino logger via Logger.setProvider(): Pino loads pino-pretty in a worker thread, which Vite's SSR bundler can't resolve from the bundled output. Fix: add pino and pino-pretty to ssr.external in your vite.config.ts:

ts
export default defineConfig({
  ssr: {
    external: ['pino', 'pino-pretty'],
  },
  // ...
})

This tells Vite not to bundle these modules — Node.js resolves them at runtime, allowing the worker thread to find pino-pretty.

kick.config.ts changes not picked up

kick dev watches kick.config.ts and automatically restarts the Vite server when it changes. If the restart doesn't happen, ensure:

  • The file is named kick.config.ts (not .js or .mjs) — only .ts is watched
  • You're running kick dev, not npx vite directly

(client) warning: Module "node:*" externalized

This warning appears when Vite creates a client environment alongside the SSR environment. It's harmless for backend apps. KickJS's kick dev filters these warnings automatically. If you see them, rebuild the CLI: pnpm --filter @forinda/kickjs-cli build.

Released under the MIT License. Built with TypeScript — runs on Express, Fastify, or h3.