DomKit is a lightweight, minimalist front-end renderer with virtual DOM, dynamic component loading, and comprehensive state management. It provides an elegant API for building dynamic web applications without the overhead of larger frameworks.
- Virtual DOM with efficient diffing algorithm and key-based optimization
- Dynamic Component Loading with intelligent caching and lazy loading
- Component-based architecture for reusable UI elements
- State management with built-in state hooks and automatic re-rendering
- Declarative rendering similar to modern UI libraries
- Small footprint with zero dependencies
- DOM injection utilities for flexible integration
- Simple API that's easy to learn and use
- Focus retention for form inputs during re-renders
- Memoization for performance optimization
- Custom renderers for special cases
- MutationObserver integration for external DOM change detection
- SVG support built-in
- Event handler preservation during component loading
- Component registry system with cleanup utilities
<script src="path/to/DomKit.js"></script>import DomKit from 'path/to/DomKit.js';// Create elements with the hyperscript function
const { h, render } = DomKit;
// Create a simple virtual DOM structure
const app = h('div', { className: 'container' }, [
h('h1', {}, ['Hello, DomKit!']),
h('p', {}, ['A lightweight front-end renderer']),
h('button', {
className: 'btn',
onClick: () => alert('Button clicked!')
}, ['Click me'])
]);
// Render to the DOM
render(app, '#app');DomKit's most powerful feature is its ability to load components dynamically from external files:
// Configure the component loader
DomKit.configureComponentLoader({
domain: 'https://your-domain.com',
componentPath: '/components/',
components: {
'MyButton': 'my-button.js',
'UserCard': 'user-card.js',
'DataTable': 'data-table.js'
}
});Create a component file (e.g., my-button.js):
// my-button.js
const MyButton = DomKit.createComponent(({ text, onClick, variant = 'primary' }) => {
return DomKit.h('button', {
className: `btn btn-${variant}`,
onClick
}, [text]);
});
// Register the component
window.registerDomKitComponent('MyButton', MyButton);// Components will be loaded automatically when needed
const app = h('div', {}, [
h('MyButton', {
text: 'Click Me',
variant: 'success',
onClick: () => console.log('Clicked!')
}),
h('UserCard', { userId: 123 })
]);
// DomKit will automatically load MyButton and UserCard components
render(app, '#app');// Check if a component is loaded
if (DomKit.isComponentLoaded('MyButton')) {
console.log('MyButton is ready');
}
// Preload components
await DomKit.preloadComponents(['MyButton', 'UserCard']);
// Load a single component
await DomKit.loadComponent('DataTable');
// Get loaded component
const MyButton = DomKit.getComponent('MyButton');
// Check registration status
if (DomKit.isComponentRegistered('MyButton')) {
console.log('MyButton is registered');
}
// Get component configuration
const config = DomKit.getComponentConfig();
console.log('Loaded components:', config.loadedComponents);
// Cleanup
DomKit.unloadComponent('MyButton');
DomKit.unloadAllComponents();
DomKit.cleanupComponentLoader();DomKit supports several ways to create reusable components:
const { h, createComponent, render } = DomKit;
// Create a Button component
const Button = createComponent(({ text, onClick, variant = 'primary' }) => {
return h('button', {
className: `btn btn-${variant}`,
onClick
}, [text]);
});
// Create a Header component
const Header = createComponent(({ title, subtitle }) => {
return h('header', { className: 'app-header' }, [
h('h1', {}, [title]),
subtitle && h('p', { className: 'subtitle' }, [subtitle])
]);
});
// Use components in your app
const app = h('div', { className: 'app' }, [
h(Header, {
title: 'My DomKit App',
subtitle: 'Built with dynamic components'
}),
h('main', {}, [
h('p', {}, ['Welcome to my app built with DomKit']),
h(Button, {
text: 'Primary Button',
variant: 'primary',
onClick: () => console.log('Primary clicked!')
}),
h(Button, {
text: 'Success Button',
variant: 'success',
onClick: () => console.log('Success clicked!')
})
])
]);
// Render the app
render(app, '#app');const Counter = createStatefulComponent(({ state, setState, initialValue = 0 }) => {
// Initialize count if not set
if (state.count === undefined) {
setState({ count: initialValue });
}
return h('div', { className: 'counter' }, [
h('h3', {}, ['Counter Component']),
h('p', {}, [`Count: ${state.count || 0}`]),
h('div', { className: 'counter-buttons' }, [
h('button', {
onClick: () => setState({ count: (state.count || 0) - 1 })
}, ['Decrement']),
h('button', {
onClick: () => setState({ count: (state.count || 0) + 1 })
}, ['Increment']),
h('button', {
onClick: () => setState({ count: initialValue })
}, ['Reset'])
])
]);
}, { count: 0 });
// Use the stateful component
render(h(Counter, { initialValue: 5 }), '#app');DomKit provides comprehensive state management options:
const state = DomKit.createState({
count: 0,
user: { name: 'John', email: '[email protected]' }
});
// Subscribe to state changes
const unsubscribe = state.subscribe((newState) => {
console.log('State updated:', newState);
// Re-render your app here
});
// Update state (batched and debounced)
state.setState({ count: 5 });
state.setState({ user: { ...state.getState().user, name: 'Jane' } });
// Get current state
const currentState = state.getState();
// Cleanup when done
unsubscribe();
state.cleanup();const app = createApp((state, setState) => {
const handleIncrement = () => {
setState({ count: state.count + 1 });
};
const handleReset = () => {
setState({ count: 0, lastReset: new Date().toISOString() });
};
return h('div', { className: 'app' }, [
h('h1', {}, ['Counter App']),
h('p', {}, [`Current count: ${state.count}`]),
state.lastReset && h('p', { className: 'reset-info' }, [
`Last reset: ${new Date(state.lastReset).toLocaleString()}`
]),
h('div', { className: 'buttons' }, [
h('button', { onClick: handleIncrement }, ['Increment']),
h('button', { onClick: handleReset }, ['Reset'])
])
]);
}, { count: 0 }, '#app');
// App automatically handles state and re-rendering
// Access state management if needed
console.log('Current state:', app.getState());
app.setState({ count: 10 });const initializeApp = () => {
useState(
{ count: 0, theme: 'light' },
(state, setState) => {
return h('div', {
className: `app theme-${state.theme}`
}, [
h('h1', {}, ['useState Example']),
h('p', {}, [`Count: ${state.count}`]),
h('button', {
onClick: () => setState({ count: state.count + 1 })
}, ['Increment']),
h('button', {
onClick: () => setState({
theme: state.theme === 'light' ? 'dark' : 'light'
})
}, [`Switch to ${state.theme === 'light' ? 'dark' : 'light'} theme`])
]);
},
'#app'
);
};
// Initialize the app
initializeApp();DomKit provides several ways to inject components into existing DOM structures:
const { h, inject, append, prepend, updateInjected } = DomKit;
// Replace the content of a container
inject(h('div', { className: 'new-content' }, ['Replaced content']), '#container');
// Append to a container
append(h('p', { className: 'appended' }, ['Appended content']), '#container');
// Prepend to a container
prepend(h('p', { className: 'prepended' }, ['Prepended content']), '#container');
// Update a previously injected component
const newContent = h('div', { className: 'updated' }, ['Updated content']);
updateInjected(newContent, '#container', 0);
// Mount a component directly
const MyComponent = createComponent(() => h('div', {}, ['Mounted component']));
DomKit.mount(MyComponent, '#mount-point');DomKit includes special handling for form inputs to maintain focus and cursor position:
const FormComponent = createStatefulComponent(({ state, setState }) => {
const handleInputChange = (value) => {
setState({ inputValue: value });
};
const handleTextareaChange = (value) => {
setState({ textareaValue: value });
};
return h('form', { className: 'form' }, [
h('h3', {}, ['Form with Focus Retention']),
// Input field with focus retention
DomKit.createInputField({
type: 'text',
placeholder: 'Type something...',
value: state.inputValue || '',
onChange: handleInputChange,
className: 'form-input'
}),
// Regular textarea (focus retention works automatically)
h('textarea', {
placeholder: 'Multi-line text...',
value: state.textareaValue || '',
onChange: (e) => handleTextareaChange(e.target.value),
className: 'form-textarea'
}),
h('div', { className: 'form-output' }, [
h('p', {}, [`Input: ${state.inputValue || ''}`]),
h('p', {}, [`Textarea: ${state.textareaValue || ''}`])
])
]);
}, { inputValue: '', textareaValue: '' });
render(h(FormComponent), '#app');DomKit supports all DOM events using on prefixed properties:
h('div', {
onClick: (e) => console.log('Clicked!', e),
onMouseover: (e) => console.log('Mouse over!', e),
onKeydown: (e) => {
if (e.key === 'Enter') {
console.log('Enter pressed!');
}
},
onFocus: (e) => console.log('Focused!', e),
onBlur: (e) => console.log('Blurred!', e)
}, ['Interactive element'])const StyledComponent = createStatefulComponent(({ state, setState }) => {
const toggleColor = () => {
setState({
color: state.color === 'red' ? 'blue' : 'red'
});
};
return h('div', {
style: {
color: state.color || 'red',
fontSize: '18px',
padding: '20px',
border: `2px solid ${state.color || 'red'}`,
borderRadius: '8px',
cursor: 'pointer',
transition: 'all 0.3s ease'
},
onClick: toggleColor
}, ['Click to change color']);
}, { color: 'red' });const ConditionalComponent = createComponent(({
isVisible,
content,
showAlternative = false
}) => {
if (!isVisible) {
return showAlternative
? h('div', { className: 'alternative' }, ['Alternative content'])
: null;
}
return h('div', { className: 'main-content' }, [content]);
});
// Usage with different conditions
const app = h('div', {}, [
h(ConditionalComponent, {
isVisible: true,
content: 'This is visible'
}),
h(ConditionalComponent, {
isVisible: false,
showAlternative: true
})
]);const TodoList = createStatefulComponent(({ state, setState }) => {
const addTodo = () => {
const newTodo = {
id: Date.now(),
text: state.newTodoText || '',
completed: false
};
setState({
todos: [...(state.todos || []), newTodo],
newTodoText: ''
});
};
const toggleTodo = (id) => {
setState({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
});
};
const removeTodo = (id) => {
setState({
todos: state.todos.filter(todo => todo.id !== id)
});
};
return h('div', { className: 'todo-app' }, [
h('h2', {}, ['Todo List']),
// Add new todo
h('div', { className: 'add-todo' }, [
DomKit.createInputField({
type: 'text',
placeholder: 'Add new todo...',
value: state.newTodoText || '',
onChange: (value) => setState({ newTodoText: value })
}),
h('button', { onClick: addTodo }, ['Add'])
]),
// Todo list with keys for efficient diffing
h('ul', { className: 'todo-list' },
(state.todos || []).map(todo =>
h('li', {
key: todo.id,
className: `todo-item ${todo.completed ? 'completed' : ''}`
}, [
h('span', {
className: 'todo-text',
onClick: () => toggleTodo(todo.id)
}, [todo.text]),
h('button', {
className: 'remove-btn',
onClick: () => removeTodo(todo.id)
}, ['Remove'])
])
)
)
]);
}, { todos: [], newTodoText: '' });const ExpensiveComponent = DomKit.memo(({ data, processData = true }) => {
// Simulate expensive calculation
const processedData = processData ?
data.items.map(item => ({ ...item, processed: true })) :
data.items;
return h('div', { className: 'expensive-component' }, [
h('h3', {}, ['Expensive Component']),
h('p', {}, [`Processed ${processedData.length} items`]),
h('ul', {},
processedData.map(item =>
h('li', { key: item.id }, [
`${item.name} ${item.processed ? '(processed)' : ''}`
])
)
)
]);
}, (prevProps, nextProps) => {
// Only re-render if data.items length changes or processData changes
return prevProps.data.items.length === nextProps.data.items.length &&
prevProps.processData === nextProps.processData;
});
// Usage
const app = h('div', {}, [
h(ExpensiveComponent, {
data: { items: [{ id: 1, name: 'Item 1' }] },
processData: true
})
]);const CustomRenderComponent = (props) => {
let elementRef = null;
const vnode = h('div', {
className: 'custom-render',
ref: (el) => { elementRef = el; }
}, ['Custom rendered content']);
// Add custom rendering behavior
vnode._customRender = (oldNode) => {
// Pre-render logic
const wasScrolled = elementRef && elementRef.scrollTop > 0;
// Return post-render callback
return () => {
// Post-render logic - restore scroll position
if (wasScrolled && elementRef) {
elementRef.scrollTop = 100; // Restore scroll
}
};
};
return vnode;
};const RefComponent = createStatefulComponent(({ state, setState }) => {
let inputRef = null;
let canvasRef = null;
const focusInput = () => {
if (inputRef) {
inputRef.focus();
inputRef.select();
}
};
const drawOnCanvas = () => {
if (canvasRef) {
const ctx = canvasRef.getContext('2d');
ctx.fillStyle = state.canvasColor || 'blue';
ctx.fillRect(10, 10, 100, 100);
}
};
return h('div', { className: 'ref-component' }, [
h('h3', {}, ['Refs Example']),
h('input', {
ref: (el) => { inputRef = el; },
type: 'text',
value: state.inputValue || '',
onChange: (e) => setState({ inputValue: e.target.value })
}),
h('button', { onClick: focusInput }, ['Focus Input']),
h('canvas', {
ref: (el) => { canvasRef = el; },
width: 200,
height: 200,
style: { border: '1px solid #ccc', margin: '10px 0' }
}),
h('div', {}, [
h('button', {
onClick: () => {
setState({ canvasColor: 'red' });
setTimeout(drawOnCanvas, 0);
}
}, ['Draw Red']),
h('button', {
onClick: () => {
setState({ canvasColor: 'blue' });
setTimeout(drawOnCanvas, 0);
}
}, ['Draw Blue'])
])
]);
}, { inputValue: '', canvasColor: 'blue' });const SvgComponent = createComponent(({ size = 100, color = 'blue' }) => {
return h('svg', {
width: size,
height: size,
xmlns: 'http://www.w3.org/2000/svg',
viewBox: `0 0 ${size} ${size}`
}, [
h('circle', {
cx: size / 2,
cy: size / 2,
r: size / 3,
fill: color,
stroke: 'black',
strokeWidth: 2
}),
h('text', {
x: size / 2,
y: size / 2,
textAnchor: 'middle',
dominantBaseline: 'central',
fill: 'white',
fontSize: size / 6
}, ['SVG'])
]);
});
// Usage
const svgApp = h('div', {}, [
h('h2', {}, ['SVG Examples']),
h(SvgComponent, { size: 150, color: 'red' }),
h(SvgComponent, { size: 100, color: 'green' }),
h(SvgComponent, { size: 75, color: 'purple' })
]);Creates a virtual DOM node. Supports HTML elements, components, and dynamic component loading.
tag: String HTML tag name, component function, or registered component nameprops: Object containing element properties, event handlers, and special propertieschildren: Array of child elements (virtual nodes or strings)
Renders a virtual DOM tree to a DOM container with efficient diffing and automatic component loading.
vnode: Virtual DOM node to rendercontainer: DOM element or CSS selector to render into
Creates a reusable component from a template function.
template: Function that receives props and returns a virtual DOM structure
Configures the dynamic component loading system.
settings.domain: Base domain for component loadingsettings.componentPath: Path to components directorysettings.components: Object mapping component names to file paths
Loads a single component dynamically.
name: Component name to load- Returns: Promise that resolves to the component function
Preloads multiple components.
names: Array of component names to preload- Returns: Promise that resolves when all components are loaded
Checks if a component is already loaded.
name: Component name to check- Returns: Boolean indicating if component is loaded
Gets a loaded component.
name: Component name- Returns: Component function (throws error if not loaded)
Registers a component for dynamic loading.
name: Component namepath: File path relative to component directory
Gets current component loader configuration.
- Returns: Object with domain, path, registered components, and loaded components
Component cleanup utilities.
Creates a state management object with batched updates.
initialState: Initial state object- Returns: Object with
getState,setState,subscribe, andcleanupmethods
Creates a component with built-in state management.
renderFn: Function that receives{ state, setState, ...props }and returns virtual DOMinitialState: Initial state object
Hook-style state management with automatic rendering.
initialState: Initial state objectrenderFn: Function that receives(state, setState)and returns virtual DOMcontainer: Target container for rendering
Creates an application with automatic state management and rendering.
renderFn: Function that receives(state, setState)and returns virtual DOMinitialState: Initial state objectcontainerSelector: Target container selector
Creates an input field with focus and cursor position retention during re-renders.
props: Standard input properties plusonChangecallback- Returns: Virtual DOM node with focus retention behavior
Injects a virtual node into the DOM.
vnode: Virtual DOM node to injecttarget: DOM element or CSS selectorposition: "replace" (default), "append", or "prepend"
Convenience methods for appending/prepending nodes.
Updates a previously injected component with diffing.
index: Index of the component to update (default: 0)
Convenience method to mount a component function to a container.
Memoizes a component to prevent unnecessary re-renders.
component: Component function to memoizeshouldUpdate: Optional comparison function(prevProps, nextProps) => boolean
- DomKit uses an efficient virtual DOM diffing algorithm to minimize DOM operations
- Components are loaded dynamically only when needed, reducing initial bundle size
- Use unique
keyproperties when rendering lists for optimal diffing performance - State updates are automatically batched and debounced using
requestAnimationFrame - MutationObserver detects external DOM changes and handles them gracefully
- Use
memofor expensive components to prevent unnecessary re-renders - Component loading is cached - components are only loaded once
- Focus retention for inputs works without performance penalties
DomKit works in all modern browsers that support:
- ES5+ features
- Promise API
- MutationObserver (graceful degradation if not available)
requestAnimationFramefor optimal performance
For older browsers, consider using appropriate polyfills.
- Component Organization: Keep components in separate files and use the dynamic loading system
- State Management: Use
createAppfor application-level state,createStatefulComponentfor component-level state - Performance: Use keys for list items, memoize expensive components
- Error Handling: Components automatically handle errors and display fallback content
- Memory Management: Call cleanup methods when destroying components or apps
- Event Handling: Prefer declarative event handling over manual DOM manipulation
If migrating from other frameworks:
- From React: DomKit's API is very similar -
hreplacescreateElement, components work the same way - From Vue: Use
createStatefulComponentfor component-level reactivity - From vanilla JS: Use injection methods to gradually adopt DomKit in existing applications
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.