Write once, compile anywhere. Eliminate framework lock-in forever.
A production-ready component compiler that lets you write components once in a clear, Svelte 5-inspired DSL and compile them to any major framework: React, Vue, SolidJS, Svelte, or Web Components.
1. Write a component in .dce format:
<script>
let { initialCount = 0 } = $props();
let count = $state(initialCount);
let doubled = $derived(count * 2);
function increment() {
count++;
}
</script>
<template>
<div class="counter">
<button on:click={increment}>
Count: {count} | Doubled: {doubled}
</button>
</div>
</template>
<style>
.counter { padding: 2rem; }
button {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
</style>2. Compile to your target framework:
# Compile to React
npx dcc compile Counter.dce --target react -o Counter.jsx
# Compile to SolidJS
npx dcc compile Counter.dce --target solid -o Counter.jsx
# Compile to Svelte 5
npx dcc compile Counter.dce --target svelte -o Counter.svelte3. Use in your app:
// React
import { Counter } from './Counter';
import './Counter.css';
<Counter initialCount={5} /><!-- Svelte 5 -->
<script>
import Counter from './Counter.svelte';
</script>
<Counter initialCount={5} />Framework churn creates massive technical debt:
- Component libraries locked to aging frameworks
- Expensive, risky rewrites every few years
- Shrinking talent pool for legacy frameworks
- Cost of waiting compounds annually
Write once, compile to any framework. Your components become portable assets that can:
- β Target React today, Vue tomorrow, without rewrites
- β Generate both native framework code AND standard Web Components
- β Survive framework churn with zero migration cost
- β Integrate seamlessly with existing codebases
Built on Durable Programming Principles for long-term stability.
- React Compilation - Full support with Hooks (useState, useMemo, useEffect)
- SolidJS Compilation - Native Signals output (createSignal, createMemo, createEffect)
- Svelte 5 Compilation - Native Runes output (reverse transformation)
- Scoped CSS - Svelte/Vue-style scoped styles with attribute selectors
- Explicit Reactivity - Svelte 5 Runes ($state, $props, $derived, $effect)
- Vue 3 Compilation - Composition API output (
<script setup>, ref, computed) - Template Directives - {#if}, {#each}, on:event, bind:value
- Behavior Primitives - Focus trap, Escape, scroll lock, and auto-dismiss timers, portable across every target
- Component Flattening - Automatic recursive compilation of DCE component dependencies
- CLI Tool - Simple
dcccommand for compilation - Programmatic API -
compile()function for build tools - Vite Plugin - Seamless integration with Vite build tool
- TypeScript Support - Full type definitions included
- Comprehensive Test Coverage - Generated output is verified against each framework's real compiler
- Web Components (standards-based)
- UnoCSS integration
- Webpack plugin
- VS Code extension
- Quick Start Guide - Get up and running in 5 minutes
- Architecture Documentation - Deep dive into compiler internals
- Component Flattening Guide - Automatic dependency resolution
- Vite Plugin Guide - Vite integration setup
- Examples - Real-world component examples
.dce Source β Parser β D-AST β Transformer β JSON IR β Generator β React/Vue/etc.
- Parser - Converts
.dcefiles into a Durable AST (D-AST) - Transformer - Creates a framework-agnostic JSON Intermediate Representation (IR)
- Generator - Produces target-specific code from IR
The JSON IR is the "source of truth" - a canonical representation that maps 1:1 to all major frameworks' reactive primitives.
Based on Svelte 5 Runes for explicit, portable reactivity:
// Reactive state
let count = $state(0);
// Props with defaults
let { name = 'World' } = $props();
// Computed values
let doubled = $derived(count * 2);
// Side effects
$effect(() => {
console.log('Count changed:', count);
});
// Event handlers (plain functions)
function increment() {
count++;
}Interaction behaviors that every framework needs but none spells the same way.
Each compiles to a mount/unmount effect β useEffect on React,
onMount/onCleanup on Solid, onMounted/onUnmounted on Vue, $effect on
Svelte β with the teardown wired in, so nothing leaks when the component
unmounts.
| Primitive | Purpose |
|---|---|
<dce:focus-trap for={el} /> |
Confine Tab navigation to an element; restores focus on teardown |
<dce:escape on:escape={fn} /> |
Run a handler on the Escape key |
<dce:scroll-lock /> |
Prevent the page behind from scrolling; nested locks are reference-counted |
<dce:timer after={ms} on:elapsed={fn} /> |
Auto-dismiss timer; a non-positive delay disables it |
<script>
let { onClose } = $props();
let dialog;
</script>
<template>
<div class="overlay">
<dce:scroll-lock />
<dce:escape on:escape={onClose} />
<dce:timer after={5000} on:elapsed={onClose} />
<dce:focus-trap for={dialog} />
<div bind:this={dialog} role="dialog">
<slot />
</div>
</div>
</template>The helper implementations are emitted inline alongside the component, so generated output stays standalone with no runtime dependency. Only the helpers a component actually uses are included.
The focus trap honours [autofocus], skips hidden and disabled elements, and
cycles in both directions. The scroll lock compensates for scrollbar width so
the page does not shift when it engages.
A primitive that reads a loop binding belongs to one item, not to the component, so it compiles per item rather than to a single hoisted effect:
{#each toasts as toast}
<div class="toast">{toast.text}</div>
<dce:timer after={toast.ms} on:elapsed={() => dismiss(toast.id)} />
{/each}Each item gets its own timer, and that timer is torn down when the item leaves the list β not only when the whole component unmounts. React and Solid emit a small per-item component, Svelte an action, and Vue a custom directive.
A primitive that reads nothing from the loop, such as a bare <dce:scroll-lock />,
still compiles to one component-level effect, which is both correct and cheaper
than one per item.
bind:this={panel} declares panel and fills it with the element. Script code
uses the bare name, and each target's accessor is added on the way out β
panel.current on React, panel.value on Vue, the variable itself on Solid and
Svelte:
<script>
let panel;
function focusIt() { panel.focus(); }
</script>
<template>
<div bind:this={panel} tabindex="-1"></div>
</template>Two situations produce a warning on compile()'s warnings array rather than
questionable output:
| Code | Meaning |
|---|---|
REF_SHADOWS_STATE |
A name is both $state and a bind:this target. bind:this declares the reference itself, so the $state declaration is redundant and is dropped. |
REF_BOUND_IN_LOOP |
A bind:this inside an {#each}. One name cannot hold every element the loop renders, and the targets disagree on what survives, so move the element into a child component. |
Warnings do not fail a build. They reach you through whichever entry point you compile with:
dcc compileprints them to stderr, so piping the compiled output somewhere keeps that output clean.- The Vite plugin reports them through
this.warn, so they appear in the dev server output and the build log. - The showcase server prints each one once per component and again after you edit the file, rather than on every recompile.
compile()returns them onresult.warningsfor anything building on the API directly.
With includeReferences enabled, a warning from a component your entry point
imports is reported too, prefixed with the file it came from.
npm install @durable/compilerThe easiest way to use Durable Components in your project is with the Vite plugin:
npm install @durable/compiler// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { durableComponents } from '@durable/compiler/vite-plugin';
export default defineConfig({
plugins: [
react(), // or solid(), svelte(), etc.
durableComponents({
target: 'react', // 'react' | 'solid' | 'svelte' | 'vue'
style: 'scoped' // 'scoped' | 'inline' | 'unocss'
})
]
});Then just import .dce files directly in your code:
// App.tsx
import Counter from './Counter.dce';
function App() {
return <Counter initialCount={5} />;
}Features:
- β Hot Module Replacement (HMR)
- β Automatic CSS extraction and injection
- β TypeScript support
- β Dev mode with better error messages
See examples/vite-demo for complete examples with React, SolidJS, and Svelte.
# Compile to React
dcc compile Counter.dce --target react -o Counter.jsx
# Compile to SolidJS
dcc compile Counter.dce --target solid -o Counter.jsx
# Compile to Svelte 5
dcc compile Counter.dce --target svelte -o Counter.svelte
# Show available targets
dcc infoimport { compile } from '@durable/compiler';
import fs from 'fs';
const source = fs.readFileSync('Counter.dce', 'utf-8');
const result = compile(source, {
filename: 'Counter.dce',
target: 'react', // 'react' | 'vue' | 'solid' | 'svelte' | 'wc'
style: 'scoped' // 'scoped' | 'inline' | 'unocss'
});
fs.writeFileSync('Counter.jsx', result.js.code);
fs.writeFileSync('Counter.css', result.css?.code || '');# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch mode
npm run dev
# Compile examples
npm run build && node dist/cli/index.js compile examples/Counter.dce --target react| DSL Construct | React | Vue 3 | SolidJS | Svelte 5 |
|---|---|---|---|---|
$state(0) |
useState(0) |
ref(0) |
createSignal(0) |
$state(0) |
$derived(x*2) |
useMemo(()=>x*2,[x]) |
computed(()=>x*2) |
createMemo(()=>x*2) |
$derived(x*2) |
$effect(...) |
useEffect(...,[deps]) |
watchEffect(...) |
createEffect(...) |
$effect(...) |
$props() |
function(props) |
defineProps() |
function(props) |
$props() |
Built on Durable Programming Principles:
- Technical Flexibility - Pluggable architecture, never bound to one framework
- Clarity over Cleverness - Explicit reactivity, no magic
- Pragmatic Approach - Common subset of features that work everywhere
- Business-Centric - Reduces technical debt, increases component lifespan
Contributions welcome! To add a new generator:
- Create
src/generators/{target}.ts - Implement IR β Target transformation (see
generators/react.ts) - Add tests
- Update documentation
MIT
Built with β€οΈ by the Durable Programming community