A modern JavaScript frontend framework built with TypeScript, featuring MVI (Model-View-Intent) architecture, virtual DOM, and a powerful mixins system.
- MVI Architecture: Unidirectional data flow with clear separation of concerns
- Virtual DOM: Efficient DOM updates with double-ended diff algorithm
- Mixins System: Reusable component logic and state management
- JSX/TSX Support: Full React-compatible JSX syntax with Function Components
- Template System: String-based templating with interpolation and filters
- TypeScript: Full type safety with comprehensive type definitions
- Component System: Modular, reusable UI components with lifecycle management
- Performance Optimized:
- Batch update scheduler (significantly reduces render frequency)
- Shallow equality props comparison (much faster than JSON serialization)
- Double-ended diff algorithm (notably reduces DOM operations)
- Component memoization API
- Automatic memory cleanup
- Developer Experience: Development warnings, error handling, and debugging support
- Vitest Integration: Comprehensive testing support out of the box
The framework follows the Model-View-Intent pattern:
- Model: Pure functions that update application state based on actions
- View: Functions that render state to virtual DOM nodes
- Intent: Functions that handle user interactions and dispatch actions
The virtual DOM system provides efficient DOM updates through:
- Virtual node creation and management
- Diffing algorithm to detect changes
- Patch application for minimal DOM updates
Mixins allow you to share logic and state across components:
- State management mixins
- Lifecycle mixins
- Event handling mixins
- Custom behavior mixins
npm create vite@latest my-app -- --template typescript
cd my-app
npm install
npm install --save-dev vitest @types/node jsdomimport { run } from './mvi/core';
import { Fragment } from './jsx-runtime';
// Define your MVI application with JSX
const app = {
initialState: { count: 0 },
intent: (dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
}),
model: (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
},
view: (state) => (
<div className="counter">
<h1>Count: {state.count}</h1>
<button onClick={() => ({ type: 'INCREMENT' })}>+</button>
<button onClick={() => ({ type: 'DECREMENT' })}>-</button>
</div>
),
rootElement: '#app'
};
// Run the application
run(app);import { template, defaultFilters } from './template';
const user = {
name: 'John Doe',
age: 30,
city: 'New York'
};
// Render template with data
const result = template(`
<div class="user-profile">
<h2>{{ user.name }}</h2>
<p>Age: {{ user.age }}</p>
<p>City: {{ user.city | uppercase }}</p>
</div>
`, { user }, { filters: defaultFilters });import { template } from './template';
// Template for data display
const statsTemplate = template(`
<div class="stats">
<div class="stat">Users: {{ count }}</div>
</div>
`, { count: 42 });
// JSX for interactive components
const component = (
<div className="dashboard">
<h1>Dashboard</h1>
<button onClick={() => refresh()}>Refresh</button>
<div className="content">
{statsTemplate}
</div>
</div>
);import { createComponent } from './components/core';
import { counterMixin, loggingMixin } from './examples/mixins';
const CounterComponent = createComponent({
displayName: 'Counter',
initialState: { displayValue: 0 },
mixins: {
counter: counterMixin,
logger: loggingMixin
},
model: (state, action) => {
// Handle actions and update display value
if (action.type === 'INCREMENT') {
const newCounter = { ...state.counter, count: state.counter.count + 1 };
return { ...state, counter: newCounter, displayValue: newCounter.count };
}
return state;
},
view: (state) => createElement('div', { className: 'counter' },
createElement('h2', {}, 'Counter: ', state.displayValue),
createElement('button', {
onClick: () => ({ type: 'INCREMENT' })
}, '+')
)
});
// Use the component
const counter = CounterComponent();The framework provides a comprehensive JSX/TSX runtime with full React-compatible syntax:
import { jsx, Fragment } from './jsx-runtime';
// JSX is automatically transformed to jsx() calls
const element = (
<div className="container">
<h1>Title</h1>
<p>Content</p>
</div>
);
// Fragment for grouping without wrapper
const fragment = (
<Fragment>
<div>Item 1</div>
<div>Item 2</div>
</Fragment>
);Create reusable components with function components:
import { FunctionComponent } from './jsx.d';
interface ButtonProps {
label: string;
onClick: () => void;
}
const Button: FunctionComponent<ButtonProps> = ({ label, onClick }) => (
<button className="btn" onClick={onClick}>
{label}
</button>
);- ✅ Full JSX/TSX syntax support
- ✅ Function components with TypeScript support
- ✅ Fragment support for grouping elements
- ✅ Key-based reconciliation for efficient list rendering
- ✅ Event handler optimization (automatic deduplication)
- ✅ SVG support with proper type definitions
- ✅ Development warnings for missing keys and performance issues
- ✅ React-compatible API for easy migration
TypeScript and Vite are configured to handle JSX/TSX files automatically:
// tsconfig.json includes:
{
"jsx": "react-jsx",
"jsxImportSource": "./src"
}
// vite.config.ts includes:
{
esbuild: {
jsx: 'automatic',
jsxImportSource: './src',
}
}See the JSX/TSX Usage Guide for comprehensive documentation and examples.
import { template, defaultFilters } from './template';
const user = {
name: 'John',
age: 30,
items: ['apple', 'banana', 'cherry']
};
// Simple interpolation
const result = template(`
<div class="user">
<h2>{{ user.name }}</h2>
<p>Age: {{ user.age }}</p>
<p>Items: {{ user.items | length }}</p>
</div>
`, { user }, { filters: defaultFilters });// Template with conditionals and loops
const advancedTemplate = template(`
<div class="list">
<h1>{{ title | uppercase }}</h1>
<ul>
<li class="item" *for="item in items">
{{ item.name | capitalize }}
</li>
</ul>
</div>
`, {
title: 'Product List',
items: [
{ name: 'laptop' },
{ name: 'mouse' }
]
});const customFilters = {
...defaultFilters,
currency: (value: number) => `$${value.toFixed(2)}`,
formatDate: (date: Date) => date.toLocaleDateString()
};
const result = template(`
<div>
<p>Price: {{ price | currency }}</p>
<p>Date: {{ createdAt | formatDate }}</p>
</div>
`, data, { filters: customFilters });The JSX factory function used internally by the JSX compiler.
React-style Fragment component for grouping children without a wrapper element.
Renders a template string with the provided context and options.
interface TemplateOptions {
context?: TemplateContext;
components?: Record<string, Function>;
filters?: Record<string, Function>;
}Class-based template engine for more advanced usage:
const engine = new TemplateEngine({
context: { user: { name: 'John' } },
filters: { uppercase: (str) => str.toUpperCase() }
});
const result = engine.render('<h1>{{ user.name | uppercase }}</h1>');Creates a virtual DOM node.
const vnode = createElement('div', { className: 'container' },
createElement('h1', {}, 'Title'),
createElement('p', {}, 'Content')
);Lower-level function to create virtual nodes.
Compares two virtual DOM trees and generates patches.
Applies patches to the actual DOM.
Runs an MVI application.
interface MVIApp<T, A> {
initialState: T;
intent: Intent<A>;
model: Model<T, A>;
view: View<T>;
rootElement: Element | string;
}
const control = run(app);
// control.dispatch(action)
// control.getState()
// control.stop()Creates a mixin definition.
const myMixin = createMixin({
initialState: { value: 0 },
lifecycle: {
created: (state, dispatch) => {
console.log('Component created');
}
},
methods: {
increment: (state, dispatch) => {
dispatch({ type: 'INCREMENT' });
}
}
});Applies mixins to a base state.
Creates a component factory.
interface ComponentDefinition<T, P = {}> {
initialState: T;
props?: P;
mixins?: Record<string, Mixin>;
model?: Model<T, any>;
view: View<T>;
lifecycle?: MixinLifecycle<T>;
}
const MyComponent = createComponent({
initialState: { count: 0 },
view: (state) => createElement('div', {}, state.count.toString())
});See the Framework Integration Guide for comprehensive examples showing all systems working together, including:
- Counter component with mixins
- Timer component with lifecycle management
- Todo list with state persistence
- Component composition patterns
- Real-time WebSocket applications
- E-commerce dashboard with all systems integrated
The framework includes complete Context API integration examples:
- Theme Context: Theme switching (light/dark mode)
- Auth Context: User authentication and authorization
- Counter Context: Global state with mixins integration
- MVI Integration: Context usage in MVI architecture
- Todo App: Complete todo application with Context + MVI
Run examples:
import {
runMultiContextExample,
runContextMVIExample,
runTodoExample,
runThemeExample,
runAuthExample,
runCounterExample,
listExamples
} from './examples';
// List all available examples
listExamples();
// Run a specific example
runTodoExample();Additional examples are available in the src/examples/ directory.
The framework includes comprehensive tests using Vitest:
npm test # Run all tests
npm run test:run # Run tests once
npm run test:coverage # Run tests with coveragesrc/
├── vdom/ # Virtual DOM implementation
│ ├── types.ts # Type definitions
│ ├── vnode.ts # Virtual node creation
│ ├── createElement.ts # JSX-like element creation
│ ├── diff.ts # Diffing algorithm
│ └── patch.ts # DOM patching
├── mvi/ # MVI architecture
│ ├── types.ts # Type definitions
│ └── core.ts # Core MVI implementation
├── mixins/ # Mixins system
│ ├── types.ts # Type definitions
│ └── core.ts # Mixin application logic
├── components/ # Component system
│ ├── types.ts # Type definitions
│ └── core.ts # Component creation and management
└── examples/ # Usage examples and demos
├── mixins.ts # Example mixins
├── components.ts # Example components
└── index.ts # Main example entry point
npm run build # Build for production
npm run dev # Start development server- Follow TypeScript strict mode guidelines
- Add tests for new features
- Update documentation for API changes
- Use meaningful commit messages
MIT License - see LICENSE file for details.
- JSX/TSX Usage Guide - Complete guide to JSX syntax and features
- JSX Runtime API - Production JSX runtime functions
- JSX Development Runtime - Development-time JSX features
- MVI Architecture API - Model-View-Intent pattern implementation
- Virtual DOM API - Virtual DOM diffing and patching
- Components API - Component system with lifecycle management
- Mixins API - Reusable component logic and state management
- Context API - Context management for component tree data sharing
- Template System API - String-based templating with interpolation
- Utils and Warnings API - Development utilities and validation
- Framework Integration Guide - Complete examples showing all systems working together
Marabutan includes several automatic performance optimizations:
- Batch Updates: Multiple dispatches are automatically batched into a single render
- Smart Diffing: Double-ended algorithm optimizes list updates
- Shallow Comparison: Fast props comparison without JSON serialization
- Memo API: React.memo-style component memoization
- Memory Safety: Automatic cleanup prevents memory leaks
See Performance Guide for best practices and benchmarks.
- JSX/TSX support
- Context API
- Performance optimizations (batch updates, memo, optimized diff)
- Hooks API for functional components
- Server-side rendering support
- Component lazy loading
- Developer tools and DevTools integration