Skip to content

Commit

Permalink
fix(docs): reduce initial bundle size (#1327)
Browse files Browse the repository at this point in the history
  • Loading branch information
pujitm authored Aug 22, 2024
1 parent 2f55725 commit 9ab5938
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 74 deletions.
2 changes: 1 addition & 1 deletion packages/commons/next-seo/src/jsonld/types/breadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import z from "zod";
import { z } from "zod";

export const ListElementSchema = z.object({
"@type": z.literal("ListItem"),
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/analytics/CustomerAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { GoogleAnalytics } from "@next/third-parties/google";
import { useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import { isEqual } from "lodash-es";
import dynamic from "next/dynamic";
import Script from "next/script";
import { ReactElement, memo } from "react";
import { DOCS_ATOM, DOMAIN_ATOM } from "../atoms";
import { GoogleTagManager } from "./GoogleTagManager";
import { Posthog } from "./PosthogContainer";
import { renderSegmentSnippet } from "./segment";

const IntercomScript = dynamic(() => import("./IntercomScript").then((mod) => mod.IntercomScript));
const FullstoryScript = dynamic(() => import("./FullstoryScript").then((mod) => mod.FullstoryScript));
const GoogleAnalytics = dynamic(() => import("@next/third-parties/google").then((mod) => mod.GoogleAnalytics));
const GoogleTagManager = dynamic(() => import("./GoogleTagManager").then((mod) => mod.GoogleTagManager));

const ANALYTICS_ATOM = selectAtom(DOCS_ATOM, (docs) => docs.analytics ?? {}, isEqual);
const ANALYTICS_CONFIG_ATOM = selectAtom(DOCS_ATOM, (docs) => docs.analyticsConfig ?? {}, isEqual);
Expand Down
31 changes: 16 additions & 15 deletions packages/ui/app/src/analytics/posthog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DocsV1Read } from "@fern-api/fdr-sdk";
import type { DocsV1Read } from "@fern-api/fdr-sdk";
import { Router } from "next/router";
import posthog, { PostHog } from "posthog-js";
import type { PostHog } from "posthog-js";
import { useEffect } from "react";
import { safeCall } from "./sentry";

Expand All @@ -24,9 +24,10 @@ function posthogHasCustomer(instance: PostHog): instance is PostHogWithCustomer
}

let IS_POSTHOG_INITIALIZED = false;
function safeAccessPosthog(run: () => void): void {
async function safeAccessPosthog(run: (posthog: PostHog) => void): Promise<void> {
if (IS_POSTHOG_INITIALIZED) {
safeCall(run);
const posthog = (await import("posthog-js")).default;
safeCall(() => run(posthog));
}
}

Expand All @@ -35,18 +36,19 @@ function safeAccessPosthog(run: () => void): void {
*
* @param run
*/
function ifCustomer(run: (hog: PostHogWithCustomer) => void): void {
function ifCustomer(posthog: PostHog, run: (hog: PostHogWithCustomer) => void): void {
safeCall(() => {
if (IS_POSTHOG_INITIALIZED && posthogHasCustomer(posthog)) {
run(posthog);
}
});
}

export function initializePosthog(customerConfig?: DocsV1Read.PostHogConfig): void {
export async function initializePosthog(customerConfig?: DocsV1Read.PostHogConfig): Promise<void> {
const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY?.trim();
if (process.env.NODE_ENV === "production" && apiKey != null && apiKey.length > 0 && !IS_POSTHOG_INITIALIZED) {
const posthogProxy = "/api/fern-docs/analytics/posthog";
const posthog = (await import("posthog-js")).default;

posthog.init(apiKey, {
api_host: posthogProxy,
Expand Down Expand Up @@ -75,30 +77,30 @@ export function initializePosthog(customerConfig?: DocsV1Read.PostHogConfig): vo
}

export function identifyUser(userId: string): void {
safeAccessPosthog(() => {
void safeAccessPosthog((posthog) => {
posthog.identify(userId);
ifCustomer((posthog) => posthog.customer.identify(userId));
ifCustomer(posthog, (posthog) => posthog.customer.identify(userId));
});
}

export function registerPosthogProperties(properties: Record<string, unknown>): void {
safeAccessPosthog(() => {
void safeAccessPosthog((posthog) => {
posthog.register(properties);
ifCustomer((posthog) => posthog.customer.register(properties));
ifCustomer(posthog, (posthog) => posthog.customer.register(properties));
});
}

export function resetPosthog(): void {
safeAccessPosthog(() => {
void safeAccessPosthog((posthog) => {
posthog.reset();
ifCustomer((posthog) => posthog.customer.reset());
ifCustomer(posthog, (posthog) => posthog.customer.reset());
});
}

export function capturePosthogEvent(eventName: string, properties?: Record<string, unknown>): void {
safeAccessPosthog(() => {
void safeAccessPosthog((posthog) => {
posthog.capture(eventName, properties);
ifCustomer((posthog) => posthog.customer.capture(eventName, properties));
ifCustomer(posthog, (posthog) => posthog.customer.capture(eventName, properties));
});
}

Expand All @@ -115,7 +117,6 @@ const trackPageView = (url: string) => {
export function useInitializePosthog(customerConfig?: DocsV1Read.PostHogConfig): void {
useEffect(() => {
safeCall(() => initializePosthog(customerConfig));

Router.events.on("routeChangeComplete", trackPageView);
return () => {
Router.events.off("routeChangeComplete", trackPageView);
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-playground/PlaygroundEndpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { compact, mapValues, once } from "lodash-es";
import { FC, ReactElement, useCallback, useState } from "react";
import urljoin from "url-join";
import { useCallbackOne } from "use-memo-one";
import { capturePosthogEvent } from "../analytics/posthog";
import { captureSentryError } from "../analytics/sentry";
import {
PLAYGROUND_AUTH_STATE_ATOM,
Expand Down Expand Up @@ -92,6 +91,7 @@ export const PlaygroundEndpoint: FC<PlaygroundEndpointProps> = ({ endpoint, type
}
setResponse(loading());
try {
const { capturePosthogEvent } = await import("../analytics/posthog");
capturePosthogEvent("api_playground_request_sent", {
endpointId: endpoint.id,
endpointName: endpoint.title,
Expand Down
47 changes: 0 additions & 47 deletions packages/ui/app/src/atoms/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ColorsConfig } from "@fern-ui/fdr-utils";
import { atom, useAtom, useAtomValue } from "jotai";
import { atomWithRefresh, selectAtom } from "jotai/utils";
import { isEqual } from "lodash-es";
import { createElement, memo } from "react";
import { noop } from "ts-essentials";
import { useCallbackOne } from "use-memo-one";
import { z } from "zod";
Expand Down Expand Up @@ -167,49 +166,3 @@ export function useInitializeTheme(): void {
}, []),
);
}

// this script cannot reference any other code since it will be stringified to be executed in the browser
export const script = (themes: AvailableThemes): void => {
const el = document.documentElement;

function updateDOM(theme: string) {
el.classList.remove("light", "dark");
el.classList.add(theme);
el.style.colorScheme = theme;
}

function getSystemTheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

if (themes.length === 1) {
updateDOM(themes[0]);
} else {
try {
const themeName = localStorage.getItem("theme") ?? "system";
const isSystem = themes.length > 0 && themeName === "system";
const theme = isSystem ? getSystemTheme() : themeName;
updateDOM(theme);
} catch {
//
}
}
};

// including the ThemeScript component will prevent a flash of unstyled content (FOUC) when the page loads
// by setting the theme from local storage before the page is rendered
export const ThemeScript = memo(
({ nonce, colors }: { nonce?: string; colors?: ColorsConfig }) => {
const scriptArgs = JSON.stringify(getAvailableThemes(colors));

return createElement("script", {
suppressHydrationWarning: true,
nonce: typeof window === "undefined" ? nonce : "",
dangerouslySetInnerHTML: { __html: `(${script.toString()})(${scriptArgs})` },
});
},
(prev, next) =>
prev.nonce === next.nonce && getAvailableThemes(prev.colors).length === getAvailableThemes(next.colors).length,
);

ThemeScript.displayName = "ThemeScript";
5 changes: 4 additions & 1 deletion packages/ui/app/src/mdx/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RemoteFontAwesomeIcon } from "@fern-ui/components";
import type { MDXComponents } from "mdx/types";
import dynamic from "next/dynamic";
import { ComponentProps, PropsWithChildren, ReactElement } from "react";
import { FernErrorBoundaryProps, FernErrorTag } from "../../components/FernErrorBoundary";
import { AccordionGroup } from "./accordion";
Expand All @@ -20,7 +21,6 @@ import {
} from "./callout";
import { Card, CardGroup } from "./card";
import { ClientLibraries } from "./client-libraries";
import { CodeBlock, CodeGroup } from "./code";
import { Column, ColumnGroup } from "./columns";
import { Frame } from "./frame";
import { A, HeadingRenderer, Image, Li, Ol, Strong, Ul } from "./html";
Expand All @@ -33,6 +33,9 @@ import { Steps } from "./steps";
import { TabGroup } from "./tabs";
import { Tooltip } from "./tooltip";

const CodeBlock = dynamic(() => import("./code").then((mod) => mod.CodeBlock));
const CodeGroup = dynamic(() => import("./code").then((mod) => mod.CodeGroup));

const FERN_COMPONENTS = {
AccordionGroup,
Availability,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/mdx/plugins/rehypeFernCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Element, Root } from "hast";
import type { MdxJsxAttribute, MdxJsxFlowElementHast } from "mdast-util-mdx-jsx";
import rangeParser from "parse-numeric-range";
import { visit } from "unist-util-visit";
import { FernSyntaxHighlighterProps } from "../../syntax-highlighting/FernSyntaxHighlighter";
import type { FernSyntaxHighlighterProps } from "../../syntax-highlighting/FernSyntaxHighlighter";
import { unknownToString } from "../../util/unknownToString";
import type { CodeGroup } from "../components/code";
import { isElement, isMdxJsxFlowElement, isText, toAttribute } from "./utils";
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/next-app/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useRouteChanged } from "../hooks/useRouteChanged";
import { NextSeo } from "../seo/NextSeo";
import { InitializeTheme } from "../themes";
import { ThemedDocs } from "../themes/ThemedDocs";
import { scrollToRoute } from "../util/anchor";
import { JavascriptProvider } from "./utils/JavascriptProvider";

const SearchDialog = dynamic(() => import("../search/SearchDialog").then(({ SearchDialog }) => SearchDialog), {
Expand All @@ -26,7 +25,8 @@ export function DocsPage(pageProps: DocsProps): ReactElement | null {
// this is a hack to scroll to the correct anchor position when the route changes (see workato's docs)
// the underlying issue is that content rendering is delayed by an undetermined amount of time, so the anchor doesn't exist yet.
// TODO: fix this properly.
useRouteChanged((route) => {
useRouteChanged(async (route) => {
const scrollToRoute = await import("../util/anchor").then((mod) => mod.scrollToRoute);
const scroll = () => scrollToRoute(route);
if (!scroll()) {
setTimeout(scroll, 150);
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/app/src/next-app/NextApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import PageLoader from "next/dist/client/page-loader";
import { Router } from "next/router";
import { ReactElement, useEffect } from "react";
import { SWRConfig } from "swr";
import { DocsProps, ThemeScript, store } from "../atoms";
import { DocsProps, store } from "../atoms";
import { FernErrorBoundary } from "../components/FernErrorBoundary";
import "../css/globals.scss";
import { NextNProgress } from "../docs/NProgress";
import { ThemeScript } from "./utils/ThemeScript";

export function NextApp({ Component, pageProps, router }: AppProps<DocsProps | undefined>): ReactElement {
// This is a hack to handle edge-cases related to multitenant subpath rendering:
Expand Down
50 changes: 50 additions & 0 deletions packages/ui/app/src/next-app/utils/ThemeScript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ColorsConfig } from "@fern-ui/fdr-utils";
import Script from "next/script";
import type { ReactElement } from "react";
import type { AvailableThemes } from "../../atoms";

// this script cannot reference any other code since it will be stringified to be executed in the browser
const script = (themes: AvailableThemes): void => {
const el = document.documentElement;

function updateDOM(theme: string) {
el.classList.remove("light", "dark");
el.classList.add(theme);
el.style.colorScheme = theme;
}

function getSystemTheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

if (themes.length === 1) {
updateDOM(themes[0]);
} else {
try {
const themeName = localStorage.getItem("theme") ?? "system";
const isSystem = themes.length > 0 && themeName === "system";
const theme = isSystem ? getSystemTheme() : themeName;
updateDOM(theme);
} catch {
//
}
}
};

const getAvailableThemes = (colors: Partial<ColorsConfig> = {}): AvailableThemes => {
if (Boolean(colors.dark) === Boolean(colors.light)) {
return ["light", "dark"];
}

return colors.dark ? ["dark"] : ["light"];
};

export function ThemeScript({ colors }: { colors?: ColorsConfig }): ReactElement {
const args = getAvailableThemes(colors);
return (
<Script
id="theme-script"
dangerouslySetInnerHTML={{ __html: `(${script.toString()})(${JSON.stringify(args)})` }}
/>
);
}
11 changes: 9 additions & 2 deletions packages/ui/app/src/syntax-highlighting/fernShiki.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { Root } from "hast";
import { h } from "hastscript";
import { memoize } from "lodash-es";
import memoize from "lodash-es/memoize";
import { useCallback, useEffect, useState } from "react";
import { BundledLanguage, BundledTheme, Highlighter, SpecialLanguage, bundledLanguages, getHighlighter } from "shiki";
import {
bundledLanguages,
type BundledLanguage,
type BundledTheme,
type Highlighter,
type SpecialLanguage,
} from "shiki";
import { additionalLanguages } from "./syntaxes";

let highlighterPromise: Promise<Highlighter>;
Expand All @@ -19,6 +25,7 @@ export const getHighlighterInstance: (language: string) => Promise<Highlighter>
}

if (highlighterPromise == null) {
const getHighlighter = (await import("shiki")).getHighlighter;
highlighterPromise = getHighlighter({
langs: [additionalLanguages[lang] ?? lang],
themes: [LIGHT_THEME, DARK_THEME],
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/util/parseStringStyle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { camelCase } from "lodash-es";
import camelCase from "lodash-es/camelCase";
import StyleToObject from "style-to-object";
import { captureSentryError } from "../analytics/sentry";

Expand Down

0 comments on commit 9ab5938

Please sign in to comment.