@mr_hugo/boredom
v0.25.25
Published
Another boring JavaScript framework.
Maintainers
Readme
boreDOM
A JavaScript framework for building reactive web components with template-based architecture and automatic state synchronization.
Features
- 🥱 Reactive State Management - Automatic DOM updates when state changes
- 🥱 Template-based Components - Define components using HTML templates with
data-componentattributes - 🥱 Hot Module Reloading - Built-in dev server with file watching and auto-reload
- 🥱 Zero Configuration - Works out of the box with sensible defaults
- 🥱 CLI Tools - Development server and build tools included
- 🥱 TypeScript Support - Full TypeScript definitions included
- 🥱 Project Generator - Quick project scaffolding with
create-boredom
Quick Start
Installation
# Install the framework
pnpm install @mr_hugo/boredom
# Or create a new project
npx create-boredom my-app
cd my-app
pnpm devBasic Usage
- Create an HTML file with component templates:
<!DOCTYPE html>
<html>
<head>
<title>My boreDOM App</title>
</head>
<body>
<h1>Counter Example</h1>
<simple-counter></simple-counter>
<template data-component="simple-counter">
<div>
<p>Count: <slot name="counter">0</slot></p>
<button onclick="['increase']">+</button>
<button onclick="['decrease']">-</button>
</div>
</template>
<script src="main.js" type="module"></script>
</body>
</html>- Create the component logic in JavaScript:
// main.js
import { inflictBoreDOM, webComponent } from "@mr_hugo/boredom";
// Initialize with state
const uiState = await inflictBoreDOM({ count: 0 });
// simple-counter.js (or inline)
export const SimpleCounter = webComponent(({ on }) => {
on("increase", ({ state }) => {
state.count += 1;
});
on("decrease", ({ state }) => {
state.count -= 1;
});
return ({ state, slots }) => {
slots.counter = state.count;
};
});Development Server
boreDOM includes a built-in development server with hot reloading:
# Start dev server (watches for changes)
npx boredom
# Custom options
npx boredom --index ./src/index.html --html ./components --static ./publicThe CLI will:
- Watch for file changes in components, HTML, and static files
- Automatically rebuild and inject components
- Serve your app with hot reloading
- Copy static files to the build directory
API Reference
Core Functions
inflictBoreDOM(initialState, componentsLogic?)
Initializes the boreDOM framework and creates reactive state.
initialState- Initial application state objectcomponentsLogic- Optional inline component definitions- Returns - Proxified reactive state object
const state = await inflictBoreDOM({
users: [],
selectedUser: null,
});webComponent(initFunction)
Creates a web component with reactive behavior.
initFunction- Component initialization function- Returns - Component definition for use with boreDOM
const MyComponent = webComponent(({ on, state, refs, self }) => {
// Setup event handlers
on("click", ({ state }) => {
state.clicked = true;
});
// Return render function
return ({ state, slots, refs }) => {
slots.content = `Clicked: ${state.clicked}`;
};
});Component API
Components receive these parameters:
Initialization Phase
on(eventName, handler)- Register event listenersstate- Reactive state accessorrefs- DOM element referencesself- Component instancedetail- Component-specific data
Render Phase
state- Current state (read-only in render)slots- Named content slots for the templaterefs- DOM element referencesmakeComponent(tag, options)- Create child components
Template Syntax
Templates use standard HTML with special attributes:
<template data-component="my-component">
<!-- Named slots for dynamic content -->
<div>
<h2><slot name="title">Default Title</slot></h2>
<p><slot name="content">Default content</slot></p>
</div>
<!-- Event dispatching -->
<button onclick="['save']">Save</button>
<button onclick="['cancel']">Cancel</button>
<!-- Reference elements -->
<input ref="userInput" type="text">
</template>How Templates Become Components
- Declare a template with a tag name
<simple-counter></simple-counter>
<template data-component="simple-counter" data-aria-label="Counter">
<p>Count: <slot name="count">0</slot></p>
<button onclick="['increment']">+</button>
<button onclick="['decrement']">-</button>
<!-- Any other data-* on the template is mirrored to the element -->
<!-- e.g., data-aria-label -> aria-label on <simple-counter> -->
<!-- Add shadowrootmode="open" to render into a ShadowRoot -->
<!-- <template data-component=\"simple-counter\" shadowrootmode=\"open\"> -->
<!-- Optional: external script for behavior -->
<script type="module" src="/simple-counter.js"></script>
</template>- Provide behavior (first export is used)
// /simple-counter.js
import { webComponent } from "@mr_hugo/boredom";
export const SimpleCounter = webComponent(({ on }) => {
on("increment", ({ state }) => {
state.count += 1;
});
on("decrement", ({ state }) => {
state.count -= 1;
});
return ({ state, slots }) => {
slots.count = String(state.count);
};
});- Initialize once
import { inflictBoreDOM } from "@mr_hugo/boredom";
await inflictBoreDOM({ count: 0 });What happens under the hood
- The runtime scans
<template data-component>and registers custom elements. - It mirrors template
data-*to host attributes and wires inlineonclick="['...']"to custom events ("[]" is the dispatch action). - Scripts are dynamically imported and run for every matching instance in the DOM (including multiple instances).
- Subsequent instances created programmatically use the same initialization via
makeComponent().
State and Subscriptions
Rendering subscribes to the state paths it reads, and mutations trigger batched updates.
import { inflictBoreDOM, webComponent } from "@mr_hugo/boredom";
export const Counter = webComponent(({ on }) => {
on("inc", ({ state }) => {
state.count++;
}); // mutable state in handlers
return ({ state, slots }) => { // read-only during render
slots.value = String(state.count); // reading subscribes to `count`
};
});
await inflictBoreDOM({ count: 0 });- Subscriptions: Any property read in render (e.g.,
state.count) registers that render as a subscriber to that path. - Mutations: Changing arrays/objects (e.g.,
state.todos.push(...),state.user.name = 'X') schedules a single rAF to call subscribed renders. - Scope: Subscriptions are per component instance; only components that read a path re-render when that path changes.
Project Structure
A typical boreDOM project structure:
my-app/
├── index.html # Main HTML file
├── main.js # App initialization
├── components/ # Component files
│ ├── user-card.html # Component template
│ ├── user-card.js # Component logic
│ └── user-card.css # Component styles
├── public/ # Static assets
│ └── assets/
└── build/ # Generated build filesExamples
Counter Component
const Counter = webComponent(({ on }) => {
on("increment", ({ state }) => state.count++);
on("decrement", ({ state }) => state.count--);
return ({ state, slots }) => {
slots.value = state.count;
};
});Todo List Component
const TodoList = webComponent(({ on }) => {
on("add-todo", ({ state, e }) => {
state.todos.push({ id: Date.now(), text: e.text, done: false });
});
on("toggle-todo", ({ state, e }) => {
const todo = state.todos.find((t) => t.id === e.id);
if (todo) todo.done = !todo.done;
});
return ({ state, slots, makeComponent }) => {
slots.items = state.todos.map((todo) =>
makeComponent("todo-item", { detail: { todo } })
).join("");
};
});CLI Reference
# Development server with file watching
npx boredom [options]
Options:
--index <path> Base HTML file (default: index.html)
--html <folder> Components folder (default: components)
--static <folder> Static files folder (default: public)TypeScript Support
boreDOM includes full TypeScript definitions:
import { inflictBoreDOM, webComponent } from "@mr_hugo/boredom";
interface AppState {
count: number;
users: User[];
}
const state = await inflictBoreDOM<AppState>({
count: 0,
users: [],
});
const MyComponent = webComponent<AppState>(({ on, state }) => {
// TypeScript will infer correct types
on("increment", ({ state }) => {
state.count++; // ✓ Type-safe
});
return ({ state, slots }) => {
slots.count = state.count.toString();
};
});Resources
- Official Documentation: https://hugodaniel.com/pages/boredom/
- Repository: https://github.com/HugoDaniel/boreDOM
- Examples: Check the
/examplesdirectory for complete examples
