@faststats/web

中文文档
0.15.1发布于 2026/11/09TypeScriptES Module近 30 天下载 3802

[!IMPORTANT] > This repository is in early development and not intended for production use.

原始使用文档
文档目录8 节
  1. FastStats Web Analytics Core
  2. Identity and traits
  3. Visit duration
  4. Built-in event names
  5. Default context
  6. Feature flags
  7. Network recording in session replay
  8. Custom extensions

FastStats Web Analytics Core

[!IMPORTANT] This repository is in early development and not intended for production use.

The base SDK handles analytics, identity, sessions, navigation, outbound link clicks, and consent. Error tracking, Web Vitals, session replay, and feature flags are independent extensions, so applications only download the products they use.

import { init, track } from "@faststats/web";
import { errorTracking } from "@faststats/web/error";
import { sessionReplay } from "@faststats/web/replay";
import { webVitals } from "@faststats/web/web-vitals";

init({
    siteKey: "site_123",
    extensions: [
        errorTracking(),
        webVitals(),
        sessionReplay({ recordConsole: true }),
    ],
});

track("checkout_completed", { total: 42 });

init() starts one shared client and is idempotent, making it safe to call from application bootstrap code. Calls made before initialization are ignored rather than buffered with stale context. Use shutdown() before initializing with new options.

For multiple sites, tests, or framework integrations, create an independent client with explicit lifecycle control:

import { createClient } from "@faststats/web";

const analytics = createClient({ siteKey: "site_123" });
analytics.start();
analytics.track("checkout_completed", { total: 42 });
analytics.destroy();

Consent remains client-local: use "anonymous" for cookieless collection, "granted" for normal collection, or "denied" to keep the client dormant.

Identity and traits

identify() links the visitor to your user ID and sets profile fields and traits. traits override existing values; traitsOnce only fill in traits the person does not have yet.

import { identify, setTraits, unsetTraits } from "@faststats/web";

await identify("user_123", "[email protected]", {
    traits: { plan: "free" },
    traitsOnce: { signupSource: "reddit" },
});

await setTraits({ plan: "pro" });
await setTraits({ firstFeature: "export" }, { mode: "setOnce" });
await unsetTraits(["legacyPlan"]);

After a successful identify(), the user ID is stored next to the anonymous visitor ID and sent as externalId with every event, error, and replay chunk, so the collector knows who the visitor is. Use an opaque ID rather than an email address. getExternalId() returns it, or "" before identify(), in cookieless mode, and while consent is denied. logout() and denying consent always forget it. Cookieless clients never store or send it.

An identify() identical to the last one sent in this browser session, for the same anonymous visitor and with no queued trait changes, resolves true without a request, so calling it on every page load is cheap. Pass force: true to always send.

setTraits() and unsetTraits() update the identified visitor's person without a full identify. They apply to the user of the latest identify() call, even while its request is in flight. Before identify(), changes are kept in memory for the current page and sent, in order, right after the next successful identify(). Like identify(), they resolve false and send nothing in cookieless mode.

Visit duration

properties.time_on_page on $page_ping and $page_leave is the accumulated time in milliseconds that the page was visible and its document had focus. It pauses when the tab is hidden, the window loses focus, or the page enters the back-forward cache, and resets on navigation, logout, or session rotation. A monotonic clock keeps system-clock changes from affecting this duration. It also pauses after 60 seconds without pointer, keyboard, or scroll input, unless a <video> or <audio> element on the page is playing, and resumes on the next input. Time the device spent asleep or the tab frozen is not counted.

Pageviews wait until the page becomes visible. Pages replaced or closed before being shown do not emit pageviews or page-leave events.

The SDK sends a cumulative $page_ping when activity pauses and periodically while active: after 15 and 45 seconds, then every 60 seconds. A ping that adds no time is suppressed. A visit ends with exactly one $page_leave, on navigation, a terminal pagehide, logout, a consent change, or session rotation. A page the browser discards without notice never sends one, so its last ping holds its duration. A pageview, its pings, and its leave share properties.visit_id; consumers should use the maximum time_on_page per visit across both events, never sum them.

Before 0.13 the pings were sent as page_leave every 15 seconds. The collector and dashboards read both shapes; deploy them before releasing this SDK.

Built-in event names

The SDK sends its own events as $pageview, $page_ping, $page_leave, $outbound_link, and $error. Names starting with $ are reserved for built-in events, so track() ignores them and warns once per name in the console. Every other name, including pageview or error, is a custom event.

Each payload carries protocol: 2. Older SDKs sent the built-in events without the prefix; the collector renames those events, so older versions keep working.

Default context

Every event carries timezone (IANA name), language, and screen (1920x1080, CSS pixels) beside the page context, separate from its properties. Custom events and outbound links also carry the visit_id of the page view they happened during.

Each visit retains its original identity, session, and page metadata. A session or identity change starts a new visit, even on the same route. Both persistent and cookieless sessions expire after 30 minutes of inactivity or 24 hours of age.

session_duration is elapsed wall-clock time from the session start to the last counted activity. It includes time away between two active periods of the same session, so it is separate from active time on the page. An idle visitor stops extending the session, which then expires after 30 minutes like any other.

Delivery is best effort: a sudden process kill or network failure can lose time since the last delivered ping. The collector must accept successive events for the same visitor and URL; deploy the removal of its five-second debounce before releasing this SDK.

Feature flags

featureFlags() loads the project's flags for the visitor the client tracks and resolves them synchronously from memory:

import { init } from "@faststats/web";
import { featureFlags } from "@faststats/web/flags";

const flags = featureFlags();
init({ siteKey: "site_123", extensions: [flags] });

await flags.ready();
if (flags.isEnabled("new-checkout")) showNewCheckout();
const maxUploads = flags.get("max-uploads", 10);

A flag counts as exposed the first time the app reads a served value with get(), isEnabled() or details(), not when it loads. Reads are sent in small batches and on page hide, so the dashboard's exposure numbers show the flags people actually saw. Reading a default, while loading or for a missing flag, is not an exposure.

Flags are evaluated for the anonymous visitor ID, and the session ID links each evaluation to analytics. identify() re-evaluates them for that user, so rollouts follow the user across devices, and logout() goes back to a new anonymous visitor. When the user is known at startup, pass userId to skip the anonymous load. A cookieless visitor has no lasting ID, so rollouts hold for the session only. Flags are not loaded while consent is "denied", and revoking consent drops exposures not yet sent.

get() returns the default passed at the call site while flags are loading, and when the flag does not exist, is archived, has a different type, or the service cannot be reached within timeoutMs. details(key) adds the variant and reason (rule_match, target_match, fallthrough, disabled, prerequisite_failed).

flags.subscribe(() => render(flags.status)); // "idle" | "loading" | "ready" | "error"
await flags.setContext({ attributes: { plan: "pro" } });
await flags.reload();
Option
flagsLoads only these keys, for projects with many flags.
userIdYour ID for a signed-in user, when known at startup.
attributesAnything targeting rules should match on.
endpointBase URL of the flags service, when self-run.
timeoutMsAborts a slower load. Defaults to 3000.
fetchA fetch implementation to use instead of the global.

Typed flags

Generate types from the project's flags with the FastStats CLI:

faststats flags typegen my-project

This writes faststats-flags.d.ts (into src/ when there is one), declaring every active flag and the values it can serve. Keys then autocomplete, a typo or an archived flag fails to compile, and values are typed by their variants:

flags.get("theme", "system"); // "light" | "dark" | "system"
flags.isEnabled("theme"); // error: not a boolean flag

The default joins the result type because it is what loading serves. Run the command again after changing flags, or add --check in CI to fail when the file is out of date. Without the file, any key is accepted and the result is typed by the default.

Network recording in session replay

Network recording is disabled by default. Enable it with sessionReplay({ recordNetwork: true }). This records resource URLs and timing data using rrweb's network plugin. Headers and bodies are not recorded by default. FastStats collection endpoints (including a custom baseUrl) are always excluded to prevent recording replay uploads recursively.

Use networkOptions to configure rrweb's plugin, including filtering or redaction:

sessionReplay({
    recordNetwork: true,
    networkOptions: {
        initiatorTypes: ["fetch", "xmlhttprequest"],
        transformRequestFn: (request) => {
            const url = new URL(request.name);
            if (url.pathname.startsWith("/private/")) return;
            url.search = "";
            request.name = url.toString();
            return request;
        },
    },
});

recordHeaders, recordBody, and recordInitialRequests can also be set in networkOptions. Redact sensitive data before enabling headers or bodies. Network options alone do not enable recording. The same replay options are available through the React and Nuxt integrations.

Custom extensions

An extension registers only the lifecycle hooks it needs. Hook failures are isolated from analytics collection, asynchronous setup is safe during teardown, and cleanup receives discard: true when consent is revoked.

import type { AnalyticsExtension } from "@faststats/web";

export function diagnostics(): AnalyticsExtension {
    return {
        name: "diagnostics",
        setup(context) {
            const off = context.on("pageChange", ({ url }) => {
                console.debug("page changed", url);
            });

            return () => off();
        },
    };
}

版本与发布记录

@faststats/web 共发布 55 个版本,首次发布于 2026/03/15;过去 12 个月发布了 55 个版本。

版本发布日期距今
0.15.1latest2026/11/095 天前
0.15.02026/11/0212 天前
0.14.02026/10/2817 天前
0.13.32026/10/2520 天前
0.13.22026/10/2223 天前
0.13.12026/10/2025 天前

关于 @faststats/web 的常见问题

@faststats/web 是什么?

[!IMPORTANT] > This repository is in early development and not intended for production use.

如何安装 @faststats/web?

在项目目录运行 npm install @faststats/web,或使用 pnpm add / yarn add / bun add。

@faststats/web 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@faststats/web 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@faststats/web 可以商用吗?

@faststats/web 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@faststats/web 的下载量有多少?

根据 npm 官方下载统计,@faststats/web 在 2026/10/15 – 2026/11/13 这 30 天内被下载 3,802 次,日均约 127 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@faststats/web 还在维护吗?

最新版本 0.15.1 发布于 2026/11/09(5 天前);至今共发布 55 个版本,其中过去 12 个月发布了 55 个;首次发布于 2026/03/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。