or-fe-fwk
v4.0.4
Published
A minimal, modern JavaScript framework for building reactive UIs with a virtual DOM, component model, and built-in router. Designed for learning and experimentation.
Downloads
23
Readme
Frontend Framework
A minimal, modern JavaScript framework for building reactive UIs with a virtual DOM, component model, and built-in router. Designed for learning and experimentation.
Key Features
- 🚀 Virtual DOM – Fast UI updates using a diffing algorithm
- 🧩 Component Model – Encapsulated, reusable components with lifecycle events
- 🔄 Reactive State – State-driven rendering and updates
- 💾 State Persistence – Opt-in automatic localStorage saving/restoring of component state
- 🎯 Event System – Simple event emission and handling between components
- 🛣️ Routing – Hash-based navigation with guards
- 📦 Slots – Flexible content projection for composition
- ⚡ Scheduler – Batched updates for performance
- 🖼️ SVG Support – Native handling of SVG elements and attributes for icons and graphics
- 🎨 Zero Dependencies – Pure JavaScript, no external libraries
Table of Contents
- Getting Started
- Core Ideas
- Routing
- Additional Features
- API Reference
- Best Practices
- How It Works (Overview)
- Examples
Getting Started
Installation (npm)
npm install or-fe-fwkImport
import { createApp, defineComponent, h, hString, hFragment } from 'or-fe-fwk';Example Usage
import { createApp, defineComponent, h } from 'or-fe-fwk';
const HelloWorld = defineComponent({
state() {
return { count: 0 };
},
render() {
return h('div', {}, [
h('h1', {}, ['Hello World!']),
h('p', {}, [`Count: ${this.state.count}`]),
h('button', {
on: { click: () => this.updateState({ count: this.state.count + 1 }) }
}, ['Increment'])
]);
}
});
createApp(HelloWorld).mount(document.body);Core Ideas
Virtual DOM
The framework represents the UI as a virtual DOM tree, enabling efficient updates:
const vnode = h('div', { class: 'container', id: 'main' }, [
h('h1', {}, ['Title']),
h('p', {}, ['Content'])
]);Components
Define components with defineComponent(). Components can have local state, lifecycle hooks, and custom methods:
const TodoItem = defineComponent({
// Component state
state(props) {
return {
completed: false
};
},
// Lifecycle hooks
onMounted() {
console.log('Component mounted');
},
onUnmounted() {
console.log('Component unmounted');
},
// Custom methods
toggleComplete() {
this.updateState({ completed: !this.state.completed });
},
// Render function
render() {
const { text } = this.props;
const { completed } = this.state;
return h('li', {
class: completed ? 'completed' : '',
on: {
click: () => this.toggleComplete()
}
}, [text]);
}
});State Management
Component state is local and reactive. Updating state triggers a re-render:
// Update state (triggers re-render)
this.updateState({ count: this.state.count + 1 });
// Update props (from parent component)
this.updateProps({ newProp: 'value' });State Persistence (localStorage)
You can opt-in to automatic state persistence for any component by providing a persistKey option to defineComponent. When set, the component's state will be saved to localStorage on every update, and restored on initialization. This is useful for features like todo lists, drafts, or any state you want to survive page reloads.
Usage Example:
const App = defineComponent({
persistKey: "todos-app", // Unique key for localStorage
state() {
return {
todos: [],
currentTodo: "",
filter: "filter-all"
};
},
// ...methods and render as usual...
});How it works:
- On component creation, if
persistKeyis set, the framework will merge any saved state fromlocalStoragewith the initial state. - On every
updateState, the new state is saved tolocalStorageunder the given key. - No manual
localStoragecode is needed for persisted state fields.
See the examples/Todo with framework app for a real-world usage.
Event Handling
Components can emit and listen for custom events:
const ChildComponent = defineComponent({
render() {
return h('button', {
on: {
click: () => this.emit('custom-event', { data: 'hello' })
}
}, ['Click me']);
}
});
const ParentComponent = defineComponent({
render() {
return h(ChildComponent, {
on: {
'custom-event': (payload) => {
console.log('Received:', payload.data);
}
}
});
}
});Slots (Content Projection)
Slots allow flexible content composition:
const Card = defineComponent({
render() {
return h('div', { class: 'card' }, [
h('div', { class: 'card-header' }, [
hSlot() // Default slot for projected content
])
]);
}
});
// Usage
h(Card, {}, [
h('h2', {}, ['Card Title']),
h('p', {}, ['Card content'])
]);SVG Support
You can use SVG elements and attributes directly in your components. The framework automatically handles SVG namespaces and attributes, so you can create icons, charts, and scalable graphics just like in plain HTML:
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24' }, [
h('circle', { cx: 12, cy: 12, r: 10, fill: 'blue' }),
h('text', { x: 12, y: 16, 'text-anchor': 'middle', fill: 'white' }, ['Hi'])
])Routing
The framework provides a hash-based router with navigation guards:
Basic Routing
import { HashRouter, RouterOutlet, RouterLink } from './framework.js';
// Define routes
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/users/:id', component: UserProfile },
{ path: '*', component: NotFound } // Catch-all route
];
// Create router
const router = new HashRouter(routes);
// Create app with router
const app = createApp(App, {}, { router });Route Guards
const routes = [
{
path: '/admin',
component: AdminPanel,
beforeEnter: async (from, to) => {
// Return false to prevent navigation
if (!isAuthenticated()) {
return false;
}
// Return string to redirect
if (!hasAdminRole()) {
return '/unauthorized';
}
// Return nothing/true to allow navigation
return true;
}
}
];Router Components
const App = defineComponent({
render() {
return h('div', {}, [
// Navigation
h('nav', {}, [
h(RouterLink, { to: '/' }, ['Home']),
h(RouterLink, { to: '/about' }, ['About'])
]),
// Route outlet
h(RouterOutlet)
]);
}
});Accessing Route Data
const UserProfile = defineComponent({
render() {
// Access route params and query
const { id } = this.appContext.router.params;
const { tab } = this.appContext.router.query;
return h('div', {}, [
h('h1', {}, [`User ${id}`]),
h('p', {}, [`Current tab: ${tab || 'profile'}`])
]);
}
});Programmatic Navigation
// Navigate to a route
this.appContext.router.navigateTo('/users/123?tab=settings');
// Go back/forward
this.appContext.router.back();
this.appContext.router.forward();Additional Features
Fragments
Render multiple sibling elements without a wrapper:
import { hFragment } from './framework.js';
const MultipleElements = defineComponent({
render() {
return hFragment([
h('h1', {}, ['Title']),
h('p', {}, ['Paragraph 1']),
h('p', {}, ['Paragraph 2'])
]);
}
});Conditional Rendering
const ConditionalComponent = defineComponent({
state() {
return { showMessage: false };
},
render() {
return h('div', {}, [
h('button', {
on: {
click: () => this.updateState({
showMessage: !this.state.showMessage
})
}
}, ['Toggle']),
// Conditional rendering
this.state.showMessage ? h('p', {}, ['Hello!']) : null
]);
}
});Lists and Keys
const TodoList = defineComponent({
state() {
return {
todos: [
{ id: 1, text: 'Learn framework', completed: false },
{ id: 2, text: 'Build app', completed: true }
]
};
},
render() {
return h('ul', {},
this.state.todos.map(todo =>
h(TodoItem, {
key: todo.id, // Important for efficient updates
text: todo.text,
completed: todo.completed
})
)
);
}
});Styling
const StyledComponent = defineComponent({
render() {
return h('div', {
class: ['container', 'active'], // Array of classes
style: {
backgroundColor: 'blue',
padding: '20px',
margin: '10px'
}
}, ['Styled content']);
}
});API Reference
Core Functions
h(tag, props, children)
Create a virtual DOM node.
createApp(RootComponent, props, options)
Create an application instance.
defineComponent(options)
Define a reusable component.
Component Options
defineComponent({
// Initial state factory
state(props) {
return { /* initial state */ };
},
// Lifecycle hooks
onMounted() { /* after mount */ },
onUnmounted() { /* before unmount */ },
// Render function
render() {
return h(/* virtual DOM */);
},
// Custom methods
customMethod() {
// Available as this.customMethod()
}
});Component Instance Methods
updateState(newState)- Update component stateupdateProps(newProps)- Update component propsemit(eventName, payload)- Emit custom event
Component Instance Properties
this.props- Component propsthis.state- Component statethis.appContext- Application context (router, etc.)
Router API
// Router instance methods
router.navigateTo(path) // Navigate to path
router.back() // Go back in history
router.forward() // Go forward in history
router.subscribe(handler) // Subscribe to route changes
router.unsubscribe(handler) // Unsubscribe from route changes
// Router instance properties
router.matchedRoute // Current matched route
router.params // Route parameters
router.query // Query parametersBest Practices
Component Organization
// Keep components focused and single-purpose
const Button = defineComponent({
render() {
const { variant = 'primary', disabled = false } = this.props;
return h('button', {
class: [`btn`, `btn-${variant}`],
disabled,
on: {
click: () => this.emit('click')
}
}, [hSlot()]);
}
});State Management
// Use computed values in render
const UserCard = defineComponent({
render() {
const { user } = this.props;
const displayName = user.firstName + ' ' + user.lastName;
return h('div', { class: 'user-card' }, [
h('h3', {}, [displayName]),
h('p', {}, [user.email])
]);
}
});Performance
// Use keys for lists
this.state.items.map(item =>
h(ItemComponent, { key: item.id, ...item })
)
// Minimize unnecessary re-renders by checking props/state changes
// The framework automatically does shallow comparison for optimizationHow It Works (Overview)
1. Render Cycle
- Each component implements a
render()function that returns a virtual DOM tree usingh(). - When state or props change, a new tree is generated and diffed against the previous one, producing a minimal set of DOM updates.
- DOM changes are batched for performance.
2. Reactive State
- Each component has its own
stateobject. - Calling
updateState(partial)merges new state and schedules a re-render. - State is local to each component instance.
3. Lifecycle
| Hook | When it runs | |-------------|------------------------------| | onMounted | After first DOM patch | | onUnmounted | Before element is removed |
Events emitted by children bubble up to parents via an internal dispatcher.
4. Composition
- Fragments: Render multiple siblings without extra wrappers.
- Slots: Placeholders for projected content from parents.
5. Router
- Hash-based router listens to
hashchangeand matches routes. - Async
beforeEnterguards can block, redirect, or allow navigation.
6. Footprint
- Distributed as a single ES module (~6 kB min+gzip).
- No build step or external dependencies required.
Examples
See the examples directory for sample apps and usage patterns.
