Duct is a modern UI framework that provides a clear separation between view code (templates) and logic code (companions). It embraces simplicity and maintainability through a well-defined component architecture.
Core Architecture
Blueprint Pattern
Every Duct component follows a blueprint pattern with distinct phases:
Render
Pure JSX templates that define the component's structure and initial state
Load
Optional async data fetching phase that runs before binding
Bind
Event handling, DOM manipulation, and component logic initialization
Release
Cleanup phase that removes listeners and frees resources
Separation of Concerns
Duct enforces a clean separation between different aspects of component development:
Concern | Location | Purpose |
---|---|---|
Presentation | Render Function | Define HTML structure and styling |
Data Loading | Load Function | Fetch data asynchronously before binding |
Behavior | Bind Function | Handle interactions and state changes |
Cleanup | Release Function | Remove listeners and prevent memory leaks |
Key Features
Type-Safe Event System
Duct provides a strongly typed event system built on TypeScript:
// Define component events with full type safety
export interface ButtonEvents extends BaseComponentEvents {
click: (el: HTMLElement) => void
stateChange: (el: HTMLElement, newState: string) => void
}
// Use events in templates with clear syntax
<Button
label="Click me"
on:click={handleButtonClick}
on:stateChange={handleStateChange}
/>
Direct DOM Manipulation
Duct components have direct access to DOM elements, allowing for efficient updates without virtual DOM overhead:
function bind(el: HTMLElement, eventEmitter: EventEmitter<Events>) {
const button = el.querySelector('button')
// Direct DOM updates are fast and explicit
function updateState(newState: string) {
button.className = `btn btn-${newState}`
button.dataset.state = newState
}
}
Precompiled Templates
Templates are compiled at build time for optimal performance, with no runtime template parsing overhead.
Component Logic Access
Components expose their logic for programmatic control using refs:
// Recommended: Use refs for synchronous access
import { Button } from '@duct-ui/components'
const buttonRef = createRef()
<Button ref={buttonRef} label="Test" />
// Access component logic immediately
buttonRef.current?.setDisabled(true)
buttonRef.current?.updateLabel('New Text')
Lifecycle Flow
Generate initial HTML structure with JSX templates
Fetch data asynchronously (optional) - perfect for API calls
Attach event listeners, initialize component logic, and update DOM with loaded data
Clean up event listeners and resources when component is removed