Repository navigation
perf: lazy-load posthog-js (#406) and date-fns locales (#408) - #46
Merged
Merged
Conversation
posthog-js (~209 KB raw / ~48 KB brotli) was on the eager import graph via two
paths — main.tsx (init + PostHogProvider) and layout.tsx -> ee/posthog-user.tsx
(usePostHog) — so every self-hosted visitor downloaded and evaluated it though
telemetry is never sent. CLOUD is a runtime value (window.CONFIG), so the same
bundle serves cloud and self-hosted; the fix gates posthog behind a runtime
dynamic import, not a build-time drop.
New fork-owned features/analytics/ owns all posthog knowledge:
- posthog-init.ts (lazy-only): the sole static `import posthog from "posthog-js"`;
idempotent initPostHog() with the exact upstream init options.
- posthog-analytics.tsx (eager, no static posthog import): PostHogAnalytics always
renders <>{children}</> (stable element type -> <App/> never remounts) and, only
when isCloud() && isPostHogEnabled(), dynamically imports posthog-init and calls
initPostHog() in an effect (best-effort). PosthogUser is a lazyWithReload wrapper
around ee/posthog-user.tsx. No PostHogProvider: usePostHog() falls back to the
global singleton initPostHog() initializes, and posthog-user reads it with `?.`.
main.tsx (seam docmost#120) drops the eager posthog imports/init/provider and wraps
<App/> in <PostHogAnalytics>; this also fixes the latent guard bug (isPostHogEnabled
was referenced un-called). layout.tsx repoints its PosthogUser import at the lazy
wrapper. lazy-pages.test.ts registers the new lazy source so the lazyWithReload
convention is enforced.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
date-locale.ts statically imported all 12 date-fns locales from the barrel (~97 KB raw / ~19 KB brotli), so every visitor downloaded every locale though only one is ever used. Now en-US is the static default/fallback and the other 11 are lazy chunks (one per locale, literal import() specifiers) loaded on demand. Consumers need a Locale synchronously in render (incl. non-hook utils time.ts and format-label-date.ts), so the locale is preloaded BEFORE the language becomes active: preloadDateFnsLocale() is awaited immediately before i18n.changeLanguage() at the two (and only two) app-language set points — user-provider.tsx and account-language.tsx. getDateFnsLocale() therefore stays a pure synchronous getter that is always correct, with zero flash and no subscription store / event re-emit. Loaders are best-effort (a failed chunk keeps the en-US fallback and never rejects). Public API (getDateFnsLocale/useDateFnsLocale/formatLocalized) and en-US output are unchanged. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…al-JS budget Add "posthog-js" to mustStayLazy (matches the core and its /react subpath under node_modules/) so a future eager re-import reds the strict build. With posthog-js (docmost#406) and 11 date-fns locales (docmost#408) off the eager graph, the initial JS set dropped 789,788 -> 722,673 B brotli; tighten the budget ceiling 860,000 -> 790,000 (~9% headroom) to lock in the win. CSS unchanged (48,435 B). Co-Authored-By: Claude Opus 4.8 <[email protected]>
…t the locale-switch invariant Extract changeAppLanguage() in date-locale.ts (preload the date-fns locale, then i18n.changeLanguage) and route both app-language switch sites (user-provider, account-language) through it. This turns the previously-unenforced "every changeLanguage must be preceded by preloadDateFnsLocale" invariant into an enforceable, testable one (Architecture + Testing L3): - date-locale.test.ts: source-scan fitness function — no client file calls i18n.changeLanguage() outside date-locale.ts, and both switch sites use the helper (mirrors lazy-pages.test.ts); an ordering test proving the locale is cached BEFORE the switch fires (a change-then-load reorder fails it); a formatLocalized non-English test (the localized-pattern branch was unguarded); order-independent fallback assertions (reserved ko-KR/it-IT locales). Also: reword the overstated posthog-init.ts / posthog-analytics.test.tsx comments (posthog-js has two static importers, both lazy-reached; the off-eager-graph guarantee is the budget lane's, not the effect test's); reconcile the 2026-09-18 baseline to 789,788 B across vite.config.ts. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fork half of the paired PR for cccvu/wiki-v2#406 and cccvu/wiki-v2#408 — two bundle-trim follow-ups from docmost#309 / ADR 0020. Super-repo PR (gitlink bump +
UPSTREAM_MODIFICATIONS.md+ ADR) is separate.docmost#406 — posthog-js off the eager graph
posthog-js (~209 KB raw / ~48 KB br) sat on the eager import graph via two paths —
main.tsx(init +PostHogProvider) andlayout.tsx → ee/posthog-user.tsx(usePostHog) — so every self-hosted visitor downloaded and evaluated it although telemetry is never sent.CLOUDis a runtime value (window.CONFIG), so the same bundle serves cloud + self-hosted; posthog is now gated behind a runtime dynamic import, not a build-time drop (cloud telemetry is preserved).features/analytics/:posthog-init.ts(lazy-only, the sole staticimport posthog) +posthog-analytics.tsx(PostHogAnalyticspassthrough that dynamically inits posthog only whenisCloud() && isPostHogEnabled(), and alazyWithReloadPosthogUserwrapper).PostHogProvider:usePostHog()falls back to the global singletoninitPostHog()initializes, andposthog-user.tsxreads it with?.— so the wrapper always renders<>{children}</>(stable element type →<App/>never remounts).main.tsxdrops the eager imports/init/provider (also fixes the latentisPostHogEnabledun-called guard bug);layout.tsxrepoints itsPosthogUserimport.docmost#408 — only the active date-fns locale
date-locale.tsstatically imported all 12 locales (~97 KB raw / ~19 KB br). Nowen-USis the static default/fallback and the other 11 are lazy chunks (one per locale). Consumers need aLocalesynchronously in render (incl. non-hook utils), so the locale is preloaded before the language becomes active:preloadDateFnsLocale()is awaited beforei18n.changeLanguage()at the two (and only two) app-language set points.getDateFnsLocale()stays a pure sync getter — zero flash, no store, no re-emit. Public API + en-US output unchanged.Result & verification
posthog-jsadded tomustStayLazy; budget tightened 860,000 → 790,000.pnpm --filter client testgreen (46 files, 342 tests, incl. newposthog-analytics.test.tsx+date-locale.test.ts); strict build (BUNDLE_BUDGET_STRICT=1) passes; bundle report confirms posthog-js + non-active locales are lazy chunks (off the initial set); touched files typecheck clean.🤖 Generated with Claude Code