mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
refactor: mitigate FOUC when applying Prism theme
This commit is contained in:
parent
7c9892888d
commit
3094b9f4fa
9 changed files with 48 additions and 45 deletions
|
@ -15,6 +15,8 @@ import rtlcss from 'rtlcss';
|
||||||
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
|
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
|
||||||
import type {Options} from '@docusaurus/theme-classic';
|
import type {Options} from '@docusaurus/theme-classic';
|
||||||
import type webpack from 'webpack';
|
import type webpack from 'webpack';
|
||||||
|
import type {PrismTheme} from 'prism-react-renderer';
|
||||||
|
import type {CSSProperties} from 'react';
|
||||||
|
|
||||||
const requireFromDocusaurusCore = createRequire(
|
const requireFromDocusaurusCore = createRequire(
|
||||||
require.resolve('@docusaurus/core/package.json'),
|
require.resolve('@docusaurus/core/package.json'),
|
||||||
|
@ -22,6 +24,22 @@ const requireFromDocusaurusCore = createRequire(
|
||||||
const ContextReplacementPlugin: typeof webpack.ContextReplacementPlugin =
|
const ContextReplacementPlugin: typeof webpack.ContextReplacementPlugin =
|
||||||
requireFromDocusaurusCore('webpack/lib/ContextReplacementPlugin');
|
requireFromDocusaurusCore('webpack/lib/ContextReplacementPlugin');
|
||||||
|
|
||||||
|
const getPrismCssVariables = (prismTheme: PrismTheme): CSSProperties => {
|
||||||
|
const mapping: {[name: keyof PrismTheme['plain']]: string} = {
|
||||||
|
color: '--prism-color',
|
||||||
|
backgroundColor: '--prism-background-color',
|
||||||
|
};
|
||||||
|
|
||||||
|
const properties: {[key: string]: string} = {};
|
||||||
|
Object.entries(prismTheme.plain).forEach(([key, value]) => {
|
||||||
|
const varName = mapping[key];
|
||||||
|
if (varName && typeof value === 'string') {
|
||||||
|
properties[varName] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return properties;
|
||||||
|
};
|
||||||
|
|
||||||
// Need to be inlined to prevent dark mode FOUC
|
// Need to be inlined to prevent dark mode FOUC
|
||||||
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
|
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
|
||||||
const ThemeStorageKey = 'theme';
|
const ThemeStorageKey = 'theme';
|
||||||
|
@ -103,10 +121,14 @@ export default function themeClassic(
|
||||||
const {
|
const {
|
||||||
announcementBar,
|
announcementBar,
|
||||||
colorMode,
|
colorMode,
|
||||||
prism: {additionalLanguages},
|
prism: {additionalLanguages, theme, darkTheme},
|
||||||
} = themeConfig;
|
} = themeConfig;
|
||||||
const {customCss} = options ?? {};
|
const {customCss} = options ?? {};
|
||||||
const {direction} = localeConfigs[currentLocale]!;
|
const {direction} = localeConfigs[currentLocale]!;
|
||||||
|
const prismBaseStyles = {
|
||||||
|
':root': getPrismCssVariables(theme),
|
||||||
|
'[data-theme="dark"]': getPrismCssVariables(darkTheme),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'docusaurus-theme-classic',
|
name: 'docusaurus-theme-classic',
|
||||||
|
@ -201,6 +223,17 @@ ${noFlashColorMode(colorMode)}
|
||||||
${announcementBar ? AnnouncementBarInlineJavaScript : ''}
|
${announcementBar ? AnnouncementBarInlineJavaScript : ''}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
tagName: 'style',
|
||||||
|
innerHTML: Object.entries(prismBaseStyles)
|
||||||
|
.map(
|
||||||
|
([selector, properties]) =>
|
||||||
|
`${selector} {${Object.entries(properties)
|
||||||
|
.map(([name, value]) => `${name}:${value};`)
|
||||||
|
.join('')}}`,
|
||||||
|
)
|
||||||
|
.join(' '),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,24 +7,17 @@
|
||||||
|
|
||||||
import React, {type ComponentProps} from 'react';
|
import React, {type ComponentProps} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {ThemeClassNames} from '@docusaurus/theme-common';
|
||||||
usePrismTheme,
|
|
||||||
getPrismCssVariables,
|
|
||||||
ThemeClassNames,
|
|
||||||
} from '@docusaurus/theme-common';
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function CodeBlockContainer<T extends 'div' | 'pre'>({
|
export default function CodeBlockContainer<T extends 'div' | 'pre'>({
|
||||||
as: As,
|
as: As,
|
||||||
...props
|
...props
|
||||||
}: {as: T} & ComponentProps<T>): JSX.Element {
|
}: {as: T} & ComponentProps<T>): JSX.Element {
|
||||||
const prismTheme = usePrismTheme();
|
|
||||||
const prismCssVariables = getPrismCssVariables(prismTheme);
|
|
||||||
return (
|
return (
|
||||||
<As
|
<As
|
||||||
// Polymorphic components are hard to type, without `oneOf` generics
|
// Polymorphic components are hard to type, without `oneOf` generics
|
||||||
{...(props as never)}
|
{...(props as never)}
|
||||||
style={prismCssVariables}
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
props.className,
|
props.className,
|
||||||
styles.codeBlockContainer,
|
styles.codeBlockContainer,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type {Props} from '@theme/CodeBlock/Line';
|
import type {Props} from '@theme/CodeBlock/Line';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||||
|
|
||||||
export default function CodeBlockLine({
|
export default function CodeBlockLine({
|
||||||
line,
|
line,
|
||||||
|
@ -17,6 +18,8 @@ export default function CodeBlockLine({
|
||||||
getLineProps,
|
getLineProps,
|
||||||
getTokenProps,
|
getTokenProps,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
|
const isBrowser = useIsBrowser();
|
||||||
|
|
||||||
if (line.length === 1 && line[0]!.content === '\n') {
|
if (line.length === 1 && line[0]!.content === '\n') {
|
||||||
line[0]!.content = '';
|
line[0]!.content = '';
|
||||||
}
|
}
|
||||||
|
@ -27,11 +30,15 @@ export default function CodeBlockLine({
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineTokens = line.map((token, key) => (
|
const lineTokens = line.map((token, key) => (
|
||||||
<span key={key} {...getTokenProps({token, key})} />
|
<span
|
||||||
|
key={key}
|
||||||
|
{...getTokenProps({token, key})}
|
||||||
|
{...(!isBrowser && {style: undefined})}
|
||||||
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span {...lineProps}>
|
<span {...lineProps} {...(!isBrowser && {style: undefined})}>
|
||||||
{showLineNumbers ? (
|
{showLineNumbers ? (
|
||||||
<>
|
<>
|
||||||
<span className={styles.codeLineNumber} />
|
<span className={styles.codeLineNumber} />
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {isValidElement, type ReactNode} from 'react';
|
import React, {isValidElement, type ReactNode} from 'react';
|
||||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
||||||
import type {Props} from '@theme/CodeBlock';
|
import type {Props} from '@theme/CodeBlock';
|
||||||
import ElementContent from '@theme/CodeBlock/Content/Element';
|
import ElementContent from '@theme/CodeBlock/Content/Element';
|
||||||
import StringContent from '@theme/CodeBlock/Content/String';
|
import StringContent from '@theme/CodeBlock/Content/String';
|
||||||
|
@ -29,17 +28,8 @@ export default function CodeBlock({
|
||||||
children: rawChildren,
|
children: rawChildren,
|
||||||
...props
|
...props
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
// The Prism theme on SSR is always the default theme but the site theme can
|
|
||||||
// be in a different mode. React hydration doesn't update DOM styles that come
|
|
||||||
// from SSR. Hence force a re-render after mounting to apply the current
|
|
||||||
// relevant styles.
|
|
||||||
const isBrowser = useIsBrowser();
|
|
||||||
const children = maybeStringifyChildren(rawChildren);
|
const children = maybeStringifyChildren(rawChildren);
|
||||||
const CodeBlockComp =
|
const CodeBlockComp =
|
||||||
typeof children === 'string' ? StringContent : ElementContent;
|
typeof children === 'string' ? StringContent : ElementContent;
|
||||||
return (
|
return <CodeBlockComp {...props}>{children as string}</CodeBlockComp>;
|
||||||
<CodeBlockComp key={String(isBrowser)} {...props}>
|
|
||||||
{children as string}
|
|
||||||
</CodeBlockComp>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ export const DEFAULT_CONFIG = {
|
||||||
prism: {
|
prism: {
|
||||||
additionalLanguages: [],
|
additionalLanguages: [],
|
||||||
theme: defaultPrismTheme,
|
theme: defaultPrismTheme,
|
||||||
|
darkTheme: defaultPrismTheme,
|
||||||
magicComments: [
|
magicComments: [
|
||||||
{
|
{
|
||||||
className: 'theme-code-block-highlighted-line',
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
|
|
@ -16,9 +16,7 @@ import {useThemeConfig} from '../utils/useThemeConfig';
|
||||||
export function usePrismTheme(): PrismTheme {
|
export function usePrismTheme(): PrismTheme {
|
||||||
const {prism} = useThemeConfig();
|
const {prism} = useThemeConfig();
|
||||||
const {colorMode} = useColorMode();
|
const {colorMode} = useColorMode();
|
||||||
const lightModeTheme = prism.theme;
|
const prismTheme = colorMode === 'dark' ? prism.darkTheme : prism.theme;
|
||||||
const darkModeTheme = prism.darkTheme || lightModeTheme;
|
|
||||||
const prismTheme = colorMode === 'dark' ? darkModeTheme : lightModeTheme;
|
|
||||||
|
|
||||||
return prismTheme;
|
return prismTheme;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ export {
|
||||||
parseLanguage,
|
parseLanguage,
|
||||||
parseLines,
|
parseLines,
|
||||||
containsLineNumbers,
|
containsLineNumbers,
|
||||||
getPrismCssVariables,
|
|
||||||
} from './utils/codeBlockUtils';
|
} from './utils/codeBlockUtils';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import rangeParser from 'parse-numeric-range';
|
import rangeParser from 'parse-numeric-range';
|
||||||
import type {PrismTheme} from 'prism-react-renderer';
|
|
||||||
import type {CSSProperties} from 'react';
|
|
||||||
|
|
||||||
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
||||||
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
||||||
|
@ -231,19 +229,3 @@ export function parseLines(
|
||||||
});
|
});
|
||||||
return {lineClassNames, code};
|
return {lineClassNames, code};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
|
|
||||||
const mapping: {[name: keyof PrismTheme['plain']]: string} = {
|
|
||||||
color: '--prism-color',
|
|
||||||
backgroundColor: '--prism-background-color',
|
|
||||||
};
|
|
||||||
|
|
||||||
const properties: {[key: string]: string} = {};
|
|
||||||
Object.entries(prismTheme.plain).forEach(([key, value]) => {
|
|
||||||
const varName = mapping[key];
|
|
||||||
if (varName && typeof value === 'string') {
|
|
||||||
properties[varName] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ export type AnnouncementBarConfig = {
|
||||||
|
|
||||||
export type PrismConfig = {
|
export type PrismConfig = {
|
||||||
theme: PrismTheme;
|
theme: PrismTheme;
|
||||||
darkTheme?: PrismTheme;
|
darkTheme: PrismTheme;
|
||||||
defaultLanguage?: string;
|
defaultLanguage?: string;
|
||||||
additionalLanguages: string[];
|
additionalLanguages: string[];
|
||||||
magicComments: MagicCommentConfig[];
|
magicComments: MagicCommentConfig[];
|
||||||
|
|
Loading…
Add table
Reference in a new issue