Type-safe reactive Web Components — HTML-first, backend-agnostic
Le Truc adds a thin reactive layer to server-rendered HTML. You keep your existing backend (Java, PHP, Python, C#, static site generator — anything that outputs HTML). Le Truc wires fine-grained DOM updates to reactive component properties in the browser, without re-rendering whole subtrees and without requiring JavaScript on the server.
The result is SolidJS-style reactivity packaged as standard Custom Elements: components are reusable across projects, type-safe, and carry no framework lock-in.
Digital agencies building content-rich sites face a recurring choice: imperative JavaScript that becomes unmaintainable as complexity grows, or a SPA framework that takes over rendering and requires a JavaScript backend. Neither is a good fit when the backend is a CMS and the initial HTML is already correct.
Le Truc solves the specific problem of stateful interactivity on server-rendered pages:
- The server renders HTML — Le Truc never re-renders it
- Reactive properties update only the DOM nodes that actually changed
- Components are plain Custom Elements — they work in any host environment
- TypeScript catches integration errors at compile time
npm install @zeix/le-truc
# or
bun add @zeix/le-truc- Start with your server-rendered HTML:
<basic-hello>
<label for="name">Your name</label>
<input id="name" name="name" type="text" autocomplete="given-name" />
<p>Hello, <output for="name">World</output>!</p>
</basic-hello>- Define the component:
import { asString, defineComponent, on, setText } from '@zeix/le-truc'
defineComponent(
'basic-hello', // Component name (must contain a hyphen)
{ // Reactive property — fallback read from DOM
name: asString(ui => ui.output.textContent)
},
({ first }) => ({ // Select DOM elements
input: first('input', 'Needed to enter the name.'),
output: first('output', 'Needed to display the name.'),
}),
({ host, input }) => { // Wire behaviour
const fallback = host.name
return {
input: on('input', () => { host.name = input.value || fallback }),
output: setText('name'),
}
},
)- Import the module and watch it work.
defineComponent returns a Custom Element class registered via customElements.define(). Properties declared in step 2 become reactive: reading them inside an effect tracks the dependency, writing them triggers only the affected DOM updates.
Full documentation with live examples is at zeixcom.github.io/le-truc:
- 🧱 HTML-first — enhances server-rendered markup; no Virtual DOM, no hydration
- 🚦 Reactive properties — signals with automatic dependency tracking and fine-grained updates
- ⚡️ Pinpoint effects — only the exact DOM nodes that changed are touched
- 🧩 Composable — build behaviour from small, reusable parsers and effect functions
- 🌐 Context support — share state across components without prop drilling
- 🪶 Tiny — ≤10 kB gzipped, tree-shakeable
- 🛡️ Type-safe — full TypeScript inference from selector strings to property types
Le Truc uses Cause & Effect for its reactive primitives.
Contributions, bug reports, and suggestions are welcome — see CONTRIBUTING.md.