Skip to content

About

True HMR for Lit components, plus CSS helpers and a DevTools panel

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1,651 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

@oddsquad/vite-plugin-lit

CI npm JSR JSR score Chrome Web Store Firefox Add-ons Mentioned in Awesome Lit Open in StackBlitz

A Vite plugin for Lit projects with true HMR, CSS delivery helpers for shadow roots, and a DevTools timeline.

video.mp4

Documentation →

Started life in a fork of the lit monorepo as a proposed @lit-labs/vite-hmr package, and now lives here on its own. Lit is kept checked out as a read-only submodule for reference and opt-in canary testing against lit main.

Try it

Each demo opens on StackBlitz and runs the latest release of the plugin in your browser. The Demos page embeds them, so you can try one without leaving the docs.

Demo What to try
Playground HMR and the Vite DevTools dock: click the counter, edit src/hmr-counter.ts, and the count stays. Open in StackBlitz
Standalone mode The same page as static files, without Vite, next to the DevTools panel on its own port. Open in StackBlitz
Lit flame One component: turn its colours, stoke it and blow it out, then edit it and watch its state, #private field and colours survive. Open in StackBlitz
Web Awesome A component built from a third-party Lit library: edit it, and the library's elements keep their checked boxes and half-typed text. Open in StackBlitz
Fernhouse A small shop to explore the DevTools on: pick a card, open its slots and parts in Anatomy, then record adding it to the cart. Open in StackBlitz
CSS One card per way of styling a shadow root, with UnoCSS as one shared sheet: change --brand and every card recolours without re-rendering. Open in StackBlitz
Tailwind Tailwind v4 utilities as one shared sheet: add a class to a component and the sheet swaps in place, with shadows and gradients working in shadow roots. Open in StackBlitz
Forms Form-associated custom elements from one base class, in a plain <form>: submit it empty, reset it, and watch each control's :state() change. Open in StackBlitz
Lit SSR Components rendered on the server with @lit-labs/ssr, then hydrated: click the counter, edit src/ssr-counter.ts, and the count stays. Open in StackBlitz

Why

lit-html decides "is this the same template?" by object identity of the TemplateStringsArray, not by content. Vite HMR re-executes an edited module, so a one-character edit rebuilds the component's entire subtree and takes focus, scroll, input state, and child @state with it.

This plugin fixes that with two cooperating mechanisms:

  1. Template-strings interning — the html/svg/mathml/css tags are wrapped in dev so strings arrays are canonicalized by content, and only the edited template rebuilds its part of the DOM.
  2. In-place class patching — the duplicate customElements.define from the re-executed module patches the originally-registered class in place, then restores reactive property values through the new accessors.

How it works →

Usage

// vite.config.ts
import {defineConfig} from 'vite';
import {litPlugin} from '@oddsquad/vite-plugin-lit';

export default defineConfig({
  plugins: [litPlugin()],
});

The HMR feature only applies to the dev server (apply: 'serve'); production builds are untouched. The CSS queries (?hmr-url, ?css-sheet) and Lightning CSS literal processing apply in both dev and build.

The dev-only code (HMR, the source overlay, DevTools) is imported only when the dev server (or vite preview) starts, so vite build never loads it. For a build config that depends on even less, use the build-only entry, which carries just the CSS plugins:

import {litCss} from '@oddsquad/vite-plugin-lit/build';

export default defineConfig(({command}) => ({
  plugins: [command === 'serve' ? litPlugin() : litCss()],
}));

Features

  • HMR that keeps state — what survives an edit, and what falls back to a reload.
  • Stylesheets at scale — one shared CSSStyleSheet for thousands of shadow roots, hot-swapped without re-rendering, from a bare ?css-sheet import.
  • DevTools timeline — a layered event recorder and live component inspector inside Vite DevTools.
  • Source overlay — click any element in the page to open its source in your editor.
  • Agent access — the same component tree and timeline as MCP tools, for coding agents.
  • Ecosystem support — signals, context, tasks, and the virtualizer across a patch.

Reference: options · environment variables · import queries · runtime API · CLI · limitations · compatibility · benchmarks · troubleshooting

Coding agents (MCP)

The DevTools panel's data is also exposed as MCP tools — lit_list-components, lit_component-details, lit_component-docs, lit_update-summary, lit_recent-events, lit_range-summary, lit_get-meta, lit_hmr-history, lit_hmr-incompatibilities, lit_set-recording, lit_set-prop — so an agent can read the live component tree and timeline instead of guessing from source, and try a property value on a live element.

These answer only while a Vite dev server with timeline on is running. There is no stored data and nothing to go stale: with the server down an agent gets a connection error, not an empty answer.

Point an MCP client at it either way:

# Auto-discovers every running dev server on this machine.
claude mcp add lit-devtools -- npx -y @oddsquad/vite-plugin-lit mcp

# Or dial one server directly, if you'd rather pin the port
# (`/__lit/__mcp` when the server runs without Vite DevTools).
claude mcp add lit-devtools -- npx -y mcp-remote http://localhost:5179/__devtools/__mcp

The equivalent claude_desktop_config.json / mcp.json entry:

{
  "mcpServers": {
    "lit-devtools": {
      "command": "npx",
      "args": ["-y", "@oddsquad/vite-plugin-lit", "mcp"]
    }
  }
}

The two differ in how the server is found, not in what it can do:

  • ... vite-plugin-lit mcp runs devframe's connector over stdio. The plugin publishes each running dev server to devframe's instance registry, and the connector lists them through two gateway tools (devframe_connect_list-instances, devframe_connect_call-tool) — so one entry covers every project you have running, with no port to keep in sync. Being a shared connector, it surfaces every devframe on the machine, not only this plugin's tools.
  • mcp-remote <url> bridges stdio to one fixed URL and depends on no discovery at all. Reach for it if registry discovery is unavailable — the plugin logs a warning saying so at startup, naming the reason.

The CLI also has lit-devtools dev, a standalone devframe server. Started on its own it shows an empty panel, because it has no page of its own. Add the script tag it prints at startup to any page, including one that Vite does not serve and one on another origin, and the page feeds it the live component tree, inspector and timeline:

<script src="http://localhost:5180/lit-devtools.js"></script>

Put it before the scripts that define your components. The server asks for its one-time code unless you start it with --no-auth: type the code into the prompt the page shows, open the page once with #devframe_otp=<code> on its URL, or skip the gate on a loopback host with --no-auth. Pages on a loopback origin (any port) may connect; for any other origin, pass it to the server with --allow-origin https://myapp.test:8443 (repeatable). A * in the host matches any subdomain, e.g. --allow-origin 'https://*.webcontainer-api.io' for StackBlitz, where every port gets its own origin.

Outside Vite the page has no build-time transforms, so HMR patching and open-in-editor do not work there; the tree, inspector and timeline do. Source locations come from the page's sourcemaps when it ships them. A page that Vite serves can instead call connectToDevServer() from @oddsquad/vite-plugin-lit/connect.js.

Production builds. The script tag also attaches to a minified build with Lit's production condition, checked by src/test/e2e/standalone-production_test.ts:

  • Works: the component tree, the inspector (properties, controllers and @lit/task instances, even though Lit mangles its private controller set) and Record with the lifecycle layer (performUpdate, willUpdate, update, updated).
  • Empty: the render layers, because Lit's production build never dispatches lit-debug events.
  • Missing: open-in-editor and HMR (no build-time transform). Source locations come from the build's sourcemaps, when it ships them.
  • Blocked: a page whose Content Security Policy does not allow the dev server's origin in script-src (for example script-src 'self') refuses the tag, so nothing attaches.
  • Cosmetic: names the minifier renamed show as it left them, so a plain controller's type reads as a short mangled identifier.

To try it from this repo, run pnpm run standalone:demo; see Playground.

Playground

A manually inspectable fixture app (also the source for the e2e fixtures), and self-contained enough to open on StackBlitz straight from the repo URL. Run it locally from the repo root:

pnpm dev   # builds the plugin, then serves http://localhost:5179

The same playground runs in standalone mode, built to static files and fed to a lit-devtools dev panel on another port: pnpm run standalone:demo here, or on StackBlitz with ?startScript=standalone. StackBlitz installs the published plugin, so both links run the latest release of the plugin against the playground on main.

Development

Requires Node 26 and pnpm 12 (corepack enable picks up the pinned version). The published package itself runs on Node 20 or newer.

pnpm install         # workspace: root package + playground + docs
pnpm build           # tsc + panel assets -> ./lib, ./panel, ./index.js
pnpm exec vp check   # format, lint, type check
pnpm test            # unit + e2e
pnpm docs:dev        # the documentation site

More in Development and Testing. Issues and pull requests are welcome; see CONTRIBUTING.md.

License

BSD-3-Clause. See LICENSE.

The DevTools bundle Web Awesome components (MIT) in the panel and Phosphor icons (MIT) in the panel and the in-page overlay.

About

True HMR for Lit components, plus CSS helpers and a DevTools panel

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages