@bernotieno/mini-framework v1.1.0
Mini Framework
A lightweight, zero-dependency JavaScript framework built from scratch. Mini Framework provides all the essential features you need to build modern web applications without the bloat of larger frameworks.
Features
- šÆ Virtual DOM - Efficient DOM rendering and updates
- šŖ Custom Event System - Pub-sub pattern without addEventListener
- š Reactive State Management - Global state with automatic UI updates
- š£ļø Client-side Routing - Hash and history-based navigation
- š¦ Zero Dependencies - No external libraries required
- š§© Component System - Reusable UI components
- š¾ Local Storage Integration - Built-in persistence helpers
- š§ Utility Functions - Common helpers included
Installation
NPM Installation
npm install @bernotieno/mini-frameworkCDN Usage
<script type="module">
import { createApp, h, defineComponent } from 'https://unpkg.com/@bernotieno/mini-framework/src/framework.js';
// Your app code here
</script>Quick Start
Using NPM
import { createApp, defineComponent, h } from '@bernotieno/mini-framework';
const HelloWorld = defineComponent({
render() {
return h('div', {}, [
h('h1', {}, 'Hello, Mini Framework!'),
h('p', {}, 'Built with zero dependencies')
]);
}
});
const app = createApp();
const component = new HelloWorld();
component.mount(document.getElementById('app'));Development Setup
- Clone this repository
- Install dependencies:
npm install - Start development server:
npm run dev - Open
http://localhost:3000/examples/todomvc/to see the TodoMVC demo
How the Framework Works
Mini Framework is built from scratch using vanilla JavaScript with zero dependencies. Here's how each core system works internally:
Architecture Overview
The framework consists of four main modules:
- Virtual DOM Engine (
src/core/dom.js) - Converts JSON-like structures to real DOM elements - Event System (
src/core/events.js) - Custom pub-sub pattern replacing addEventListener - State Management (
src/core/state.js) - Reactive state store with automatic UI updates - Router (
src/core/router.js) - Client-side navigation with URL synchronization
Virtual DOM Implementation
The Virtual DOM system works by:
1. Creating lightweight JavaScript objects representing DOM structure
2. Rendering these objects to real DOM elements when needed
3. Providing a simple h() function for element creation
4. Supporting nested elements, attributes, and event handlers
Event System Architecture
Instead of relying on addEventListener, the framework implements:
1. A custom EventEmitter class with pub-sub pattern
2. DOM event delegation for efficient event handling
3. Declarative event binding through element attributes
4. Global event communication between components
Reactive State Management
The state system provides: 1. Global state store with dot-notation path access 2. Automatic subscriber notification on state changes 3. Component-level state isolation 4. Action pattern for complex state mutations
Router Implementation
The routing system handles: 1. URL parsing and route matching with parameters 2. History API integration for clean URLs 3. Hash-based fallback for broader compatibility 4. Navigation hooks for route guards and side effects
Core Concepts
1. Virtual DOM
The framework uses a JSON-like structure to represent DOM elements:
import { h, vdom } from '@bernotieno/mini-framework';
// Create virtual DOM elements
const element = h('div', { class: 'container' }, [
h('h1', {}, 'Hello World'),
h('p', { id: 'description' }, 'This is a paragraph')
]);
// Mount to real DOM
vdom.mount(element, document.getElementById('app'));Virtual DOM Structure
{
tag: 'div',
attrs: { class: 'example', id: 'my-div' },
children: [
'Text content',
{
tag: 'span',
attrs: { style: 'color: red' },
children: ['Nested content']
}
]
}2. Custom Event System
Instead of addEventListener, the framework uses a custom pub-sub event system:
import { eventEmitter, domEvents } from '@bernotieno/mini-framework';
// Global event emitter
eventEmitter.on('custom-event', (data) => {
console.log('Event received:', data);
});
eventEmitter.emit('custom-event', { message: 'Hello!' });
// DOM event binding
const element = document.querySelector('#my-button');
domEvents.bind(element, 'click', (e) => {
console.log('Button clicked!');
});
// Declarative event binding
domEvents.bindEvents(element, {
click: () => console.log('Clicked!'),
mouseover: () => console.log('Hovered!')
});3. Reactive State Management
Global state that automatically triggers UI updates:
import { globalState, subscribeToState } from '@bernotieno/mini-framework';
// Set state
globalState.set('user', { name: 'John', age: 30 });
globalState.set('user.name', 'Jane'); // Nested updates
// Get state
const user = globalState.get('user');
const userName = globalState.get('user.name');
// Subscribe to changes
const unsubscribe = subscribeToState('user', (user) => {
console.log('User changed:', user);
});
// Update with function
globalState.update('user.age', age => age + 1);
// Actions for complex state changes
const userActions = {
updateProfile: globalState.action('updateProfile', (state, newData) => {
state.set('user', { ...state.get('user'), ...newData });
})
};4. Client-side Routing
Simple routing with URL synchronization:
import { router } from '@bernotieno/mini-framework';
// Define routes
router.route('/', () => {
console.log('Home page');
});
router.route('/users/:id', (route) => {
console.log('User ID:', route.params.id);
});
router.route('/products', () => {
console.log('Products page');
});
// Navigate programmatically
router.navigate('/users/123');
router.navigate('/products', { replace: true });
// Navigation hooks
router.beforeEach((to, from) => {
console.log(`Navigating from ${from} to ${to}`);
return true; // Return false to cancel navigation
});
router.afterEach((to, from) => {
console.log('Navigation completed');
});Component System
Create reusable components using the defineComponent function:
import { defineComponent, h } from '@bernotieno/mini-framework';
const UserCard = defineComponent({
// Initial component state
state: {
expanded: false
},
// Lifecycle hooks
afterMount() {
console.log('Component mounted');
},
beforeUnmount() {
console.log('Component will unmount');
},
// Methods
toggleExpanded() {
this.setState('expanded', !this.getState('expanded'));
},
// Render method
render() {
const { user } = this.props;
const expanded = this.getState('expanded');
return h('div', { class: 'user-card' }, [
h('h3', {}, user.name),
h('button', {
onclick: () => this.toggleExpanded()
}, expanded ? 'Collapse' : 'Expand'),
expanded ? h('div', { class: 'details' }, [
h('p', {}, `Age: ${user.age}`),
h('p', {}, `Email: ${user.email}`)
]) : null
]);
}
});
// Use the component
const userCard = new UserCard({ user: { name: 'John', age: 30, email: 'john@example.com' } });
userCard.mount(document.getElementById('container'));Application Setup
Create and initialize your application:
import { createApp } from '@bernotieno/mini-framework';
const app = createApp({
routes: {
'/': () => console.log('Home'),
'/about': () => console.log('About'),
'/contact': () => console.log('Contact')
}
});
// Initialize the app
app.init(document.getElementById('app'));
// Register components
app.component('UserCard', UserCard);
// Create component instances
const userCard = app.createComponent('UserCard', { user: userData });Utility Functions
The framework includes helpful utility functions:
import { debounce, throttle, storage, validators } from '@bernotieno/mini-framework';
// Debounce function calls
const debouncedSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);
// Throttle function calls
const throttledScroll = throttle(() => {
console.log('Scrolling...');
}, 100);
// Local storage helpers
storage.set('user', { name: 'John' });
const user = storage.get('user');
storage.remove('user');
// Validation
const isValidEmail = validators.email('test@example.com'); // true
const isRequired = validators.required(''); // false
const hasMinLength = validators.minLength(5)('hello'); // trueEvent Handling Examples
Basic Event Binding
// In component render method
h('button', {
onclick: (e) => {
console.log('Button clicked!');
e.preventDefault();
e.stopPropagation();
}
}, 'Click me')
// Multiple events
h('input', {
oninput: (e) => this.setState('value', e.target.value),
onkeydown: (e) => {
if (e.originalEvent.key === 'Enter') {
this.handleSubmit();
}
},
onblur: () => this.handleBlur()
})Custom Events
import { eventEmitter } from '@bernotieno/mini-framework';
// Component A emits an event
eventEmitter.emit('user-updated', { id: 123, name: 'John' });
// Component B listens for the event
eventEmitter.on('user-updated', (userData) => {
console.log('User updated:', userData);
});
// One-time listener
eventEmitter.once('app-ready', () => {
console.log('App is ready!');
});State Management Patterns
Local Component State
const Counter = defineComponent({
state: {
count: 0
},
increment() {
this.setState('count', this.getState('count') + 1);
},
render() {
return h('div', {}, [
h('span', {}, `Count: ${this.getState('count')}`),
h('button', { onclick: () => this.increment() }, '+')
]);
}
});Global State with Actions
import { globalState } from '@bernotieno/mini-framework';
// Define actions
const counterActions = {
increment: globalState.action('increment', (state) => {
const count = state.get('counter.count') || 0;
state.set('counter.count', count + 1);
}),
decrement: globalState.action('decrement', (state) => {
const count = state.get('counter.count') || 0;
state.set('counter.count', count - 1);
}),
reset: globalState.action('reset', (state) => {
state.set('counter.count', 0);
})
};
// Use in components
const GlobalCounter = defineComponent({
afterMount() {
this.subscribeToGlobalState('counter.count', () => {
this.setState('updated', Date.now()); // Trigger re-render
});
},
render() {
const count = globalState.get('counter.count') || 0;
return h('div', {}, [
h('span', {}, `Global Count: ${count}`),
h('button', { onclick: counterActions.increment }, '+'),
h('button', { onclick: counterActions.decrement }, '-'),
h('button', { onclick: counterActions.reset }, 'Reset')
]);
}
});Routing Examples
Basic Routing
import { router, globalState } from '@bernotieno/mini-framework';
// Set up routes
router.route('/', () => {
globalState.set('currentPage', 'home');
});
router.route('/users', () => {
globalState.set('currentPage', 'users');
});
router.route('/users/:id', (route) => {
globalState.set('currentPage', 'user-detail');
globalState.set('currentUserId', route.params.id);
});
// Route with query parameters
router.route('/search', (route) => {
globalState.set('currentPage', 'search');
globalState.set('searchQuery', route.query.q || '');
});Route-based Component Rendering
const App = defineComponent({
afterMount() {
this.subscribeToGlobalState('currentPage', () => {
this.setState('updated', Date.now());
});
},
render() {
const currentPage = globalState.get('currentPage');
return h('div', { class: 'app' }, [
h('nav', {}, [
h('a', {
href: '#/',
onclick: (e) => {
e.preventDefault();
router.navigate('/');
}
}, 'Home'),
h('a', {
href: '#/users',
onclick: (e) => {
e.preventDefault();
router.navigate('/users');
}
}, 'Users')
]),
h('main', {}, this.renderPage(currentPage))
]);
},
renderPage(page) {
switch (page) {
case 'home':
return h(HomePage);
case 'users':
return h(UsersPage);
case 'user-detail':
return h(UserDetailPage, {
userId: globalState.get('currentUserId')
});
default:
return h('div', {}, 'Page not found');
}
}
});TodoMVC Example
The examples/todomvc/ directory contains a complete TodoMVC implementation demonstrating all framework features:
- State Management: Global state for todos, filters, and editing state
- Components: Modular components for header, todo items, and footer
- Events: Custom event handling for user interactions
- Routing: URL-based filtering (all/active/completed)
- Persistence: Local storage integration
Key features implemented:
- ā Add new todos
- ā Toggle todo completion
- ā Edit todos (double-click)
- ā Delete todos
- ā Filter todos (All/Active/Completed)
- ā Clear completed todos
- ā Toggle all todos
- ā Persistent storage
- ā URL routing
API Reference
Core Functions
h(tag, attrs, children)- Create virtual DOM elementscreateApp(options)- Create application instancedefineComponent(definition)- Create component class
State Management
globalState.get(path)- Get state valueglobalState.set(path, value)- Set state valueglobalState.update(path, updater)- Update with functionglobalState.subscribe(path, callback)- Subscribe to changesglobalState.action(name, actionFn)- Create state action
Routing
router.route(path, handler)- Define routerouter.navigate(path, options)- Navigate to routerouter.beforeEach(hook)- Add before navigation hookrouter.afterEach(hook)- Add after navigation hook
Events
eventEmitter.on(event, callback)- Subscribe to eventeventEmitter.emit(event, data)- Emit eventdomEvents.bind(element, event, handler)- Bind DOM event
Security Features
Mini Framework includes comprehensive security measures to protect against common web vulnerabilities:
XSS Protection
- HTML Escaping: All text content is automatically escaped to prevent script injection
- Attribute Sanitization: Dangerous attributes like
onclickwith string values are blocked - URL Validation:
javascript:and unsafedata:URLs are automatically blocked - CSS Sanitization: Style attributes are cleaned to remove dangerous CSS expressions
Input Validation
- Path Traversal Protection: Router paths are sanitized to prevent
../attacks - Prototype Pollution Prevention: State objects are validated to block
__proto__manipulation - Event Name Validation: Event names are sanitized and length-limited
- Storage Key Validation: LocalStorage keys are validated and sanitized
Resource Limits
- Memory Protection: Limits on subscribers, routes, and event listeners prevent memory exhaustion
- Recursion Prevention: Maximum depth limits prevent stack overflow attacks
- Rate Limiting: Event emission is rate-limited to prevent DoS attacks
- Size Limits: Storage values are size-checked to prevent quota exhaustion
Error Handling
- Graceful Degradation: Components continue working even when individual parts fail
- Error Boundaries: Rendering errors are contained and don't crash the entire app
- Safe Fallbacks: Invalid operations fall back to safe defaults
- Comprehensive Logging: All security events are logged for monitoring
// Example: Automatic XSS protection
const userInput = '<script>alert("xss")</script>';
h('div', {}, userInput); // Automatically escaped: <script>alert("xss")</script>
// Example: Blocked dangerous attributes
h('div', { onclick: 'alert("xss")' }); // Blocked and logged as warning
// Example: Safe URL handling
h('a', { href: 'javascript:alert("xss")' }); // Blocked and logged as warningError Handling Best Practices
The framework provides robust error handling throughout:
Component Error Boundaries
const SafeComponent = defineComponent({
render() {
try {
return h('div', {}, this.renderContent());
} catch (error) {
console.error('Component render error:', error);
return h('div', { class: 'error' }, 'Something went wrong');
}
}
});State Error Recovery
// State operations are automatically wrapped in try-catch
globalState.set('user.profile', userData); // Safe even if userData is invalid
// Subscribe with error handling
globalState.subscribe('user', (user) => {
try {
updateUI(user);
} catch (error) {
console.error('UI update failed:', error);
// Framework continues working
}
});Event Error Isolation
// Event handlers are automatically wrapped for safety
h('button', {
onclick: () => {
throw new Error('Handler error');
// Error is caught and logged, app continues working
}
}, 'Click me');Browser Support
- Modern browsers with ES6+ support
- Chrome 61+
- Firefox 60+
- Safari 12+
- Edge 79+
File Structure
mini-framework/
āāā src/
ā āāā core/
ā ā āāā dom.js # Virtual DOM implementation
ā ā āāā events.js # Custom event system
ā ā āāā state.js # Reactive state management
ā ā āāā router.js # Client-side routing
ā āāā framework.js # Main framework entry
ā āāā utils.js # Utility functions
āāā examples/
ā āāā todomvc/ # TodoMVC implementation
āāā README.md # This documentation
āāā package.json # Project configurationPublishing to NPM
This framework is designed to be published as an npm package. Here's how to publish it:
Prerequisites
- NPM Account: Create an account at npmjs.com
- Login to NPM: Run
npm loginin your terminal - Update Package Name: The package is configured as
@bernotieno/mini-framework
Publishing Steps
Package Configuration (already configured):
{ "name": "@bernotieno/mini-framework", "repository": { "type": "git", "url": "git+https://github.com/bernotieno/mini-framework.git" }, "bugs": { "url": "https://github.com/bernotieno/mini-framework/issues" }, "homepage": "https://github.com/bernotieno/mini-framework#readme" }Version Management:
# Patch version (1.0.0 -> 1.0.1) npm version patch # Minor version (1.0.0 -> 1.1.0) npm version minor # Major version (1.0.0 -> 2.0.0) npm version majorPublish:
# For scoped packages (recommended) npm publish --access public # For unscoped packages npm publish
Using the Published Package
Once published, users can install and use your framework:
npm install @bernotieno/mini-frameworkimport { createApp, defineComponent, h } from '@bernotieno/mini-framework';
const MyComponent = defineComponent({
render() {
return h('div', {}, 'Hello from Mini Framework!');
}
});
const app = createApp();
const component = new MyComponent();
component.mount(document.getElementById('app'));Package Features
- ā ES Modules: Native ES module support
- ā TypeScript Definitions: Included for better developer experience
- ā Zero Dependencies: No external runtime dependencies
- ā Tree Shakeable: Import only what you need
- ā CDN Ready: Works with unpkg, jsDelivr, etc.
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
MIT License - feel free to use this framework in your projects!
Mini Framework - Building modern web apps with zero dependencies! š