Files

7.1 KiB

description, kind
description kind
Development-only hot reload for browser client plugins: rebuilding a plugin bundle swaps the running plugin in place, for developers iterating on the web GUI. package-reference

@deepseek-ai/dsh-client-hmr

English | 中文

Summary

dsh-client-hmr reloads a browser client plugin in place when its bundle is rebuilt, so a developer editing plugin source sees the change without a full page reload. The reload chain stays idle without a rebuild watcher: only a pnpm run dev:web-style process rewriting client bundles produces the rebuilds it reacts to. Each reload swaps one plugin with fresh component state while the data layer (connection, runtime, and Session objects) stays untouched. Everything here is development machinery in the browser; the model never sees it.

Table of Contents


Use this package

Enable the rebuild watcher for the plugin you are editing, then save: the browser picks up the rebuilt bundle from the dev server and swaps the plugin without reloading the page. Use it during client development; nothing observable happens in a production build, where no watcher rewrites bundles.

Starting the reload chain

Run pnpm run dev:web (or any tsdown watch process that writes the plugin's lib/client.js) against the same host; rebuilt plugins are then swapped into the running browser automatically, one at a time.

What a reload does

Each reload re-executes the plugin bundle and remounts the plugin with fresh state. Plugins that depend on the reloaded one reload with it automatically. A reload that fails is reported visibly and retried from scratch on the next rebuild.

Configuration

Field Default Meaning
pollIntervalMs 500 Bundle stat-poll interval in milliseconds

The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.

Observing success

A successful swap shows the edited UI immediately with no page reload, and the plugin keeps working after the swap. Remember the trade-off: React state inside the reloaded plugin is lost, while session, workspace, and connection state survives.


Understand the implementation

Implementation internals — click to expand

This section explains how the reload chain is built; observable behavior is covered in Use this package.

Design concept

The chain is two halves with one contract: the node half owns bundle detection and notification, the browser half owns the swap. The node half runs one interval that stat-polls each graph bundle from the module host's pre-read baseline. An unchanged startup row starts watching without a content read or hash; a changed row, or a dirty row whose artifact reappears, enters rebuilt(), and only real revision changes are broadcast. rebuilt() reads the current source map together with the changed bundle; a map-only write does not reload executable code. The node half also serves /plugins/events, an SSE channel broadcasting graph and rebuilt frames.

The browser swap

On a rebuilt frame the revision makes invalidate select that plugin's immutable one-resource combo URL instead of its initial multi-resource URL. prefetch loads and registers the new factory while the old fiber still serves. The remaining order is registry-first teardown (registry.delete before the fiber's disposer emits internal/plugin, or the vendored Loader flags the entry disabled), drain the old fiber's unload, delete entry.fiber, remove owned <style data-plugin> tags, then entry.refresh() re-imports and remounts, and fiber.await() rethrows startup failures loud. The swap is safe because execution is pure registration under the lazy-CJS model: every module side effect lives in the factory closure and runs at materialization.

Cascade and self-reload

A fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-cascades every dependent through cordis itself with zero HMR-side bookkeeping. This plugin is itself a graph entry, so a rebuilt frame may name it; the in-flight reload keeps running in the old bundle's closure and the new bundle's apply opens a fresh channel.

Failure policy

No rollback: an import failure leaves the entry fiberless (the next rebuilt frame retries from scratch), and an apply failure leaves a FAILED fiber visible in the shell's status projection. Both log loudly.

Source map

File Role
src/index.ts Node half: bundle stat-poll, rebuilt reporting, /plugins/events SSE channel
src/client/index.ts Browser half: SSE subscription, serialized reload queue, fiber swap
src/events.ts Shared frame types (graph / rebuilt) and the endpoint constant

Further Exploration

Read these when the reload contract is not enough: the module system that serves the bundles, the shell that boots them, and the module-graph rules behind the externals.


Model Experience

None, as the reload driver is a browser-side UI plugin layer that registers nothing model-facing.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

These limits define what the reload driver does not preserve or restore. They are current package constraints, not a task backlog.

  • Reload is coarse by design — a fresh fiber and fresh components; React state inside the reloaded plugin is lost while the data layer (connection/runtime fibers, Session objects) is untouched. react-refresh-grade state preservation conflicts with re-executing the bundle and is deliberately out.
  • No failure rollback — a reload that fails leaves the entry FAILED and visible in the loader status projection; the previous bundle is not restored automatically.
  • Rebuilt frames do not replace the boot graph — each frame carries the plugin-artifact revision needed for its one-resource combo reload; a page reload receives the recomposed startup graph.

Dev Note

Working context for maintainers — click to expand

None.