Skip to content

Minimum-Viable-Web/minima-js

Repository files navigation

MinimaJS

A small JavaScript library for building interactive web pages: write less code, add no extra libraries, and get pages that update smoothly when data changes. Lightweight, short syntax, zero dependencies, and friendly for AI-assisted development.

OpenSSF Best Practices Package Status Build Status GitHub Package License: MIT Zero Dependencies

⚠️ EXPERIMENTAL – This project is pending testing in production senerios. Use at own risk.


Looking for a tiny web development ecosystem? See: Minimum Viable Web


Why MinimaJS?

  • Short API syntax - Build UIs faster with ultra-concise code
  • Lightweight & modular - Use only what you need
  • Zero dependencies - No supply chain vulnerabilities
  • LLM-optimized - AI-friendly APIs for faster development
  • Modern features - Hooks, SSR, TypeScript, templating
  • XSS-safe - Built-in security by default

Framework Comparison

Feature MinimaJS React Vue 3 Preact Svelte
Virtual DOM
Hooks
SSR
TypeScript
useMemo/useCallback
Concurrent Features
Error Boundaries
Suspense
Zero Dependencies
Template Literals
Built-in XSS Protection
LLM-Optimized API
Advanced DevTools

Bundle Size Comparison

Framework Core Full Minified Notes
MinimaJS 15KB 53KB 10KB core, 37KB full All features in single file
React + ReactDOM 130KB 130KB 42KB (+deps) Requires ecosystem
Vue 3 120KB 120KB 34KB (+deps) Requires ecosystem
Svelte 1KB 1KB 1KB (compiled) Compile-time only
Preact 10KB 10KB 3KB (+deps) React-compatible only

MinimaJS includes React 18+ features while being smaller than React. The combined file provides all features in a single import.

Developer Experience

Aspect MinimaJS React Vue 3 Svelte
Learning Curve Minimal Moderate Moderate Steep
Boilerplate Ultra-minimal High Moderate Low
AI Code Gen Optimized Standard Standard Limited
Setup Complexity Zero config High Moderate Build required

Syntax Comparison

Basic Component

MinimaJS vs React vs Vue

MinimaJS

import { div, h1, button, useState, app } from '@minimum-viable-web/minimajs/full';

const Counter = () => {
  const [count, setCount] = useState(0);
  return div([
    h1(`Count: ${count}`),
    button({ onClick: () => setCount(count + 1) }, 'Click me!')
  ]);
};

app(Counter, 'app');

React

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Click me!</button>
    </div>
  );
};

ReactDOM.render(<Counter />, document.getElementById('app'));

Vue 3

<template>
  <div>
    <h1>Count: {{ count }}</h1>
    <button @click="count++">Click me!</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

Template Syntax

HTML Templates Comparison

MinimaJS

import { html, useState } from '@minimum-viable-web/minimajs/full';

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  return html`
    <div class="todo-app">
      <h1>Todos</h1>
      ${todos.map(todo => html`
        <div class="todo ${todo.done ? 'done' : ''}">
          ${todo.text}
        </div>
      `)}
    </div>
  `;
};

React (JSX)

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  return (
    <div className="todo-app">
      <h1>Todos</h1>
      {todos.map(todo => (
        <div key={todo.id} className={`todo ${todo.done ? 'done' : ''}`}>
          {todo.text}
        </div>
      ))}
    </div>
  );
};

Vue 3

<template>
  <div class="todo-app">
    <h1>Todos</h1>
    <div v-for="todo in todos" :key="todo.id" 
         :class="['todo', { done: todo.done }]">
      {{ todo.text }}
    </div>
  </div>
</template>

State Management

State & Effects Comparison

MinimaJS

import { useState, useEffect, toggle, counter } from '@minimum-viable-web/minimajs/full';

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, toggleLoading] = toggle(true);
  const [visits, increment] = counter(0);
  
  useEffect(() => {
    fetch(`/users/${userId}`)
      .then(res => res.json())
      .then(setUser)
      .finally(() => toggleLoading());
  }, [userId]);
  
  return user ? div([
    p(`Name: ${user.name}`),
    button({ onClick: increment }, `Visits: ${visits}`)
  ]) : div('Loading...');
};

React

import { useState, useEffect } from 'react';

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [visits, setVisits] = useState(0);
  
  useEffect(() => {
    fetch(`/users/${userId}`)
      .then(res => res.json())
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);
  
  return user ? (
    <div>
      <p>Name: {user.name}</p>
      <button onClick={() => setVisits(v => v + 1)}>
        Visits: {visits}
      </button>
    </div>
  ) : <div>Loading...</div>;
};
Quick Start
npm install @minimum-viable-web/minimajs

Combined File (All Features)

For the complete experience with all features in a single file:

import { div, h1, button, useState, app, quickForm, html } from '@minimum-viable-web/minimajs/full';

const Counter = () => {
  const [count, setCount] = useState(0);

  return div([
    h1(`Count: ${count}`),
    button({ onClick: () => setCount(count + 1) }, 'Click me!')
  ]);
};

app(Counter, 'app'); // Mounts to #app

Modular Imports (Tree-Shakeable)

For optimal bundle size with only needed features:

import { div, h1, button, useState, app } from '@minimum-viable-web/minimajs/full';

const Counter = () => {
  const [count, setCount] = useState(0);

  return div([
    h1(`Count: ${count}`),
    button({ onClick: () => setCount(count + 1) }, 'Click me!')
  ]);
};

app(Counter, 'app'); // Mounts to #app

Template Syntax (HTML-like)

import { html, useState, app } from '@minimum-viable-web/minimajs/full';

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  return html`
    <div class="todo-app">
      <h1>My Todos</h1>
      <input value="${input}" oninput="${e => setInput(e.target.value)}">
      <button onclick="${() => addTodo()}">Add</button>
      ${todos.map(todo => html`
        <div class="todo ${todo.done ? 'done' : ''}">
          <input type="checkbox" checked="${todo.done}" onchange="${() => toggle(todo.id)}">
          ${todo.text}
        </div>
      `)}
    </div>
  `;
};
Core Concepts

Components & Hooks

import { useState, useEffect, div, p } from '@minimum-viable-web/minimajs/full';

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);
  
  if (loading) return div('Loading...');
  if (!user) return div('User not found');
  
  return div([
    p(`Name: ${user.name}`),
    p(`Email: ${user.email}`)
  ]);
};
State Managment
import { useState, toggle, counter, inputState } from '@minimum-viable-web/minimajs/full';

const MyComponent = () => {
  // Basic state
  const [name, setName] = useState('');
  
  // Toggle helper
  const [isVisible, toggleVisible] = toggle(false);
  
  // Counter helper  
  const [count, increment, decrement, setCount] = counter(0);
  
  // Input helper
  const [email, setEmail, onEmailChange] = inputState('');
  
  return div([
    input({ value: email, onChange: onEmailChange }),
    button({ onClick: toggleVisible }, 'Toggle'),
    button({ onClick: increment }, `Count: ${count}`)
  ]);
};
LLM-Optimized APIs

Perfect for AI code generation with predictable patterns:

Template Builders

import { quickForm, quickTable, quickModal } from '@minimum-viable-web/minimajs/full';

// Complete form in one line
const form = quickForm({
  fields: ['name', 'email', { name: 'password', type: 'password' }],
  onSubmit: (values) => console.log(values)
});

// Data table in one line  
const table = quickTable(users, [
  { key: 'name', header: 'Name' },
  { key: 'email', header: 'Email' },
  { key: 'role', header: 'Role' }
]);

// Modal in one line
const modal = quickModal(isOpen, content, { onClose: () => setOpen(false) });

Chain Syntax

import { $div, $button, $input } from '@minimum-viable-web/minimajs/full';

// Fluent chain building - perfect for LLMs
const card = $div()
  .class('card')
  .child($h3().text('Title'))
  .child($p().text('Description'))  
  .child($button().text('Action').onClick(handleClick))
  .build();

Pattern Macros

import { llmCreateApp } from '@minimum-viable-web/minimajs/full';

// Complete todo app in one call
const todoApp = llmCreateApp.todo({
  initialTodos: [{ text: 'Learn MinimaJS', done: false }]
});

// Complete counter in one call
const counter = llmCreateApp.counter({ 
  title: 'My Counter', 
  initialValue: 5,
  step: 2 
});
Server-Side Rendering
import { renderToString, hydrate } from '@minimum-viable-web/minimajs/full';

// Server
const html = renderToString(App, { props });
res.send(`<!DOCTYPE html><html><body><div id="app">${html}</div></body></html>`);

// Client  
hydrate(App, document.getElementById('app'), { props });
Built Framework Options ## Combined File (`minima-full.js`)

MinimaJS provides a complete combined file that includes all features in a single import for maximum convenience and rapid prototyping.

Benefits of the Combined File

Everything Included - All features (Core, API, LLM, SSR, DevTools) in one file ✅ Rapid Prototyping - Perfect for quick development and testing ✅ No Import Decisions - Single import gives you everything ✅ Self-Contained - No external dependencies or module resolution issues ✅ CDN-Friendly - Easy to use directly from CDN

When to Use the Combined File

  • Development & Prototyping - When you need all features quickly
  • Learning & Examples - Perfect for tutorials and documentation
  • Simple Applications - When bundle size isn't a primary concern
  • CDN Usage - When you want to avoid build tools entirely

File Information

// The combined file includes:
 Core Virtual DOM & Hooks (13KB minified)
 Template Engine with XSS Protection
 Advanced Component System
 Server-Side Rendering
 LLM-Optimized APIs (quickForm, $ builders, etc.)
 Developer Tools
 State Management Helpers
 Animation Utilities

// Total: 54KB unminified, 37KB when minified

Modular Imports (Tree-Shakeable)

For optimal bundle size with only needed features:

// Full framework
import { div, useState } from '@minimum-viable-web/minimajs/full';

// Core functionality only
import { createElement, useState, render } from '@minimum-viable-web/minimajs/core';

// Template engine only
import { html } from '@minimum-viable-web/minimajs/template';

// Component system only
import { defineComponent } from '@minimum-viable-web/minimajs/component';

// SSR features only
import { renderToString } from '@minimum-viable-web/minimajs/ssr';

// LLM helpers only
import { quickForm, $div } from '@minimum-viable-web/minimajs/llm';

// Dev tools only (optional)
import { useDevTools, inspectComponentTree } from '@minimum-viable-web/minimajs/devtools';

Module Features

MinimaJS is built as modular components that can be used independently:

Core Module (@minimum-viable-web/minimajs/core)

  • Virtual DOM with efficient diffing algorithm
  • Hooks system (useState, useEffect, useContext)
  • Component lifecycle management
  • Event delegation for optimal performance
  • Memory management with automatic cleanup

Template Module (@minimum-viable-web/minimajs/template)

  • HTML template literals with tagged template strings
  • XSS protection built-in with automatic sanitization
  • Conditional rendering helpers (when, unless)
  • List rendering with optimized mapping
  • Dynamic attributes and class binding

Component Module (@minimum-viable-web/minimajs/component)

  • Advanced component system with props validation
  • Higher-order components (memo, withProps)
  • Component composition patterns
  • Render props and children patterns
  • Error boundaries for robust applications

SSR Module (@minimum-viable-web/minimajs/ssr)

  • Server-side rendering with hydration
  • Static site generation capabilities
  • SEO optimization with meta tag support
  • Streaming rendering for improved performance
  • Universal routing for client/server compatibility

API Module (@minimum-viable-web/minimajs/api)

  • Shorthand element creators (div, span, button, etc.)
  • State helpers (toggle, counter, inputState, formState)
  • Animation utilities (fade, slide transitions)
  • Routing helpers (route, link, navigate)
  • Context providers for global state

LLM Module (@minimum-viable-web/minimajs/llm)

  • AI-optimized builders (quickForm, quickTable, quickModal)
  • Fluent chain syntax ($div().class().child().build())
  • Pattern macros for common UI patterns
  • Code generation helpers for LLM workflows
  • Semantic component creation with natural language

DevTools Module (@minimum-viable-web/minimajs/devtools) - Optional

  • Component inspection (useDevTools hook for runtime debugging)
  • Performance profiling (render timing and memory analysis)
  • Component tree visualization (console-based debugging)
  • Development utilities (enable/disable dev mode)
  • Browser extension ready (extensible for GUI tools)

Enabling DevTools

// Enable dev tools globally
import { enableDevTools } from '@minimum-viable-web/minimajs/devtools';
enableDevTools();

// Or set in browser console
window.__MINIMA_DEVTOOLS__ = true;
API Reference

API Reference

Core API

Virtual DOM & Rendering

createElement(type, props, ...children)

Creates a virtual DOM node.

import { createElement } from '@minimum-viable-web/minimajs/core';

const vnode = createElement('div', { id: 'app' }, 'Hello World');
// or
const vnode = createElement(MyComponent, { name: 'John' });

render(vnode, container)

Renders a virtual DOM node to the DOM.

import { render } from '@minimum-viable-web/minimajs/core';

render(vnode, document.getElementById('app'));

Fragment

Groups elements without extra wrapper.

import { Fragment } from '@minimum-viable-web/minimajs/core';

const list = createElement(Fragment, null, 
  createElement('li', null, 'Item 1'),
  createElement('li', null, 'Item 2')
);
Hooks

useState(initialValue)

Manages component state.

import { useState } from '@minimum-viable-web/minimajs/core';

const [count, setCount] = useState(0);
setCount(count + 1);
setCount(prev => prev + 1); // functional update

useEffect(effect, dependencies)

Handles side effects and lifecycle.

import { useEffect } from '@minimum-viable-web/minimajs/core';

// On mount and every render
useEffect(() => {
  console.log('Effect ran');
});

// On mount only
useEffect(() => {
  console.log('Mount effect');
}, []);

// With dependencies
useEffect(() => {
  fetchUser(userId);
}, [userId]);

// With cleanup
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer); // cleanup
}, []);

useContext(context)

Accesses context values.

import { useContext } from '@minimum-viable-web/minimajs/core';

const theme = useContext(ThemeContext);

Template API

HTML Templates

html(strings, ...values)

Creates components using template literals.

import { html } from '@minimum-viable-web/minimajs/template';

const MyComponent = ({ name }) => html`
  <div class="greeting">
    <h1>Hello ${name}!</h1>
    <button onclick="${() => alert('Hi!')}">Click me</button>
  </div>
`;

when(condition, template)

Conditional rendering helper.

import { when } from '@minimum-viable-web/minimajs/template';

const Profile = ({ user }) => html`
  <div>
    ${when(user, html`<h1>Welcome ${user.name}</h1>`)}
    ${when(!user, html`<a href="/login">Please log in</a>`)}
  </div>
`;

sanitizeText(text)

Sanitizes user input for XSS protection.

import { sanitizeText } from '@minimum-viable-web/minimajs/template';

const safe = sanitizeText(userInput);

Component API

Component System

defineComponent(options)

Creates advanced components with lifecycle.

import { defineComponent } from '@minimum-viable-web/minimajs/component';

const Button = defineComponent({
  name: 'Button',
  props: {
    variant: { type: String, default: 'primary' },
    disabled: { type: Boolean, default: false }
  },
  setup(props) {
    const [clicked, setClicked] = useState(false);
    
    onMounted(() => {
      console.log('Button mounted');
    });
    
    return { clicked, setClicked };
  },
  template: ({ props, clicked, setClicked }) => html`
    <button 
      class="btn btn-${props.variant}"
      disabled="${props.disabled}"
      onclick="${() => setClicked(true)}"
    >
      <slot></slot>
    </button>
  `
});

memo(component)

Optimizes component re-renders.

import { memo } from '@minimum-viable-web/minimajs/component';

const ExpensiveComponent = memo(({ data }) => {
  // Only re-renders if data changes
  return html`<div>${JSON.stringify(data)}</div>`;
});

withProps(component, props)

Higher-order component for prop injection.

import { withProps } from '@minimum-viable-web/minimajs/component';

const RedButton = withProps(Button, { variant: 'danger' });

SSR API

Server-Side Rendering

renderToString(component, props)

Renders component to HTML string.

import { renderToString } from '@minimum-viable-web/minimajs/ssr';

const html = renderToString(App, { user: userData });
const response = `<!DOCTYPE html>
<html>
  <body>
    <div id="app">${html}</div>
    <script src="app.js"></script>
  </body>
</html>`;

hydrate(component, container, props)

Hydrates server-rendered HTML.

import { hydrate } from '@minimum-viable-web/minimajs/ssr';

// Client-side hydration
hydrate(App, document.getElementById('app'), { user: userData });

LLM API

AI-Optimized Helpers

quickForm(config)

Generates complete forms.

import { quickForm } from '@minimum-viable-web/minimajs/llm';

const form = quickForm({
  fields: [
    { name: 'email', type: 'email', required: true },
    { name: 'password', type: 'password', required: true },
    { name: 'remember', type: 'checkbox', label: 'Remember me' }
  ],
  onSubmit: (values) => login(values),
  submitText: 'Sign In'
});

quickTable(data, columns)

Generates data tables.

import { quickTable } from '@minimum-viable-web/minimajs/llm';

const table = quickTable(users, [
  { key: 'name', header: 'Name', sortable: true },
  { key: 'email', header: 'Email' },
  { key: 'role', header: 'Role', render: (value) => value.toUpperCase() },
  { 
    key: 'actions', 
    header: 'Actions',
    render: (_, row) => html`
      <button onclick="${() => editUser(row.id)}">Edit</button>
      <button onclick="${() => deleteUser(row.id)}">Delete</button>
    `
  }
]);

Fluent Chain Syntax

import { $div, $button, $h1 } from '@minimum-viable-web/minimajs/llm';

const card = $div()
  .class('card shadow-lg')
  .child($h1().text('Card Title'))
  .child($button()
    .class('btn btn-primary')
    .text('Action')
    .onClick(() => handleClick())
  )
  .build();

Utility API

State Helpers

toggle(initialValue)

Boolean state helper.

import { toggle } from '@minimum-viable-web/minimajs/full';

const [isOpen, toggleOpen] = toggle(false);
// toggleOpen() flips the boolean
// toggleOpen(true) sets to true

counter(initialValue)

Counter state helper.

import { counter } from '@minimum-viable-web/minimajs/full';

const [count, increment, decrement, setCount] = counter(0);

inputState(initialValue)

Input state helper with onChange handler.

import { inputState } from '@minimum-viable-web/minimajs/full';

const [value, setValue, onChange] = inputState('');

// Use in JSX-like syntax
html`<input value="${value}" oninput="${onChange}">`;

Security

MinimaJS includes built-in XSS protection (loadTemplate and preloadComponent accept only HTTPS or same-origin relative URLs). See Security (XSS/CSP).

import { html, sanitizeText } from '@minimum-viable-web/minimajs/full';

// Safe by default - auto-sanitized
const userContent = html`<div>${userInput}</div>`;

// Manual sanitization
const clean = sanitizeText(dangerousString);

// CSP-compliant - no eval() usage

TypeScript Support

import { useState, div, button } from '@minimum-viable-web/minimajs/full';

interface User {
  id: number;
  name: string;
}

const UserCard = ({ user }: { user: User }) => {
  const [expanded, setExpanded] = useState<boolean>(false);
  
  return div([
    button({ onClick: () => setExpanded(!expanded) }, user.name),
    expanded && div(`ID: ${user.id}`)
  ]);
};

Testing

// Examples use the combined file for simplicity - see examples/README.md for details
import { createElement, useState, render } from '@minimum-viable-web/minimajs';

const TestComponent = () => {
  const [count, setCount] = useState(0);
  return div([
    span({ id: 'count' }, count),
    button({ id: 'inc', onClick: () => setCount(count + 1) }, '+')
  ]);
};

// Test renders and state updates
render(TestComponent(), document.body);

Contributing

We welcome contributions! Please see our Contributing Guide.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Feedback

We use GitHub Issues for all feedback. Open an issue to report bugs, request features, ask questions, or suggest improvements. Please search existing issues first to avoid duplicates.

Security / Vulnerability reporting

Private vulnerability reports are supported. To send a report in a way that is kept private, use GitHub’s private vulnerability reporting (submissions are sent over HTTPS and visible only to maintainers):

https://github.com/Minimum-Viable-Web/minima-js/security/advisories/new

We’ll triage the report and work with you on a fix. Do not open a public issue for security-sensitive bugs.

License

MIT License - see LICENSE file for details.

Documentation

Links

Why Choose MinimaJS?

  • Shortest syntax - Write less, do more
  • Zero dependencies - No supply chain risks
  • Modern features - Hooks, SSR, TypeScript
  • LLM-optimized - Perfect for AI development
  • Fast & small - Tiny codebase, optimized code
  • Security-first - XSS protection built-in
  • Flexible deployment - Modular or combined file options
  • Production-ready - Enterprise-grade with advanced features

Built for developers who value simplicity and performance.