eleva

Eleva.js - Pure JavaScript, Pure Performance

License GitHub package.json version Version 100% Javascript Zero Dependencies Minified Size Gzipped Size



Eleva Logo

Eleva - A minimalist, pure vanilla javascript frontend framework. | Product Hunt




Welcome to the official documentation for Eleva, 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.


Table of Contents


1. Introduction

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.


2. Design Philosophy

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:


3. Core Principles

At the heart of Eleva are a few fundamental principles that guide its design and usage:


4. Performance Benchmarks

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) 1.8 10 0.018 0.25 3.02 (Best)
React (Virtual DOM) 42 40 0.020 0.25 20.57
Vue (Reactive State) 33 35 0.021 3.10 17.78
Angular (Two-way Binding) 80 100 0.021 0.25 45.07 (Slowest)

Detailed Benchmark Metrics Report

⚠️ Disclaimer: Benchmarks are based on internal tests and may vary by project and environment.


5. Getting Started

Installation

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/dist/eleva.min.js"></script>

Quick Start Tutorial

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.


6. Core Concepts

TemplateEngine

The TemplateEngine is responsible for parsing templates and evaluating embedded expressions.

Example:

const template = "Hello, !";
const data = { name: "World" };
const output = TemplateEngine.parse(template, data);
console.log(output); // "Hello, World!"

Template Interpolation

Eleva supports two methods for dynamic content:

  1. Native Template Literals (${...}):
    Evaluated once, providing static content.

Example:

const greeting = `Hello, ${name}!`; // Evaluates to "Hello, World!" if name is "World"
  1. Handlebars-like Syntax (``):
    Enables dynamic, reactive updates.
<p>Hello, !</p>

When to Use Each:

Setup Context vs. Event Context

Understanding how data flows during component initialization and event handling is key:

Setup Context

Example:

const MyComponent = {
  setup: ({ signal }) => {
    const counter = signal(0);
    return { counter };
  },
  template: (ctx) => `
    <div>
      <p>Counter: ${ctx.counter.value}</p>
    </div>
  `,
};

Event Context

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>
  `,
};

Signal (Reactivity)

The Signal provides fine-grained reactivity by updating only the affected DOM parts.

Example:

const count = new Signal(0);
count.watch((newVal) => console.log("Count updated:", newVal));
count.value = 1; // Logs: "Count updated: 1"

Emitter (Event Handling)

The Emitter enables inter-component communication through events and using a publish–subscribe pattern.

Example:

on("greet", (name) => console.log(`Hello, ${name}!`)); // Logs: "Hello, Alice!"
emit("greet", "Alice");

Renderer (DOM Diffing)

The Renderer efficiently updates the DOM through diffing and patching.

Eleva (Core)

The Eleva class orchestrates component registration, mounting, plugin integration, lifecycle management, and events.

Lifecycle Hooks

Execute code at various stages of a component’s lifecycle:

Example:

const MyComponent = {
  setup: ({ signal, onMount, onUnmount }) => {
    const counter = signal(0);
    onMount(() => console.log("Component mounted"));
    onUnmount(() => console.log("Component unmounted"));
    return { counter };
  },
  template: (ctx) => `<div>Counter: ${ctx.counter}</div>`,
};

Component Registration & Mounting

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));

Children Components & Passing Props

Eleva allows you to nest components. A parent can include child components in its template and pass data to them via props. Attributes prefixed with eleva-prop- are automatically parsed as props.

Example:

// Define the Child Component
const ChildComponent = {
  setup: (context) => {
    // Access props from the context
    const { title } = context.props;
    return { title };
  },
  template: (ctx) => `
    <div>
      <p>Child Component Title: ${ctx.title}</p>
    </div>
  `,
};

// Define the Parent Component with local child registration
const ParentComponent = {
  template: () => `
    <div>
      <h1>Parent Component</h1>
      <child-comp eleva-prop-title="Hello from Parent"></child-comp>
    </div>
  `,
  children: {
    "child-comp": ChildComponent,
  },
};

const app = new Eleva("TestApp");
app.component("parent-comp", ParentComponent);
app.mount(document.getElementById("app"), "parent-comp");

Style Injection & Scoped CSS

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

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");

7. Architecture & Data Flow

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.

Key Components

  1. Component Definition:
    Components are plain JavaScript objects that describe a UI segment. They typically include:

    • A template function that returns HTML with interpolation placeholders.
    • An optional setup() function for initializing state (using reactive signals).
    • An optional style function for scoped CSS.
    • An optional children object for nested components.
  2. Signals (Reactivity): Signals are reactive data holders that notify watchers when their values change, triggering re-renders of the affected UI.

  3. TemplateEngine (Rendering): This module processes template strings by replacing placeholders (e.g., 8) with live data, enabling dynamic rendering.

  4. 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.

  5. Emitter (Event Handling): The Emitter implements a publish–subscribe pattern to allow components to communicate by emitting and listening to custom events.

Data Flow Process

  1. Initialization:

    • Registration: Components are registered via app.component().
    • Mounting: app.mount() creates a context (including props, lifecycle hooks, and an emitter property) and executes setup() (if present) to create a reactive state.
  2. Rendering:

    • The template function is called with the combined context and reactive state.
    • The TemplateEngine parses the template, replacing expressions like 8 with the current values.
    • The Renderer takes the resulting HTML and patches it into the DOM, ensuring only changes are applied.
  3. Reactivity:

    • When a signal’s value changes (e.g., through a user event), its watcher triggers a re-run of the template.
    • The Renderer diffs the new HTML against the current DOM and applies only the necessary changes.
  4. Events:

    • Eleva binds event listeners (e.g., @click) during rendering.
    • When an event occurs, the handler is executed with the current state and event details.
    • Components can also emit custom events via the Emitter for cross-component communication.

Visual Overview

[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]   ─────────┘

Benefits


8. Plugin System

Creating a Plugin

A plugin is an object with an install method:

const MyPlugin = {
  install(eleva, options) {
    // Extend Eleva functionality here
    eleva.myPluginFeature = function () {
      // Plugin logic
    };
  },
};

export default MyPlugin;

Plugin Architecture

Plugins can:

This modular approach makes Eleva highly customizable.


9. Debugging & Developer Tools


10. Best Practices & Use Cases

Best Practices

Use Cases


11. Examples and Tutorials

Explore these guides for real-world examples:

Interactive demos are also available on Eleva’s CodePen Collection for you to experiment live.


12. FAQ

Q: Is Eleva production-ready? A: Not yet—Eleva is currently in alpha. While it’s a powerful tool, expect changes until a stable release is announced.

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.


13. Troubleshooting & Migration Guidelines

Troubleshooting

Migration Guidelines


14. API Reference

Detailed API documentation with parameter descriptions, return values, and usage examples can be found in the docs folder.

Note: In v1.2.0-alpha, the mounting context now includes an emitter property (instead of separate emit and on functions). Use context.emitter.on(...) and context.emitter.emit(...) for event handling.


15. Contributing

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.


16. Community & Support

Join our community for support, discussions, and collaboration:


17. Changelog

For a detailed log of all changes and updates, please refer to the Changelog.

Latest Update (v1.2.2-alpha):


18. License

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.