Application architecture
createApp() owns one mount root, effect scope, plugin stack, provider map,
Router plugin, and explicit cleanup boundary. Stateful UI remains a Custom
Element; functional components remain render functions.
Router and Store
import { createApp, html } from '@gluonjs/core';
import { createRouter, createRouterPlugin, createWebHistory, RouterView } from '@gluonjs/router';
import { createStoreManager, defineStore } from '@gluonjs/store';
const counter = defineStore('counter', () => ({ count: 0 }), {
actions: (store) => ({ increment: () => { store.count += 1; } }),
});
const stores = createStoreManager();
const state = counter.use(stores);
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/', component: () => html`<button @click=${state.increment}>${state.count}</button>` }],
});
await router.isReady();
const app = createApp(() => html`<main>${RouterView()}</main>`);
app.use(createRouterPlugin(router));
app.onUnmounted(() => stores.dispose());
app.mount(document.querySelector('#app')!);
Create one Store manager per application, request, or test. Do not export a process-wide live manager. The Router plugin destroys its Router with the owning application, and the example disposes its Store manager from the same lifecycle.
Custom Elements
import { GluonElement, defineElement, html } from '@gluonjs/core';
export class GluonCounter extends GluonElement {
count = 0;
increment(): void {
this.count += 1;
this.emit('change', { value: this.count });
void this.requestUpdate();
}
protected override render() {
return html`<button type="button" @click=${() => this.increment()}>${this.count}</button>`;
}
}
defineElement('gluon-counter', GluonCounter);
Properties use JavaScript property bindings for structured values, outputs are
native CustomEvent instances, and projected content uses native slots.
The component authoring guide explains each property option, event propagation and cancellation, class selection, and connection cleanup.