Skip to content

Commit

Permalink
perf: improve SSR
Browse files Browse the repository at this point in the history
  • Loading branch information
Carrotzpc committed Jul 25, 2024
1 parent c5d9bfb commit 99cf7dd
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 122 deletions.
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"scripts": {
"build": "dumi build",
"build:analyze": "ANALYZE=1 dumi build",
"build:no-compress": "COMPRESS=none dumi build",
"dev": "dumi dev",
"postinstall": "npm run setup",
"preview": "dumi preview",
Expand Down
11 changes: 5 additions & 6 deletions src/components/StoreUpdater/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import isEqual from 'fast-deep-equal';
import React, { type DependencyList, type EffectCallback, useEffect, useMemo } from 'react';

import { SiteStore, useSiteStore } from '@/store/useSiteStore';

const isBrowser = typeof window !== 'undefined';
import { isBrowser } from '@/utils';

const SSRInit: Record<string, boolean> = {};

Expand Down Expand Up @@ -45,7 +44,7 @@ const useSyncState = <T extends keyof SiteStore>(
) => {
const updater = updateMethod
? updateMethod
: (key: T, value: SiteStore[T]) => useSiteStore.setState({ [key]: value });
: (key: T, value: SiteStore[T]) => useSiteStore.setState?.({ [key]: value });

// 如果是 Node 环境,直接更新一次 store
// 但是为了避免多次更新 store,所以加一个标记
Expand Down Expand Up @@ -82,11 +81,11 @@ export const StoreUpdater = () => {
const { setLoading, ...data } = siteData;
const {
siteData: { setLoading: _, ...previousData },
} = useSiteStore.getState();
} = useSiteStore.getState?.() || { siteData: {} };

if (isEqual(data, previousData)) return;

useSiteStore.setState({ siteData });
useSiteStore.setState?.({ siteData });
});

useSyncState('sidebar', sidebar);
Expand All @@ -98,7 +97,7 @@ export const StoreUpdater = () => {
useSyncState('navData', navData, () => {
const data = siteData.themeConfig.hideHomeNav ? navData : [homeNav, ...navData];

useSiteStore.setState({ navData: data });
useSiteStore.setState?.({ navData: data });
});

return false;
Expand Down
117 changes: 6 additions & 111 deletions src/layouts/GlobalLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,112 +6,14 @@ import {
logicalPropertiesLinter,
parentSelectorLinter,
} from '@ant-design/cssinjs';
import { ConfigProvider, theme as antdTheme } from 'antd';
import { ThemeMode } from 'antd-style';
import { Outlet, usePrefersColor, useServerInsertedHTML } from 'dumi';
import { Outlet, useServerInsertedHTML } from 'dumi';
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import SiteContext, { SiteContextProps } from '@/slots/SiteContext';
import React from 'react';

import { useAdditionalThemeConfig } from '../hooks/useAdditionalThemeConfig';

type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768;
const SITE_STATE_LOCALSTORAGE_KEY = 'dumi-theme-yunti-site-state';

const defaultSiteState: SiteState = {
isMobile: false,
theme: ['light'],
};
const getAlgorithm = (themes: ThemeMode[] = []) =>
themes.map(theme => {
if (theme === 'dark') {
return antdTheme.darkAlgorithm;
}
return antdTheme.defaultAlgorithm;
});

const isThemeDark = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
const getSiteState = (siteState: any) => {
const localSiteState = siteState;
const isDark = isThemeDark(); // 系统默认主题
const theme = localSiteState?.theme || [];
const isAutoTheme = theme.includes('auto');
if (isAutoTheme) {
const nextTheme = theme.filter((item: string) => item !== 'auto');
nextTheme.push(isDark ? 'dark' : 'light');
localSiteState.theme = nextTheme;
}
return Object.assign(defaultSiteState, localSiteState);
};

const GlobalLayout: FC = () => {
const setPrefersColor = usePrefersColor()[2];
const { theme: configTheme, ssr, prefersColor } = useAdditionalThemeConfig();
const [{ theme, isMobile, direction }, setSiteState] = useState<SiteState>(defaultSiteState);

// 基于 localStorage 实现
const updateSiteConfig = useCallback((props: SiteState) => {
try {
const localSiteState = JSON.parse(
window.localStorage.getItem(SITE_STATE_LOCALSTORAGE_KEY) || '{}'
);
const nextLocalSiteState = Object.assign(localSiteState, props);
window.localStorage.setItem(SITE_STATE_LOCALSTORAGE_KEY, JSON.stringify(nextLocalSiteState));
setSiteState(prev => ({
...prev,
...props,
}));
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}, []);

const updateMobileMode = useCallback(() => {
updateSiteConfig({
isMobile: window.innerWidth < RESPONSIVE_MOBILE,
});
}, [updateSiteConfig]);

useEffect(() => {
try {
const localSiteState = JSON.parse(
window.localStorage.getItem(SITE_STATE_LOCALSTORAGE_KEY) || '{}'
);
// 首次设置主题样式
if (!localSiteState?.theme) {
localSiteState.theme = [prefersColor.default];
}
const siteConfig = getSiteState(localSiteState);
updateSiteConfig(siteConfig);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}, [prefersColor, updateSiteConfig]);

useEffect(() => {
updateMobileMode();
// set data-prefers-color
setPrefersColor((theme ?? []).includes('dark') ? 'dark' : 'light');
window.addEventListener('resize', updateMobileMode);
return () => {
window.removeEventListener('resize', updateMobileMode);
};
}, [theme, updateMobileMode, setPrefersColor]);

const siteContextValue = useMemo(
() => ({
direction,
isMobile: isMobile!,
theme: theme!,
updateSiteConfig,
}),
[isMobile, theme, direction, updateSiteConfig]
);

const { ssr } = useAdditionalThemeConfig();
const [styleCache] = React.useState(() => createCache());

useServerInsertedHTML(() => {
Expand All @@ -138,16 +40,9 @@ const GlobalLayout: FC = () => {
});

const BaseGlobalLayoutJSX = (
<SiteContext.Provider value={siteContextValue}>
<ConfigProvider
theme={{
...configTheme,
algorithm: getAlgorithm(theme),
}}
>
<Outlet />
</ConfigProvider>
</SiteContext.Provider>
<>
<Outlet />
</>
);

const SSRGlobalLayoutJSX = (
Expand Down
2 changes: 1 addition & 1 deletion src/slots/SiteContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface SiteContextProps {
const SiteContext = createContext<SiteContextProps>({
direction: 'ltr',
isMobile: false,
theme: ['light'],
theme: ['dark'],
updateSiteConfig: () => {},
});

Expand Down
47 changes: 46 additions & 1 deletion src/store/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import {
useIntl,
useLocale,
useLocation,
useNavData,
useRouteMeta,
useSidebarData,
useSiteData,
useTabMeta,
} from 'dumi';
import { AtomAsset } from 'dumi-assets-types';
import {
ILocale,
Expand All @@ -9,7 +19,7 @@ import {
} from 'dumi/dist/client/theme-api/types';
import { PICKED_PKG_FIELDS } from 'dumi/dist/constants';
import type { Location } from 'history';
import { ComponentType } from 'react';
import { ComponentType, useMemo } from 'react';

import { AllSiteThemeConfig } from '@/types';

Expand Down Expand Up @@ -86,3 +96,38 @@ export const initialState: SiteStore = {
themeConfig: {},
},
};

export const useInitialState = (): SiteStore => {
const { themeConfig }: any = useSiteData();
const navData = useNavData();
const sidebar = useSidebarData();
const routeMeta = useRouteMeta();
const tabMeta = useTabMeta();
const location = useLocation();
const locale = useLocale();
const intl = useIntl();
const homeNav = useMemo(
() => ({
activePath: '/',
link: '/',
title: intl.formatMessage({ id: 'header.nav.home' }),
}),
[intl]
);
const newNavdata = themeConfig.hideHomeNav ? navData : [homeNav, ...navData];
// console.log('themeConfig', themeConfig);

return {
...initialState,
locale: locale || initialState.locale,
location: location || initialState.location,
navData: newNavdata || initialState.navData,
routeMeta: routeMeta || initialState.routeMeta,
sidebar: sidebar || initialState.sidebar,
siteData: {
...initialState.siteData,
themeConfig,
},
tabMeta: tabMeta || initialState.tabMeta,
};
};
21 changes: 19 additions & 2 deletions src/store/useSiteStore.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import { devtools } from 'zustand/middleware';
import { createWithEqualityFn } from 'zustand/traditional';

import { SiteStore, initialState } from './initialState';
import { isBrowser } from '@/utils';

export const useSiteStore = createWithEqualityFn<SiteStore>()(
import { type SiteStore, initialState, useInitialState } from './initialState';

const browserStore = createWithEqualityFn<SiteStore>()(
devtools(() => initialState, { name: 'dumi-theme-yunti' })
);

export const useSiteStore = (
isBrowser
? browserStore
: <U>(selector: (state: SiteStore) => U, equalityFn?: (a: U, b: U) => boolean): U => {
const SSRState = useInitialState();
const useStore = createWithEqualityFn<SiteStore>()(
devtools(() => SSRState, { name: 'dumi-theme-yunti' })
);

Object.assign(useSiteStore, useStore);

return useStore(selector, equalityFn);
}
) as typeof browserStore;

export * from './initialState';
2 changes: 1 addition & 1 deletion src/store/useThemeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface ThemeStore {
export const useThemeStore = createWithEqualityFn<ThemeStore>()(
persist(
() => ({
themeMode: 'auto' as ThemeMode,
themeMode: 'light' as ThemeMode,
}),
{ name: 'ANTD_STYLE_DOC_STORE' }
)
Expand Down
1 change: 1 addition & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isBrowser = typeof window !== 'undefined';

0 comments on commit 99cf7dd

Please sign in to comment.