Understanding how Duct differs from popular frameworks helps you make informed architectural decisions. Here's an honest comparison of approaches and trade-offs.
Framework Comparison Table
Feature | Duct | React | Vue | Svelte | Web Components |
---|---|---|---|---|---|
Architecture | Blueprint pattern with separation | Component with mixed concerns | SFC with sections | Compiled components | Custom elements with class |
Virtual DOM | ❌ No | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
State Management | External/Manual | useState/useReducer | Reactive data | Reactive stores | Manual/Properties |
Learning Curve | Low | Medium-High | Medium | Low-Medium | Medium |
Template Syntax | JSX | JSX | Template/JSX | Enhanced HTML | HTML/Template literals |
Logic Location | Separate bind function | Mixed in component | Script section | Script section | Class methods |
Reactivity | Event-driven | Re-render based | Proxy-based | Compile-time | Manual/Observers |
Detailed Comparisons
Notice
Pay attention to how Duct code is direct, explicit, and easily understandable. What you see is what executes - no hidden abstractions, magic behaviors, or complex state management. This makes debugging straightforward and code behavior predictable.
Duct vs React
React popularized component-based UI development, but Duct takes a fundamentally different approach to organizing component logic.
React Component
function Button({ label, onClick }) {
const [clicked, setClicked] = useState(false);
return (
<button
className={clicked ? 'btn-clicked' : 'btn'}
onClick={() => {
setClicked(true);
onClick();
}}
>
{label}
</button>
);
}
Duct Component
// Render function
function render(props) {
return (
<button class="btn" data-clicked="false">
{props.label}
</button>
);
}
// Bind function
function bind(el, eventEmitter) {
let clicked = false;
const handleClick = () => {
clicked = true;
el.dataset.clicked = 'true';
el.className = 'btn-clicked';
eventEmitter.emit('click');
};
el.addEventListener('click', handleClick);
return { release: () => el.removeEventListener('click', handleClick) };
}
Duct Advantages
- ✓ No re-rendering overhead
- ✓ Direct DOM control
- ✓ Clear separation of concerns
- ✓ Explicit behavior
- ✓ No hooks complexity
React Advantages
- • Large ecosystem
- • Extensive tooling
- • Huge community
- • More job opportunities
- • Mature libraries
Duct vs Vue
Vue offers a middle ground between React and traditional templating, while Duct goes further in separating concerns.
Vue Component
<template>
<button
:class="buttonClass"
@click="handleClick"
>
{{ label }}
</button>
</template>
<script>
export default {
props: ['label'],
data() {
return { clicked: false };
},
computed: {
buttonClass() {
return this.clicked ? 'btn-clicked' : 'btn';
}
},
methods: {
handleClick() {
this.clicked = true;
this.$emit('click');
}
}
};
</script>
Duct Component
// Template is pure presentation
function render(props) {
return (
<button class="btn">
{props.label}
</button>
);
}
// Logic is completely separate
function bind(el, eventEmitter) {
let clicked = false;
function handleClick() {
clicked = true;
el.className = 'btn-clicked';
eventEmitter.emit('click');
}
el.addEventListener('click', handleClick);
return {
release: () => el.removeEventListener('click', handleClick)
};
}
Duct Advantages
- ✓ No reactivity magic to debug
- ✓ Explicit DOM control
- ✓ Clear separation of template/logic
- ✓ Standard JavaScript patterns
- ✓ Predictable performance
Vue Advantages
- • Gentle learning curve
- • Progressive enhancement
- • Excellent documentation
- • Built-in state management
- • Single-file components
Duct vs Svelte
Both Duct and Svelte avoid virtual DOM, but they differ significantly in their compilation and component organization approaches.
Svelte Component
<script>
export let label;
let clicked = false;
function handleClick() {
clicked = true;
dispatch('click');
}
</script>
<button
class={clicked ? 'btn-clicked' : 'btn'}
on:click={handleClick}
>
{label}
</button>
Duct Component
// Functions are completely separated
function render(props) {
return <button class="btn">{props.label}</button>;
}
function bind(el, eventEmitter) {
let clicked = false;
const handleClick = () => {
clicked = true;
el.className = 'btn-clicked';
eventEmitter.emit('click');
};
el.addEventListener('click', handleClick);
return { release: () => el.removeEventListener('click', handleClick) };
}
// Explicit blueprint creation
const Button = createBlueprint(
{ id: "my/button" },
render,
{ bind }
);
export default Button;
Duct Advantages
- ✓ No compile step required
- ✓ Runtime transparency
- ✓ Standard build tools
- ✓ Explicit behavior
- ✓ Easy debugging
Svelte Advantages
- • Tiny bundle sizes
- • Compile-time optimizations
- • Built-in animations
- • Simple reactive syntax
- • No runtime overhead
Duct vs Web Components
Both Duct and Web Components embrace direct DOM manipulation and standards-based approaches, but they differ in their component organization and abstraction levels.
Web Components
class ButtonElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
button { padding: 8px 16px; }
.clicked { background: blue; }
</style>
<button>${this.getAttribute('label')}</button>
`;
this.button = this.shadowRoot.querySelector('button');
this.button.addEventListener('click', this.handleClick.bind(this));
}
handleClick() {
this.button.classList.add('clicked');
this.dispatchEvent(new CustomEvent('button-click'));
}
disconnectedCallback() {
this.button?.removeEventListener('click', this.handleClick);
}
}
customElements.define('my-button', ButtonElement);
Duct Component
// Render function
function render(props) {
return (
<button class="btn">
{props.label}
</button>
);
}
// Bind function
function bind(el, eventEmitter) {
let clicked = false;
const handleClick = () => {
clicked = true;
el.classList.add('clicked');
eventEmitter.emit('click');
};
el.addEventListener('click', handleClick);
return {
release: () => el.removeEventListener('click', handleClick)
};
}
// Create component
const Button = createBlueprint(
{ id: "my/button" },
render,
{ bind }
);
export default Button;
Duct Advantages
- ✓ Clear separation of render/logic concerns
- ✓ No Shadow DOM complexity
- ✓ Simpler component composition
- ✓ Framework-agnostic JSX templates
- ✓ Explicit lifecycle management
- ✓ Better TypeScript integration
Web Components Advantages
- • Native browser standard
- • True encapsulation with Shadow DOM
- • Framework interoperability
- • No build step required
- • CSS isolation by default
- • Custom element lifecycle
When to Choose Each Framework
Choose Duct When
- ✓ You value explicit, debuggable code
- ✓ You need direct DOM control
- ✓ You want minimal abstractions
- ✓ You're building long-lived applications
- ✓ You work with AI code generation
- ✓ You prefer separation of concerns
- ✓ You need predictable performance
- ✓ You want clear debugging paths
- ✓ You prioritize maintainability
Choose Vue When
- • You want balanced complexity
- • You like single-file components
- • You need official router/state management
- • You're migrating from jQuery
- • You want good documentation
Choose React When
- • You need the largest ecosystem
- • You have React expertise
- • You need enterprise support
- • You're building complex SPAs
- • You need extensive third-party libraries
Choose Svelte When
- • You want the smallest bundle sizes
- • You like compile-time optimizations
- • You want built-in animations
- • You prefer simpler mental models
- • You're building content sites
Choose Web Components When
- • You need true framework interoperability
- • You want standards-based components
- • You need CSS encapsulation
- • You're building design systems
- • You want no build dependencies
Remember: Each framework solves different problems well. Choose based on your project needs, team expertise, and long-term maintenance goals rather than popularity alone.