Skip to content

Analytics

Every feature emits timing events at each phase of its lifecycle. Subscribe to them to track performance, send to your analytics pipeline, or feed the devtools panel.

import {
onAnalyticsEvent,
emitAnalyticsEvent,
getAnalyticsLog,
clearAnalyticsLog,
getModuleTimings,
getAllModuleTimings,
createFeatureTimingTracker,
} from "mountly";

Subscribe to all events for all features. Returns an unsubscribe function.

const off = onAnalyticsEvent((event) => {
if (event.phase === "mount_end") {
analytics.track("feature_mounted", {
moduleId: event.moduleId,
duration: event.duration,
});
}
});
// Later:
off();
interface TimingEvent {
moduleId: string;
phase:
| "preload_start" | "preload_end"
| "activate_start" | "activate_end"
| "mount_start" | "mount_end"
| "unmount";
timestamp: number; // performance.now()
duration?: number; // present on _end phases
error?: string; // present on errored _end phases
}

The _start events fire when the phase begins; _end events fire when it resolves (or errors). unmount fires once per container unmount.

onAnalyticsEvent((event) => {
if (event.moduleId !== "signup-card") return;
// …
});

There is no built-in per-feature filter — keep it simple, filter in your callback.

getModuleTimings(moduleId) / getAllModuleTimings()

Section titled “getModuleTimings(moduleId) / getAllModuleTimings()”

Aggregated timings, computed from the event log.

getModuleTimings("signup-card");
// → {
// preload: { count: 4, p50: 124, p95: 280, mean: 152 },
// activate: { count: 4, p50: 8, p95: 40, mean: 18 },
// mount: { count: 4, p50: 12, p95: 28, mean: 17 },
// }

getAllModuleTimings() returns the same shape keyed by moduleId.

getAnalyticsLog() returns a snapshot array of all events recorded so far. clearAnalyticsLog() empties it. The log is a rolling buffer with a default cap (1000 entries); older events are dropped.

const events = getAnalyticsLog();
fetch("/analytics", { method: "POST", body: JSON.stringify(events) });
clearAnalyticsLog();

For widgets that want to log their own milestones (e.g. “form_submitted”) through the same pipeline:

emitAnalyticsEvent({
moduleId: "signup-card",
phase: "mount_end", // reuse a phase name, or invent your own
timestamp: performance.now(),
duration: 0,
});

Subscribers see all events emitted via emitAnalyticsEvent — there’s no schema validation, so use sparingly.

The internal helper used by createOnDemandFeature. Exposed for advanced use — building a custom feature shape that participates in the same analytics pipeline.

const tracker = createFeatureTimingTracker("custom-thing");
tracker.recordPhase("preload_start");
// … do work …
tracker.recordPhase("preload_end");

Most users never call this directly.