@bapp/web-component-sdk
v0.1.0
Published
Skeleton SDK for authoring BAPP web components (custom elements) loaded by the host frontend
Readme
@bapp/web-component-sdk
SDK for authoring BAPP web components — custom elements built as ES bundles,
loaded by the host frontend via dynamic import, mounted into the page, and wired
to the host's live API client (window.bappAutoApi), pub/sub bus
(window.bappEvents), and an injected host context.
You write a class that extends BappElement, implement onMount, and register
a tag. The host does the loading, context injection, and lifecycle.
Install
npm install @bapp/web-component-sdk
# @bapp/auto-api-client is an optional peer dep — install it for typed API access:
npm install @bapp/auto-api-clientThe contract
The host owns the element's lifecycle. The order is always:
- Host creates the element and sets
el.bappContextBEFORE appending it. - Host appends it → the browser fires
connectedCallback→ the SDK calls youronMount(). - On any later change (route/
subPath, theme, tenant…), the host re-assignsbappContexton the same element. Subscribers registered viaonContextChange()are notified; the element is not re-mounted. - Host removes the element →
disconnectedCallback→ the SDK calls youronUnmount()(if defined).
Because context is set before append, this.bappContext and the context getters
are already populated inside onMount.
Quick start (vanilla)
import { BappElement } from "@bapp/web-component-sdk";
class MyWidget extends BappElement {
async onMount() {
// Initial context snapshot is available here:
const items = await this.api.list("invoice", { tenantId: this.tenantId });
this.innerHTML = `
<h2 style="color:${this.theme?.colorPrimary}">Invoices</h2>
<pre>${JSON.stringify(items, null, 2)}</pre>`;
// React to later host context changes (route, theme, …):
this._unsub = this.onContextChange((ctx) => {
// re-render based on ctx.subPath / ctx.theme …
});
}
onUnmount() {
this._unsub?.(); // always unsubscribe to avoid leaks across re-mounts
}
}
// register() is idempotent and returns the tag name.
export const tagName = MyWidget.register("bapp-my-widget");API
class BappElement extends HTMLElement
| Member | Description |
|---|---|
| abstract onMount(): void | Your entry point. Called when the element connects; context is already present. |
| onUnmount?(): void | Optional cleanup, called on disconnect. Unsubscribe here. |
| static register(tag): string | Define the custom element (idempotent). Returns tag. |
| get api: BappAutoApi | Host's live API client (window.bappAutoApi). |
| get events: BappEventBus | Host's pub/sub bus (window.bappEvents). |
| onContextChange(handler): () => void | Subscribe to context updates. Returns an unsubscribe fn. Does not fire with the initial value — read this.bappContext in onMount for that. A throwing handler is logged and skipped without affecting the others. |
| get bappContext: BappHostContext \| undefined | Current host context. |
| get tenantId / appSlug / basePath / subPath / theme / navigate | Convenience getters onto bappContext. |
type BappHostContext
type BappHostContext = {
basePath: string; // base route the widget is mounted under, e.g. "/conta/app"
subPath: string; // segments below basePath, e.g. "clients/42" — drives in-widget routing
appSlug: string; // host app this widget belongs to
tenantId?: string; // active tenant, when in a tenant context
theme: { colorPrimary: string; isDark: boolean };
navigate: (path: string) => void; // ask the host to navigate to an absolute in-app path
};interface BappEventBus
interface BappEventBus {
subscribe<T = unknown>(channel: string, handler: (data: T) => void): () => void;
publish(channel: string, data: unknown): void;
}The host bridges its realtime (websocket) layer into this bus, so subscribing to
a channel yields realtime events published on it. Always call the returned
unsubscribe function from onUnmount.
Examples
examples/conta-widget/— a full React widget: mounts React inonMount, mirrorsonContextChangeinto state for in-widget routing, subscribes to the events bus, and runs a background task. The canonical reference for a real-world widget. (Not part of the published package — see the repo.)demo/main.ts— a minimal vanilla widget used by the local dev server.
Build for the host
Build your component project as an ES bundle (with build.manifest: true),
upload dist/ to a CDN, and set the App's custom_frontend_url to that base
URL. The entry must be named src/main.tsx (or emit a { entry, styles }
manifest). The hosting origin must send CORS headers for the host origin.
Local demo
pnpm dev runs a Vite dev server (default http://localhost:5173) serving
index.html + demo/main.ts. The demo registers a <bapp-hello> element,
stubs window.bappAutoApi, and mounts it with a synthetic bappContext —
useful for smoke-testing SDK changes without booting the host. The SDK source is
imported from ../src, so HMR picks up edits to BappElement immediately.
Only dist/ (plus README.md, LICENSE, package.json) is published — the
demo/, examples/, and root index.html are excluded, per files in
package.json.
License
MIT
