Welcome to the official documentation for eleva.js, a minimalist, lightweight, pure vanilla JavaScript frontend runtime framework. Whether you’re new to JavaScript or an experienced developer, this guide will help you understand Eleva’s core concepts, architecture, and how to integrate and extend it in your projects.
Beta Release Notice: This documentation is for eleva.js v1.2.14-beta. The core functionality is stable and suitable for production use. While we’re still gathering feedback before the final v1.0.0 release, the framework has reached a significant milestone in its development. Please be aware of the known limitations and help us improve Eleva by sharing your feedback and experiences.
Eleva is designed to offer a simple yet powerful way to build frontend applications using pure vanilla JavaScript. Its goal is to empower developers who value simplicity, performance, and full control over their application to build modular and high-performance apps without the overhead of larger frameworks.
Eleva is unopinionated. Unlike many frameworks that enforce a specific project structure or coding paradigm, Eleva provides only the minimal core with a flexible plugin system, leaving architectural decisions in your hands. This means:
At the heart of Eleva are a few fundamental principles that guide its design and usage:
Minimalism:
Eleva includes only the essential features needed for building functional, high-performance applications without added complexity.
Reactivity:
With its signal-based reactivity, Eleva updates only the parts of the UI that change, ensuring smooth and efficient DOM updates.
Simplicity:
Built using pure vanilla JavaScript, Eleva offers a shallow learning curve and seamless integration with existing projects.
Modularity:
Each component is self-contained, making your application scalable and maintainable.
Flexibility:
Eleva’s unopinionated nature allows you to choose your own architectural patterns and extend the framework with plugins as needed.
Performance:
Designed to be lightweight and efficient, Eleva is ideal for performance-critical applications.
Preliminary benchmarks illustrate Eleva’s efficiency compared to popular frameworks:
Framework | Bundle Size (KB) | Initial Load Time (ms) | DOM Update Speed (s) | Peak Memory Usage (KB) | Overall Performance Score (lower is better) |
---|---|---|---|---|---|
Eleva (Direct DOM) | 2 | 0.05 | 0.002 | 0.25 | 0.58 (Best) |
React (Virtual DOM) | 4.1 | 5.34 | 0.020 | 0.25 | 9.71 |
Vue (Reactive State) | 45 | 4.72 | 0.021 | 3.10 | 13.21 |
Angular (Two-way Binding) | 62 | 5.26 | 0.021 | 0.25 | 16.88 (Slowest) |
Detailed Benchmark Metrics Report
⚠️ Disclaimer: Benchmarks are based on internal tests and may vary by project and environment.
Install via npm:
npm install eleva
Or include via CDN:
<!-- jsDelivr (Recommended) -->
<script src="https://cdn.jsdelivr.net/npm/eleva"></script>
or
<!-- unpkg -->
<script src="https://unpkg.com/eleva"></script>
Below is a step-by-step tutorial to help you get started. This example demonstrates component registration, state creation, and mounting using a DOM element (not a selector), with asynchronous handling.
import Eleva from "eleva";
const app = new Eleva("MyApp");
// Define a simple component
app.component("HelloWorld", {
// Optional setup: if omitted, Eleva defaults to an empty state
setup({ signal }) {
const count = signal(0);
return { count };
},
template: (ctx) => `
<div>
<h1>Hello, Eleva! 👋</h1>
<p>Count: ${ctx.count.value}</p>
<button @click="() => count.value++">Increment</button>
</div>
`,
});
// Mount the component by providing a DOM element and handling the returned Promise
app
.mount(document.getElementById("app"), "HelloWorld")
.then((instance) => console.log("Component mounted:", instance));
For interactive demos, check out the CodePen Example.
The TemplateEngine is responsible for parsing templates and evaluating embedded expressions.
TemplateEngine.parse(template, data)
: Replaces `` with values from data
.TemplateEngine.evaluate(expr, data)
: Safely evaluates JavaScript expressions within the provided context.Example:
const template = "Hello, !";
const data = { name: "World" };
const output = TemplateEngine.parse(template, data);
console.log(output); // "Hello, World!"
Key Features:
Eleva supports two methods for dynamic content:
${...}
):Example:
const greeting = `Hello, ${name}!`; // Evaluates to "Hello, World!" if name is "World"
<p>Hello, !</p>
When to Use Each:
${...}
for one-time, static content.Understanding how data flows during component initialization and event handling is key:
setup
function during initialization.signal
function), component props, emitter, and lifecycle hooks. The returned data forms the component’s reactive state.Example:
const MyComponent = {
setup: ({ signal }) => {
const counter = signal(0);
return { counter };
},
template: (ctx) => `
<div>
<p>Counter: ${ctx.counter.value}</p>
</div>
`,
};
setup
along with event-specific data (like event.target
).Example:
const MyComponent = {
setup: ({ signal }) => {
const counter = signal(0);
function increment(event) {
console.log("Event type:", event.type);
counter.value++;
}
return { counter, increment };
},
template: (ctx) => `
<div>
<p>Counter: ${ctx.counter.value}</p>
<button @click="increment">Increment</button>
</div>
`,
};
The Signal provides fine-grained reactivity by updating only the affected DOM parts.
new Signal(initialValue)
: Creates a Signal instance..value
: Getter/setter for the current value..watch(callback)
: Registers a function to execute on changes.Example:
const count = new Signal(0);
count.watch((newVal) => console.log("Count updated:", newVal));
count.value = 1; // Logs: "Count updated: 1"
Key Features:
The Emitter enables inter-component communication through events and using a publish–subscribe pattern.
new Emitter()
: Creates an Emitter instance..on(event, handler)
: Registers an event handler..off(event, handler)
: Removes an event handler..emit(event, ...args)
: Emits an event with optional arguments.Example:
const emitter = new Emitter();
emitter.on("greet", (name) => console.log(`Hello, ${name}!`)); // Logs: "Hello, Alice!"
emitter.emit("greet", "Alice");
Key Features:
The Renderer efficiently updates the DOM through direct manipulation, avoiding the overhead of virtual DOM implementations. It uses a performant diffing algorithm to update only the necessary parts of the DOM tree.
new Renderer()
: Creates a Renderer instance..patchDOM(container, newHtml)
: Updates container content with the new HTML.Example:
const renderer = new Renderer();
const container = document.getElementById('app');
const newHtml = '<div>Updated content</div>';
renderer.patchDOM(container, newHtml); // Update a container with new HTML
Key Features:
The Eleva class orchestrates component registration, mounting, plugin integration, lifecycle management, and events.
new Eleva(name, config)
: Creates an instance.use(plugin, options)
: Integrates a plugin.component(name, definition)
: Registers a new component.mount(container, compName, props)
: Mounts a component to a DOM element (returns a Promise).Eleva provides a set of lifecycle hooks that allow you to execute code at specific stages of a component’s lifecycle. These hooks are available through the setup method’s return object.
Available Hooks:
onBeforeMount
: Called before the component is mounted to the DOMonMount
: Called after the component is mounted to the DOMonBeforeUpdate
: Called before the component updatesonUpdate
: Called after the component updatesonUnmount
: Called before the component is unmounted from the DOMExample:
app.component("MyComponent", {
setup() {
// Define your lifecycle hooks
const hooks = {
beforeMount: () => {
console.log("Component will mount");
},
mounted: () => {
console.log("Component mounted");
},
beforeUpdate: () => {
console.log("Component will update");
},
updated: () => {
console.log("Component updated");
},
beforeUnmount: () => {
console.log("Component will unmount");
}
};
// Return both your component state and lifecycle hooks
return {
// Your component state
count: 0,
// Lifecycle hooks
onBeforeMount: hooks.beforeMount,
onMount: hooks.mounted,
onBeforeUpdate: hooks.beforeUpdate,
onUpdate: hooks.updated,
onUnmount: hooks.beforeUnmount
};
},
template(ctx) {
return `<div>Count: ${ctx.count}</div>`;
}
});
Important Notes:
Example (with Reactive State):
app.component("Counter", {
setup({ signal }) {
const count = signal(0);
return {
count,
onMount: () => {
console.log("Counter mounted with initial value:", count.value);
},
onUpdate: () => {
console.log("Counter updated to:", count.value);
}
};
},
template(ctx) {
return `
<div>
<p>Count: ${ctx.count.value}</p>
<button @click="() => count.value++">Increment</button>
</div>
`;
}
});
Register components globally or directly, then mount using a DOM element.
Example (Global Registration):
const app = new Eleva("MyApp");
app.component("HelloWorld", {
setup({ signal }) {
const count = signal(0);
return { count };
},
template: (ctx) => `
<div>
<h1>Hello, Eleva! 👋</h1>
<p>Count: ${ctx.count.value}</p>
<button @click="() => count.value++">Increment</button>
</div>
`,
});
app.mount(document.getElementById("app"), "HelloWorld").then((instance) => {
console.log("Component mounted:", instance);
});
Example (Direct Component Definition):
const DirectComponent = {
template: () => `<div>No setup needed!</div>`,
};
const app = new Eleva("MyApp");
app
.mount(document.getElementById("app"), DirectComponent)
.then((instance) => console.log("Mounted Direct:", instance));
Eleva provides two powerful ways to mount child components in your application:
:
.Example:
// Child Component
app.component("TodoItem", {
setup: (context) => {
const { title, completed, onToggle } = context.props;
return { title, completed, onToggle };
},
template: (ctx) => `
<div class="todo-item ${ctx.completed ? 'completed' : ''}">
<input type="checkbox"
${ctx.completed ? 'checked' : ''}
@click="onToggle" />
<span>${ctx.title}</span>
</div>
`
});
// Parent Component using explicit mounting
app.component("TodoList", {
setup: ({ signal }) => {
const todos = signal([
{ id: 1, title: "Learn Eleva", completed: false },
{ id: 2, title: "Build an app", completed: false }
]);
const toggleTodo = (id) => {
todos.value = todos.value.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
};
return { todos, toggleTodo };
},
template: (ctx) => `
<div class="todo-list">
<h2>My Todo List</h2>
${ctx.todos.value.map(todo => `
<div class="todo-item"
:title="${todo.title}"
:completed="${todo.completed}"
:onToggle="() => toggleTodo(${todo.id})">
</div>
`).join('')}
</div>
`,
children: {
".todo-item": "TodoItem" // Explicitly define child component
},
});
Example:
// Child Component
app.component("UserCard", {
setup: (context) => {
const { user, onSelect } = context.props;
return { user, onSelect };
},
template: (ctx) => `
<div class="user-card" @click="onSelect">
<img src="${ctx.user.avatar}" alt="${ctx.user.name}" />
<h3>${ctx.user.name}</h3>
<p>${ctx.user.role}</p>
</div>
`
});
// Parent Component using explicit mounting
app.component("UserList", {
setup: ({ signal }) => {
const users = signal([
{ id: 1, name: "John Doe", role: "Developer", avatar: "john.jpg" },
{ id: 2, name: "Jane Smith", role: "Designer", avatar: "jane.jpg" }
]);
const selectUser = (user) => {
console.log("Selected user:", user);
};
return { users, selectUser };
},
template: (ctx) => `
<div class="user-list">
<h2>Team Members</h2>
${ctx.users.value.map(user => `
<div id="user-card-container"></div>
`).join('')}
</div>
`,
children: {
'#user-card-container': {
setup: (context) => {
const user = context.props.user;
return { user };
},
template: (ctx) => `
<UserCard
:user='${JSON.stringify(ctx.user)}'
:onSelect="() => selectUser(${JSON.stringify(ctx.user)})"
></UserCard>
`,
children: {
"UserCard": "UserCard"
}
}
}
});
Eleva supports four main approaches to mounting child components, each with its own use cases and benefits:
children: {
"UserCard": "UserCard" // Direct mounting without container
}
children: {
"#container": "UserCard" // Mounting in a container element
}
children: {
".dynamic-container": {
setup: (ctx) => ({ /* dynamic setup */ }),
template: (ctx) => `<UserCard :props="${ctx.props}" />`,
children: { "UserCard": "UserCard" }
}
}
// Define component
const UserCard = {
setup: (ctx) => ({ /* setup logic */ }),
template: (ctx) => `<div>User Card</div>`
};
// Parent component using variable-based mounting
app.component("UserList", {
template: (ctx) => `
<div class="user-list">
<div class="user-card-container"></div>
</div>
`,
children: {
".user-card-container": UserCard // Mount component directly from variable
}
});
Best Practices for Component Mounting:
Eleva supports various selector types for defining child components in the children
configuration:
children: {
"UserCard": "UserCard" // Mounts UserCard component directly
}
children: {
"#user-card-container": "UserCard" // Mounts in element with id="user-card-container"
}
children: {
".todo-item": "TodoItem" // Mounts in elements with class="todo-item"
}
children: {
"[data-component='user-card']": "UserCard" // Mounts in elements with data-component="user-card"
}
Best Practices for Selector Types:
Performance Considerations:
Key Benefits of Component Mounting:
Eleva supports component-scoped styling through an optional style
function defined in a component.
The styles are injected into the component’s container to avoid global leakage.
Example:
const MyComponent = {
style: (ctx) => `
.my-component {
color: blue;
padding: rem;
}
`,
template: (ctx) => `<div class="my-component">Styled Component</div>`,
};
Inter-component communication is facilitated by the built-in Emitter. Components can publish and subscribe to events, enabling decoupled interactions.
Example:
// Component A emits an event
app.component("ComponentA", {
setup: ({ emitter }) => {
function sendMessage() {
emitter.emit("customEvent", "Hello from A");
}
return { sendMessage };
},
template: () => `<button @click="sendMessage">Send Message</button>`,
});
// Component B listens for the event
app.component("ComponentB", {
setup: ({ emitter }) => {
emitter.on("customEvent", (msg) => console.log(msg));
return {};
},
template: () => `<div>Component B</div>`,
});
app.mount(document.getElementById("app"), "ComponentA");
app.mount(document.getElementById("app"), "ComponentB");
Eleva’s design emphasizes clarity, modularity, and performance. This section explains how data flows through the framework and how its key components interact, providing more clarity on the underlying mechanics.
Component Definition:
Components are plain JavaScript objects that describe a UI segment. They typically include:
template
function that returns HTML with interpolation placeholders.setup()
function for initializing state (using reactive signals).style
function for scoped CSS.children
object for nested components.Signals (Reactivity): Signals are reactive data holders that notify watchers when their values change, triggering re-renders of the affected UI.
TemplateEngine (Rendering):
This module processes template strings by replacing placeholders (e.g., 8
) with live data, enabling dynamic rendering.
Renderer (DOM Diffing and Patching): The Renderer compares the new virtual DOM with the current DOM and patches only the parts that have changed, ensuring high performance and efficient updates.
Emitter (Event Handling): The Emitter implements a publish–subscribe pattern to allow components to communicate by emitting and listening to custom events.
Initialization:
app.component()
.app.mount()
creates a context (including props, lifecycle hooks, and an emitter
property) and executes setup()
(if present) to create a reactive state.Rendering:
8
with the current values.Reactivity:
Events:
@click
) during rendering.[Component Registration]
│
▼
[Mounting & Context Creation]
│
▼
[setup() Execution]
│
▼
[Template Function Produces HTML]
│
▼
[TemplateEngine Processes HTML]
│
▼
[Renderer Patches the DOM] ◂────────┐
│ │
▼ │
[User Interaction / Signal Change] │
│ │
▼ │ ↺
[Signal Watchers Trigger Re-render] │
│ │
▼ │
[Renderer Diffs the DOM] ─────────┘
The Plugin System in Eleva provides a powerful way to extend the framework’s functionality. Plugins can add new features, modify existing behavior, or integrate with external libraries.
A plugin in Eleva is an object that must have two required properties:
const MyPlugin = {
name: 'myPlugin', // Unique identifier for the plugin
install(eleva, options) {
// Plugin installation logic
}
};
name
: A unique string identifier for the plugininstall
: A function that receives the Eleva instance and optional configurationPlugins are installed using the use
method on an Eleva instance:
const app = new Eleva('myApp');
app.use(MyPlugin, { /* optional configuration */ });
The use
method:
install
function with the Eleva instance and provided optionsPlugins can:
install(eleva) {
eleva.newMethod = () => { /* ... */ };
}
install(eleva) {
eleva.component('enhanced-component', {
template: (ctx) => `...`,
setup: (ctx) => ({ /* ... */ })
});
}
install(eleva) {
const originalMount = eleva.mount;
eleva.mount = function(container, compName, props) {
// Add pre-mount logic
const result = originalMount.call(this, container, compName, props);
// Add post-mount logic
return result;
};
}
install(eleva) {
eleva.services = {
api: new ApiService(),
storage: new StorageService()
};
}
eleva-{plugin-name}
for published pluginsHere’s a complete example of a custom plugin:
const LoggerPlugin = {
name: 'logger',
install(eleva, options = {}) {
const { level = 'info' } = options;
// Add logging methods to Eleva instance
eleva.log = {
info: (msg) => console.log(`[INFO] ${msg}`),
warn: (msg) => console.warn(`[WARN] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`)
};
// Enhance component mounting with logging
const originalMount = eleva.mount;
eleva.mount = async function(container, compName, props) {
eleva.log.info(`Mounting component: ${compName}`);
const result = await originalMount.call(this, container, compName, props);
eleva.log.info(`Component mounted: ${compName}`);
return result;
};
}
};
// Usage
const app = new Eleva('myApp');
app.use(LoggerPlugin, { level: 'debug' });
install
function is called with the instance and optionsEleva provides TypeScript declarations for plugin development:
interface ElevaPlugin {
name: string;
install(eleva: Eleva, options?: Record<string, any>): void;
}
This ensures type safety when developing plugins in TypeScript.
console.log
in lifecycle hooks and event handlers.Explore these guides for real-world examples:
Interactive demos are also available on Eleva’s CodePen Collection for you to experiment live.
Q: Is Eleva production-ready? A: Eleva is currently in beta (v1.2.14-beta). While it’s stable and suitable for production use, we’re still gathering feedback before the final v1.0.0 release.
Q: How do I report issues or request features? A: Please use the GitHub Issues page.
Q: Can I use Eleva with TypeScript? A: Absolutely! Eleva includes built-in TypeScript declarations to help keep your codebase strongly typed.
Common Issues:
mount()
.Detailed API documentation with parameter descriptions, return values, and usage examples can be found in the docs folder.
TemplateEngine:
parse(template, data)
and evaluate(expr, data)
Signal:
new Signal(value)
, getter/setter for signal.value
, and signal.watch(fn)
Emitter:
Methods: on(event, handler)
, off(event, handler)
, and emit(event, ...args)
Renderer:
Methods: patchDOM(container, newHtml)
, diff(oldParent, newParent)
, and updateAttributes(oldEl, newEl)
Eleva (Core):
new Eleva(name, config)
, use(plugin, options)
, component(name, definition)
, and mount(container, compName, props)
Contributions are welcome! Whether you’re fixing bugs, adding features, or improving documentation, your input is invaluable. Please checkout the CONTRIBUTING file for detailed guidelines on how to get started.
Join our community for support, discussions, and collaboration:
For a detailed log of all changes and updates, please refer to the Changelog.
Eleva is open-source and available under the MIT License.
Thank you for exploring Eleva! I hope this documentation helps you build amazing, high-performance frontend applications using pure vanilla JavaScript. For further information, interactive demos, and community support, please visit the GitHub Discussions page.