Getting Started
Welcome to Inglorious Web! Let's build your first app in under 5 minutes.
Quick Start with Scaffolding
The fastest way to get started is with the official scaffolding tool:
npm create @inglorious/app@latest my-first-app
Follow the prompts:
- Choose a template (select js for this tutorial)
- Install dependencies
Then start developing:
cd my-first-app
npm install
npm run dev
2
3
Open your browser to http://localhost:5173 and you'll see "Hello world, Hello wide, Hello web!" (try clicking the words!)
Want more control? See the Installation guide for manual setup options (no build step, Vite, TypeScript).
Understanding the Starter Code
The scaffolding created a simple app that demonstrates Inglorious Web's key strength: multiple instances sharing the same behavior.
The Entity Type (src/message/message.js)
import { html } from "@inglorious/web"
export const message = {
click(entity) {
entity.isUpperCase = !entity.isUpperCase
},
render(entity, api) {
const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
return html`<span @click=${() => api.notify(`#${entity.id}:click`)}
>Hello ${who}</span
>`
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
This defines the behavior for all message entities:
- Click handler toggles between uppercase/lowercase
- Render method displays "Hello {who}"
The Entity Instances (src/store/entities.js)
export const entities = {
message1: {
type: "message",
who: "world",
},
message2: {
type: "message",
who: "wide",
},
message3: {
type: "message",
who: "web",
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Here we create three separate instances of the message type, each with different data.
This is where Inglorious Web shines - creating multiple instances is just declaring more objects!
The App (src/app.js)
import { html } from "@inglorious/web"
export const app = {
render(api) {
return html`<h1>
${api.render("message1")}, ${api.render("message2")},
${api.render("message3")}!
</h1>`
},
}
2
3
4
5
6
7
8
9
10
The app renders all three message instances. Each one:
- Shares the same behavior (click to toggle)
- Maintains independent state (uppercase/lowercase)
- Updates independently when clicked
Try it: Click on different words - each maintains its own state!
How It Works
Here's what happens when you click "world":
- Event triggered —
@clickcallsapi.notify("#message1:click") - Handler runs —
message.click(entity)togglesmessage1.isUpperCase - Store notifies subscribers — Your render function is called
- Full template re-renders —
app.render(api)runs completely - lit-html diffs — Only the changed text node updates
- Browser updates — You see "world" → "WORLD" (or vice versa)
Only message1 changed, but the entire template ran - lit-html was smart enough to update only that one word.
Key Concepts
Entities
Entities are plain JavaScript objects representing UI state:
{
id: "message1", // From the entities object key
type: "message", // References the type definition
who: "world", // Your custom state
isUpperCase: true // Added by the click handler
}
2
3
4
5
6
Types
Types define shared behavior for entities:
const message = {
// Event handler
click(entity) {
entity.isUpperCase = !entity.isUpperCase
},
// Render method
render(entity, api) {
return html`<span>${entity.who}</span>`
},
}
2
3
4
5
6
7
8
9
10
11
Think of types as classes and entities as instances. Just remember: you'll never invoke event handlers directly — they respond to events that are notified.
Multiple Instances
This is where Inglorious Web differs from traditional component frameworks:
React/Vue way:
<Message who="world" />
<Message who="wide" />
<Message who="web" />
2
3
Props passed at render time, instances managed by framework.
Inglorious Web way:
// Declare instances explicitly
entities: {
message1: { type: "message", who: "world" },
message2: { type: "message", who: "wide" },
message3: { type: "message", who: "web" },
}
// Render by ID
html`${api.render("message1")} ${api.render("message2")}`
2
3
4
5
6
7
8
9
Benefits:
- ✅ All state visible in one place (the store)
- ✅ Easy to serialize/persist entire app state
- ✅ Time-travel debugging works perfectly
- ✅ Can add/remove instances dynamically
Most components are singletons: In real apps, you typically only need one instance of most entities (header, footer, nav, etc.). For these cases, you can use the
autoCreateEntitiesflag to automatically create singleton entities without explicitly declaring them. See Auto-Create Entities for details. The explicit approach shown here is useful when you genuinely need multiple instances with different data.
The api Object
Your connection to the store:
api.render(id)— Render an entity by IDapi.notify(event, payload?)— Trigger an eventapi.getEntity(id)— Read entity stateapi.getEntities()— Read all entitiesapi.getEntities(typeName)— Read entities by type
Events
Events trigger state changes. Base format: [#entityId:]eventName
// Target specific entity
api.notify("#message1:click")
// Broadcast to all entities with this handler
api.notify("click")
// With payload
api.notify("#message1:setText", "hello")
2
3
4
5
6
7
8
Try It Yourself
Let's add a "reset all" feature.
1. Add a reset handler to the message type (src/message/message.js):
export const message = {
click(entity) {
entity.isUpperCase = !entity.isUpperCase
},
// Add this handler
resetAll(entity) {
entity.isUpperCase = false
},
render(entity, api) {
const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
return html`<span @click=${() => api.notify(`#${entity.id}:click`)}
>Hello ${who}</span
>`
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2. Add a button in the app (src/app.js):
export const app = {
render(api) {
return html`
<div>
<h1>
${api.render("message1")}, ${api.render("message2")},
${api.render("message3")}!
</h1>
<button @click=${() => api.notify("resetAll")}>Reset All</button>
</div>
`
},
}
2
3
4
5
6
7
8
9
10
11
12
13
Save and watch it update! Now clicking "Reset All" broadcasts the resetAll event to all three message entities.
Notice:
- We didn't specify entity IDs in the button's
notify() - The event broadcasts to all entities that have a
resetAllhandler - Each message entity receives the event and resets independently
Building Without Tools (Optional)
Want to skip the build step? Create a single HTML file:
<!DOCTYPE html>
<html>
<head>
<title>Inglorious Web Demo</title>
<script type="importmap">
{
"imports": {
"mutative": "https://unpkg.com/mutative@latest/dist/mutative.esm.mjs",
"lit-html": "https://unpkg.com/lit-html@latest/lit-html.js",
"lit-html/": "https://unpkg.com/lit-html@latest/",
"@inglorious/utils/": "https://unpkg.com/@inglorious%2Futils@latest/src/",
"@inglorious/store": "https://unpkg.com/@inglorious%2Fstore@latest/src/store.js",
"@inglorious/store/": "https://unpkg.com/@inglorious%2Fstore@latest/src/",
"@inglorious/web": "https://unpkg.com/@inglorious%2Fweb@latest/src/index.js"
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module">
import { createStore, mount, html } from "@inglorious/web"
const types = {
message: {
click(entity) {
entity.isUpperCase = !entity.isUpperCase
},
render(entity, api) {
const who = entity.isUpperCase
? entity.who.toUpperCase()
: entity.who
return html`<span @click=${() => api.notify(`#${entity.id}:click`)}>
Hello ${who}
</span>`
},
},
}
const entities = {
message1: { type: "message", who: "world" },
message2: { type: "message", who: "wide" },
message3: { type: "message", who: "web" },
}
const store = createStore({ types, entities })
const renderApp = (api) =>
html`<h1>
${api.render("message1")}, ${api.render("message2")},
${api.render("message3")}!
</h1>`
mount(store, renderApp, document.getElementById("root"))
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Open this file in a browser - no build step needed!
What Makes This Different?
Coming from React or Vue? Here's what's different:
| Concept | React/Vue | Inglorious Web |
|---|---|---|
| Component instances | Created implicitly at render | Declared explicitly in store |
| State location | Inside components (hooks/data) | In the store (all visible) |
| Multiple instances | Render component N times | Declare N entities in store |
| Instance identity | Managed by framework | You control via entity IDs |
| State serialization | Complex (spread across tree) | Simple (it's just the store) |
| Time-travel debugging | Requires special setup | Built-in (Redux DevTools) |
| Re-render strategy | Selective (optimize re-renders) | Full-tree (lit-html optimizes) |
The trade-off: Explicit instance management for predictable state management.
Next Steps
Now that you have a working app, explore these topics:
- Core Concepts — Deep dive into entities, types, and the
apiobject - Rendering Model — Understanding full-tree re-renders
- Event System — Master event targeting and broadcasting
- Auto-Create Entities — Simplify singleton entity management
- Featured Types Overview — Built-in router, forms, tables, and more
- Type Composition — Compose behaviors for guards and middleware
- Testing — How to test your app (spoiler: it's trivial)
Happy building! 🚀
Inglorious Web