A lightweight, React-like JavaScript framework with virtual DOM, hooks, and client-side routing.
- Virtual DOM - Efficient DOM manipulation using a virtual representation
jsx()Function - Create elements using thejsx()function (no JSX transpiler needed)- React-like Hooks -
useStateanduseEffectfor state and side effects - Key-based Reconciliation - Optimized list rendering with key props (must be unique)
- Client-side Routing - Hash-based routing system
- Lightweight - Minimal bundle size with no dependencies
- No Build Step Required - Works directly in the browser using ES modules
The framework follows a unidirectional data flow pattern similar to React:
User Interaction → State Change → Re-render → Virtual DOM Diff → DOM Patch → UI Update
-
Element Creation with
jsx()Function- Developers call
jsx('div', props, ...children)directly - Creates virtual DOM nodes (plain JavaScript objects)
- No JSX transpiler or build step required
- Developers call
-
Virtual DOM Creation
- Component functions return virtual DOM trees
- Virtual nodes contain:
{ type, props, children }
-
Diffing Algorithm
- Compares new virtual DOM with previous virtual DOM
- Uses key-based reconciliation for efficient list updates
- Identifies minimal changes needed
-
DOM Patching
- Only updates changed parts of the real DOM
- Preserves DOM state (focus, scroll position, etc.)
- Reuses existing DOM nodes when possible
- Performance: Only necessary DOM updates are performed
- Abstraction: Developers think in terms of state, not manual DOM manipulation
- Predictability: Same state always produces same UI
- Element Identity: Keys tell the framework which elements are the same across renders
- Efficient Moves: Moving list items doesn't destroy and recreate DOM nodes
- State Preservation: Input focus, scroll position, and component state are maintained
- Encapsulation: State and effects live with components
- Reusability: Custom hooks can be created for shared logic
- Order Dependency: Hooks must be called in the same order each render (enforced by index-based storage)
- No Build Step: Works directly in the browser with ES modules
- Simplicity: No need for Babel, webpack, or other transpilers
- Transparency: You see exactly what's happening - just function calls
- Learning: Better understanding of how JSX works under the hood
- Include the framework in your HTML:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="app/app.js"></script>
</body>
</html>- Import the framework functions (no build step or JSX transpiler needed):
import { jsx, useState, useEffect, render } from "./framework/main.js";Your framework uses the jsx() function to create virtual DOM elements. You call this function directly in your code.
import { jsx } from "./framework/main.js";
// Creating a simple element
const element = jsx("div", null, "Hello World");
// What it creates (virtual DOM object):
// { type: 'div', props: {}, children: ['Hello World'] }Function Signature:
jsx(type, props, ...children);type(string): HTML tag name like 'div', 'span', 'button'props(object|null): Element properties (className, id, events, etc.)children(any): Child elements, text, or arrays of children
import { jsx } from "./framework/main.js";
// Nested elements
const card = jsx(
"div",
null,
jsx("h1", null, "Title"),
jsx("p", null, "Description")
);
// This creates:
// <div>
// <h1>Title</h1>
// <p>Description</p>
// </div>// Children can be passed as multiple arguments
const list = jsx(
"ul",
null,
jsx("li", null, "Item 1"),
jsx("li", null, "Item 2"),
jsx("li", null, "Item 3")
);
// Or as an array with spread operator
const items = ["Apple", "Banana", "Cherry"];
const fruitList = jsx(
"ul",
null,
...items.map((fruit) => jsx("li", { key: fruit }, fruit))
);How it works:
- You call
jsx()directly - No JSX syntax or transpiler jsx()creates virtual DOM nodes - Plain JavaScript objects- Each virtual node has:
{ type, props, children } - The framework uses these objects to efficiently update the real DOM
- Children are automatically flattened and filtered (removes null, undefined, false, true)
Attributes are passed as the second argument (props object) to the jsx() function:
import { jsx } from "./framework/main.js";
// className attribute
const styledDiv = jsx("div", { className: "container" }, "Content");
// Creates: <div class="container">Content</div>
// id attribute
const uniqueDiv = jsx("div", { id: "main-content" }, "Content");
// Creates: <div id="main-content">Content</div>
// Multiple attributes
const multiAttr = jsx("input", {
type: "text",
placeholder: "Enter name",
id: "name-input",
className: "form-control",
});
// Creates: <input type="text" placeholder="Enter name" id="name-input" class="form-control" />
// Custom data attributes
const dataAttr = jsx(
"div",
{
"data-user-id": "123",
"data-role": "admin",
},
"User"
);
// Creates: <div data-user-id="123" data-role="admin">User</div>
// Style as object (supported by the framework)
const styledElement = jsx(
"div",
{
style: {
color: "red",
fontSize: "16px",
},
},
"Styled text"
);
// Creates: <div style="color: red; font-size: 16px;">Styled text</div>
// Boolean attributes
const checkbox = jsx("input", {
type: "checkbox",
checked: true,
disabled: false,
});
// Null props (when element has no attributes)
const simpleDiv = jsx("div", null, "No attributes");How it works:
- Attributes are stored in the
propsobject (second parameter) classNamemaps to DOM'sclassNamepropertyidmaps to DOM'sidproperty- Other attributes use
setAttribute() - Event handlers (starting with "on") are added as event listeners
styleobjects are converted to inline stylesnullor{}can be used when there are no props
Event handlers are passed in the props object with "on" prefix:
import { jsx, useState } from "./framework/main.js";
function Counter() {
const [count, setCount] = useState(0);
// Click event handler
const handleClick = () => {
setCount(count + 1);
};
// Event with parameter
const handleReset = () => {
setCount(0);
};
return jsx(
"div",
null,
jsx("h1", null, "Count: ", count),
jsx("button", { onClick: handleClick }, "Increment"),
jsx("button", { onClick: handleReset }, "Reset")
);
}import { jsx } from "./framework/main.js";
// Mouse events
jsx("button", { onClick: handleClick }, "Click");
jsx("div", { onMouseOver: handleHover }, "Hover");
jsx("div", { onMouseOut: handleOut }, "Leave");
jsx("div", { onMouseDown: handleDown }, "Press");
jsx("div", { onMouseUp: handleUp }, "Release");
// Input events
jsx("input", { onChange: handleChange });
jsx("input", { onInput: handleInput });
jsx("input", { onFocus: handleFocus });
jsx("input", { onBlur: handleBlur });
// Form events
jsx(
"form",
{ onSubmit: handleSubmit },
jsx("input", { type: "text" }),
jsx("button", { type: "submit" }, "Submit")
);
// Keyboard events
jsx("input", { onKeyDown: handleKeyDown });
jsx("input", { onKeyUp: handleKeyUp });
jsx("input", { onKeyPress: handleKeyPress });
// Example with event object
const handleInputChange = (event) => {
console.log("Input value:", event.target.value);
};
jsx("input", {
type: "text",
onChange: handleInputChange,
});
// Preventing default behavior
const handleFormSubmit = (event) => {
event.preventDefault();
console.log("Form submitted");
};
jsx(
"form",
{ onSubmit: handleFormSubmit },
jsx("button", { type: "submit" }, "Submit")
);How it works:
- Props starting with "on" are detected (e.g.,
onClick) - The "on" prefix is removed:
onClick→Click - Converted to lowercase:
Click→click - Added as event listener:
element.addEventListener('click', handler) - When state changes via the handler,
render()is called automatically - Event listeners are properly cleaned up during diffing
Elements can be deeply nested by passing jsx() calls as children:
import { jsx } from "./framework/main.js";
function UserCard({ user }) {
return jsx(
"div",
{ className: "card" },
jsx(
"div",
{ className: "card-header" },
jsx("img", { src: user.avatar, alt: user.name }),
jsx("h2", null, user.name)
),
jsx(
"div",
{ className: "card-body" },
jsx("p", null, user.bio),
jsx(
"div",
{ className: "card-footer" },
jsx("span", null, "Followers: ", user.followers),
jsx("span", null, "Following: ", user.following)
)
)
);
}
// This creates the structure:
// <div class="card">
// <div class="card-header">
// <img src="..." alt="...">
// <h2>User Name</h2>
// </div>
// <div class="card-body">
// <p>Bio text...</p>
// <div class="card-footer">
// <span>Followers: 100</span>
// <span>Following: 50</span>
// </div>
// </div>
// </div>Use key props for efficient list rendering. Pass the key in the props object:
import { jsx, useState } from "./framework/main.js";
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn framework", done: false },
{ id: 2, text: "Build project", done: false },
]);
return jsx(
"ul",
null,
...todos.map((todo) =>
jsx(
"li",
{
key: todo.id,
className: todo.done ? "done" : "",
},
todo.text
)
)
);
}
// Each list item becomes:
// jsx('li', { key: 1, className: '' }, 'Learn framework')
// jsx('li', { key: 2, className: '' }, 'Build project')Why keys matter:
- Without keys: Framework uses index-based diffing (slower, can cause bugs)
- With keys: Framework tracks elements by identity (faster, preserves state)
- When list reorders: DOM nodes are moved, not recreated
import { jsx } from "./framework/main.js";
function Navigation() {
const links = [
{ id: 1, href: "#/", text: "Home" },
{ id: 2, href: "#/about", text: "About" },
{ id: 3, href: "#/contact", text: "Contact" },
];
return jsx(
"nav",
{ className: "navbar" },
jsx(
"ul",
null,
...links.map((link) =>
jsx("li", { key: link.id }, jsx("a", { href: link.href }, link.text))
)
)
);
}
// Creates:
// <nav class="navbar">
// <ul>
// <li><a href="#/">Home</a></li>
// <li><a href="#/about">About</a></li>
// <li><a href="#/contact">Contact</a></li>
// </ul>
// </nav>Manages component state with automatic re-rendering.
const [state, setState] = useState(initialValue);Parameters:
initialValue- Initial state value (any type)
Returns:
[state, setState]- Current state and setter function
Examples:
// Number state
const [count, setCount] = useState(0);
setCount(5); // Direct value
setCount((prev) => prev + 1); // Functional update
// Object state
const [user, setUser] = useState({ name: "", age: 0 });
setUser({ name: "John", age: 30 });
// Array state
const [items, setItems] = useState([]);
setItems([...items, newItem]); // Add item
setItems(items.filter((item) => item.id !== id)); // Remove itemRules:
- Must be called in the same order every render
- Don't call inside loops, conditions, or nested functions
- Calling
setStatetriggers a re-render
Performs side effects after render.
useEffect(() => {
// Effect code
return () => {
// Cleanup (optional)
};
}, [dependencies]);Parameters:
callback- Function to run after renderdependencies- Array of values that trigger re-run when changed
Examples:
// Run once on mount
useEffect(() => {
console.log("Component mounted");
}, []);
// Run when state changes
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// Multiple dependencies
useEffect(() => {
fetchUserData(userId, filter);
}, [userId, filter]);
// With cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
return () => {
clearInterval(timer); // Cleanup on unmount
};
}, []);How it works:
- Dependencies are compared using shallow equality (
===) - If any dependency changed, effect runs
- Effect runs after DOM updates are applied
- Cleanup function runs before next effect and on unmount
Creates a global store for managing shared state across components without prop drilling.
const store = Store(initialState);Parameters:
initialState- Initial state object (optional, defaults tonull)
Returns:
- Object with
get()andset()methods
Methods:
get()- Returns the current stateset(newState)- MergesnewStatewith current state and triggers re-render
Examples:
import { Store, jsx } from "./framework/main.js";
// Create a store
const userStore = Store({ name: "Guest", loggedIn: false });
// In any component - read state
function Header() {
const user = userStore.get();
return jsx("div", null, `Welcome, ${user.name}`);
}
// In any component - update state
function LoginButton() {
const handleLogin = () => {
userStore.set({ name: "John", loggedIn: true });
};
return jsx("button", { onclick: handleLogin }, "Login");
}
// Multiple stores for different concerns
const themeStore = Store({ mode: "light", fontSize: 14 });
const cartStore = Store({ items: [], total: 0 });
// Update multiple properties
themeStore.set({ mode: "dark" }); // Only updates mode, keeps fontSize
cartStore.set({ items: [...items, newItem], total: newTotal });
// Store with no initial state
const tempStore = Store(); // state is null initially
tempStore.set({ data: "value" }); // Now state is { data: 'value' }How it works:
- Creates a closure with private state
set()merges new state with existing state using spread operator- Calling
set()automatically triggers a re-render - All components using the store will get updated values
- Unlike Context API, stores don't require Provider wrappers
When to use:
- ✅ Global app state (theme, auth, settings)
- ✅ Shared data across multiple components
- ✅ Simple state management without boilerplate
- ❌ Avoid for component-specific state (use
useStateinstead)
Registers a route with its component.
import { addRoute } from "./framework/main.js";
addRoute("/", HomePage);
addRoute("/about", AboutPage);
addRoute("/users", UsersPage);Parameters:
path- URL path (without #)component- Function that returns jsx elements
Navigation:
// In HTML
jsx("a", { href: "#/" }, "Home");
jsx("a", { href: "#/about" }, "About");
// In JavaScript
window.location.hash = "#/about";Example:
import { jsx, addRoute } from "./framework/main.js";
function App() {
return jsx(
"div",
null,
jsx(
"nav",
null,
jsx("a", { href: "#/" }, "Home"),
jsx("a", { href: "#/about" }, "About"),
jsx("a", { href: "#/contact" }, "Contact")
)
);
}
function HomePage() {
return jsx("h1", null, "Welcome Home");
}
function AboutPage() {
return jsx("h1", null, "About Us");
}
function ContactPage() {
return jsx("h1", null, "Contact Us");
}
addRoute("/", HomePage);
addRoute("/about", AboutPage);
addRoute("/contact", ContactPage);404 Not Found Page:
The framework includes a default 404 page that displays when users navigate to unregistered routes. If you want to customize it, edit the notFound() function in framework/core/notfound.js:
// framework/core/notfound.js
import { jsx } from "./jsx.js";
export function notFound() {
return jsx(
"div",
{ style: { padding: "20px", textAlign: "center" } },
jsx("h1", null, "404 - Page Not Found"),
jsx("p", null, "The page you are looking for does not exist."),
jsx("a", { href: "#/" }, "Go back home")
);
}You can customize the styling, content, and add additional functionality like logging or redirects.
Renders the root component.
import { jsx, render } from "./framework/main.js";
import { render as renderApp } from "./framework/core/render.js";
function App() {
return jsx("h1", null, "Hello World");
}
renderApp(App);Note: After the initial render, render() is called automatically by setState.
import { jsx, useState, useEffect } from "./framework/main.js";
import { render } from "./framework/core/render.js";
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const [history, setHistory] = useState([]);
// Update document title
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// Track history
useEffect(() => {
setHistory([...history, count]);
}, [count]);
const increment = () => {
setCount(count + step);
};
const decrement = () => {
setCount(count - step);
};
const reset = () => {
setCount(0);
setHistory([]);
};
const handleStepChange = (e) => {
setStep(Number(e.target.value));
};
return jsx(
"div",
{ className: "counter" },
jsx("h1", null, "Count: ", count),
jsx(
"div",
null,
jsx("label", null, "Step: "),
jsx("input", {
type: "number",
value: step,
onChange: handleStepChange,
})
),
jsx(
"div",
{ className: "buttons" },
jsx("button", { onClick: decrement }, "-", step),
jsx("button", { onClick: reset }, "Reset"),
jsx("button", { onClick: increment }, "+", step)
),
jsx(
"div",
{ className: "history" },
jsx("h3", null, "History:"),
jsx(
"ul",
null,
...history.map((value, index) => jsx("li", { key: index }, value))
)
)
);
}
render(Counter);import { jsx, useState } from "./framework/main.js";
import { render } from "./framework/core/render.js";
function TodoApp() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState("");
const [filter, setFilter] = useState("all"); // all, active, completed
const addTodo = (e) => {
e.preventDefault();
if (inputValue.trim()) {
const newTodo = {
id: Date.now(),
text: inputValue,
completed: false,
};
setTodos([...todos, newTodo]);
setInputValue("");
}
};
const toggleTodo = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const deleteTodo = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const filteredTodos = todos.filter((todo) => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true;
});
return jsx(
"div",
{ className: "todo-app" },
jsx("h1", null, "Todo List"),
jsx(
"form",
{ onSubmit: addTodo },
jsx("input", {
type: "text",
placeholder: "What needs to be done?",
value: inputValue,
onChange: (e) => setInputValue(e.target.value),
}),
jsx("button", { type: "submit" }, "Add")
),
jsx(
"div",
{ className: "filters" },
jsx(
"button",
{
className: filter === "all" ? "active" : "",
onClick: () => setFilter("all"),
},
"All (",
todos.length,
")"
),
jsx(
"button",
{
className: filter === "active" ? "active" : "",
onClick: () => setFilter("active"),
},
"Active (",
todos.filter((t) => !t.completed).length,
")"
),
jsx(
"button",
{
className: filter === "completed" ? "active" : "",
onClick: () => setFilter("completed"),
},
"Completed (",
todos.filter((t) => t.completed).length,
")"
)
),
jsx(
"ul",
{ className: "todo-list" },
...filteredTodos.map((todo) =>
jsx(
"li",
{
key: todo.id,
className: todo.completed ? "completed" : "",
},
jsx("input", {
type: "checkbox",
checked: todo.completed,
onChange: () => toggleTodo(todo.id),
}),
jsx("span", null, todo.text),
jsx("button", { onClick: () => deleteTodo(todo.id) }, "Delete")
)
)
),
todos.length === 0 &&
jsx("p", { className: "empty" }, "No todos yet. Add one above!")
);
}
render(TodoApp);import { jsx, useState, useEffect, addRoute } from "./framework/main.js";
// Navigation Component
function Navigation() {
return jsx(
"nav",
{ className: "navbar" },
jsx("a", { href: "#/" }, "Home"),
jsx("a", { href: "#/about" }, "About"),
jsx("a", { href: "#/users" }, "Users"),
jsx("a", { href: "#/contact" }, "Contact")
);
}
// Home Page
function HomePage() {
return jsx(
"div",
null,
Navigation(),
jsx(
"main",
null,
jsx("h1", null, "Welcome Home"),
jsx("p", null, "This is the home page of our mini framework demo.")
)
);
}
// About Page
function AboutPage() {
const features = [
"Virtual DOM",
"Hooks (useState, useEffect)",
"Key-based reconciliation",
"Client-side routing",
];
return jsx(
"div",
null,
Navigation(),
jsx(
"main",
null,
jsx("h1", null, "About Us"),
jsx("p", null, "We built a lightweight React-like framework!"),
jsx(
"ul",
null,
...features.map((feature) => jsx("li", { key: feature }, feature))
)
)
);
}
// Users Page with Data Fetching
function UsersPage() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Simulate API call
setTimeout(() => {
setUsers([
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
{ id: 3, name: "Charlie", email: "charlie@example.com" },
]);
setLoading(false);
}, 1000);
}, []);
return jsx(
"div",
null,
Navigation(),
jsx(
"main",
null,
jsx("h1", null, "Users"),
loading
? jsx("p", null, "Loading users...")
: jsx(
"ul",
{ className: "user-list" },
...users.map((user) =>
jsx(
"li",
{ key: user.id },
jsx("strong", null, user.name),
jsx("span", null, user.email)
)
)
)
)
);
}
// Contact Page with Form
function ContactPage() {
const [formData, setFormData] = useState({
name: "",
email: "",
message: "",
});
const [submitted, setSubmitted] = useState(false);
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Form submitted:", formData);
setSubmitted(true);
setTimeout(() => {
setSubmitted(false);
setFormData({ name: "", email: "", message: "" });
}, 3000);
};
return jsx(
"div",
null,
Navigation(),
jsx(
"main",
null,
jsx("h1", null, "Contact Us"),
submitted
? jsx(
"p",
{ className: "success" },
"Thank you! We'll be in touch soon."
)
: jsx(
"form",
{ onSubmit: handleSubmit },
jsx(
"div",
null,
jsx("label", null, "Name:"),
jsx("input", {
type: "text",
name: "name",
value: formData.name,
onChange: handleChange,
required: true,
})
),
jsx(
"div",
null,
jsx("label", null, "Email:"),
jsx("input", {
type: "email",
name: "email",
value: formData.email,
onChange: handleChange,
required: true,
})
),
jsx(
"div",
null,
jsx("label", null, "Message:"),
jsx("textarea", {
name: "message",
value: formData.message,
onChange: handleChange,
required: true,
})
),
jsx("button", { type: "submit" }, "Send Message")
)
)
);
}
// Register routes
addRoute("/", HomePage);
addRoute("/about", AboutPage);
addRoute("/users", UsersPage);
addRoute("/contact", ContactPage);import { jsx, useState } from "./framework/main.js";
import { render } from "./framework/core/render.js";
// Button Component
function Button({ children, onClick, variant = "primary" }) {
return jsx(
"button",
{
className: `btn btn-${variant}`,
onClick: onClick,
},
children
);
}
// Card Component
function Card({ title, children }) {
return jsx(
"div",
{ className: "card" },
jsx("div", { className: "card-header" }, jsx("h3", null, title)),
jsx("div", { className: "card-body" }, children)
);
}
// Modal Component
function Modal({ isOpen, onClose, title, children }) {
if (!isOpen) return null;
return jsx(
"div",
{
className: "modal-overlay",
onClick: onClose,
},
jsx(
"div",
{
className: "modal-content",
onClick: (e) => e.stopPropagation(),
},
jsx(
"div",
{ className: "modal-header" },
jsx("h2", null, title),
jsx("button", { onClick: onClose }, "×")
),
jsx("div", { className: "modal-body" }, children)
)
);
}
// Using the components
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return jsx(
"div",
{ className: "app" },
Card({
title: "Welcome",
children: [
jsx("p", { key: "p" }, "This is a reusable card component."),
Button({
key: "btn",
onClick: () => setIsModalOpen(true),
children: "Open Modal",
}),
],
}),
Modal({
isOpen: isModalOpen,
onClose: () => setIsModalOpen(false),
title: "Modal Title",
children: [
jsx("p", { key: "p" }, "This is modal content!"),
Button({
key: "btn",
variant: "secondary",
onClick: () => setIsModalOpen(false),
children: "Close",
}),
],
})
);
}
render(App);// ❌ Bad: No keys
...items.map(item => jsx('li', null, item.name))
// ✅ Good: With keys
...items.map(item => jsx('li', { key: item.id }, item.name))// ❌ Bad: One large component
function BigComponent() {
return jsx(
"div",
null
// 200 lines of nested jsx() calls...
);
}
// ✅ Good: Split into smaller components
function Header() {
/* ... */
}
function Content() {
/* ... */
}
function Footer() {
/* ... */
}
function App() {
return jsx("div", null, Header(), Content(), Footer());
}// ❌ Bad: Direct reference (can cause stale state issues)
setCount(count + 1);
// ✅ Good: Functional update
setCount((prev) => prev + 1);// ❌ Bad: Missing dependencies
useEffect(() => {
console.log(count);
}, []); // count is used but not in dependencies
// ✅ Good: All dependencies listed
useEffect(() => {
console.log(count);
}, [count]);// ✅ Good: Clear intent
jsx("div", null, "Content");
// ❌ Unnecessary: Empty object
jsx("div", {}, "Content");- Use keys for lists - Enables efficient reconciliation
- Keep state close to where it's used - Reduces re-render scope
- Define handlers outside jsx() calls - Avoid creating new functions on every render
- Minimize effect dependencies - Only include what's necessary
- Use spread operator efficiently - Flatten arrays with
...when mapping
- No JSX syntax support (must use
jsx()function directly) - No component lifecycle methods (use
useEffectinstead) - No context API (use prop drilling or external state management)
- No ref support (direct DOM access not recommended)
- No server-side rendering
- No concurrent mode or suspense
- Hash-based routing only (no history API routing)
- No Build Step - Run directly in the browser
- Better Understanding - See exactly how virtual DOM is created
- Transparency - No "magic" happening behind the scenes
- Simplicity - No Babel, webpack, or configuration needed
- Learning Tool - Understand how JSX actually works
- Pure JavaScript - Standard ES modules only
This is a learning project demonstrating core concepts of modern JavaScript frameworks. Feel free to extend it with additional features!
MIT License - Use freely for learning and projects.