Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 17 additions & 163 deletions components/MDX/MDX.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { MDXProvider as CoreMDXProvider } from '@mdx-js/react';
import mermaid from 'mermaid';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import React, { useEffect, useId, useState } from 'react';

Check warning on line 4 in components/MDX/MDX.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'useState'.

See more on https://sonarcloud.io/project/issues?id=asyncapi_website&issues=AZ-_vuA__sm3M2mr0vcg&open=AZ-_vuA__sm3M2mr0vcg&pullRequest=5679

Check warning on line 4 in components/MDX/MDX.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'useEffect'.

See more on https://sonarcloud.io/project/issues?id=asyncapi_website&issues=AZ-_vuA__sm3M2mr0vcf&open=AZ-_vuA__sm3M2mr0vcf&pullRequest=5679
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
import {
TwitterDMButton,
TwitterFollowButton,
TwitterHashtagButton,
TwitterMentionButton,
TwitterMomentShare,
TwitterOnAirButton,
TwitterShareButton,
TwitterTimelineEmbed,
TwitterTweetEmbed,
TwitterVideoEmbed
} from 'react-twitter-embed';
import YouTube from 'react-youtube-embed';

// Lazy-loaded heavy dependencies via next/dynamic (ssr: false).
// MermaidDiagram: ~1.5MB (isolated in ../MermaidDiagram.tsx)
// react-twitter-embed + react-youtube-embed: ~300KB each
// Only downloaded when a page actually uses diagrams, Twitter embeds,
// or YouTube videos — reducing initial JS bundle by ~1.8MB on typical MDX pages.
// Fixes #5667, #3186.

const MermaidDiagram = dynamic(() => import('./MermaidDiagram').then(mod => ({ default: mod.default })), { ssr: false });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const TwitterTweetEmbed = dynamic(
() => import('react-twitter-embed').then((mod) => ({ default: mod.TwitterTweetEmbed })),
{ ssr: false }
);

const YouTube = dynamic(() => import('react-youtube-embed'), { ssr: false });

import Asyncapi3ChannelComparison from '../Asyncapi3Comparison/Asyncapi3ChannelComparison';
import Asyncapi3IdAndAddressComparison from '../Asyncapi3Comparison/Asyncapi3IdAndAddressComparison';
Expand All @@ -41,146 +44,6 @@
import Warning from '../Warning';
import { Table, TableBody, TableCell, TableHeader, TableRow, Thead } from './MDXTable';

type MermaidTheme = 'light' | 'dark';

const MERMAID_THEME_VARIABLES: Record<MermaidTheme, Record<string, string>> = {
light: {
primaryColor: '#EDFAFF',
primaryBorderColor: '#47BCEE',
secondaryColor: '#F4EFFC',
secondaryBorderColor: '#875AE2',
fontFamily: 'Inter, sans-serif',
fontSize: '18px',
primaryTextColor: '#242929',
tertiaryColor: '#F7F9FA',
tertiaryBorderColor: '#BFC6C7',
lineColor: '#BFC6C7',
mainBkg: '#EDFAFF',
secondBkg: '#F4EFFC',
tertiaryBkg: '#F7F9FA',
clusterBkg: '#F7F9FA',
clusterBorder: '#BFC6C7',
edgeLabelBackground: '#FFFFFF'
},
dark: {
primaryColor: '#1E293B',
primaryBorderColor: '#38BDF8',
secondaryColor: '#2E2459',
secondaryBorderColor: '#A87EFC',
fontFamily: 'Inter, sans-serif',
fontSize: '18px',
primaryTextColor: '#F8FAFC',
tertiaryColor: '#121825',
tertiaryBorderColor: '#475569',
lineColor: '#94A3B8',
mainBkg: '#1E293B',
secondBkg: '#2E2459',
tertiaryBkg: '#121825',
clusterBkg: '#121825',
clusterBorder: '#475569',
edgeLabelBackground: '#1E293B'
}
};

// Cache the theme Mermaid was initialized with across client-side page transitions.
let initializedMermaidTheme: MermaidTheme | null = null;

/**
* @description Returns the Mermaid theme that matches the current website theme.
*/
function getMermaidTheme(): MermaidTheme {
if (typeof document === 'undefined') {
return 'light';
}

return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
}

/**
* @description Initializes the Mermaid library for the selected theme.
*/
function initializeMermaid(theme: MermaidTheme) {
if (initializedMermaidTheme === theme) {
return;
}

initializedMermaidTheme = theme;
mermaid.initialize({
startOnLoad: false,
theme: 'base',
securityLevel: 'strict',
// Keep Mermaid styling fully controlled by MERMAID_THEME_VARIABLES.
themeCSS: '',
themeVariables: MERMAID_THEME_VARIABLES[theme]
});
}

let currentId = 0;

/**
* @description Generates a unique identifier.
* @returns {string} - A unique identifier.
*/
const uuid = (): string => `mermaid-${(currentId++).toString()}`;

interface MermaidDiagramProps {
graph: string;
}

/**
* @description This component renders Mermaid diagrams.
*
* @param {MermaidDiagramProps} props - The props for the MermaidDiagram component.
* @param {string} props.graph - The Mermaid graph to render.
*/
function MermaidDiagram({ graph }: Readonly<MermaidDiagramProps>) {
const [svg, setSvg] = useState<string | null>(null);
const [theme, setTheme] = useState<MermaidTheme>('light');

useEffect(() => {
setTheme(getMermaidTheme());

const observer = new MutationObserver(() => {
setTheme(getMermaidTheme());
});

observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });

return () => observer.disconnect();
}, []);

/**
* @description Renders the Mermaid diagram.
*/
useEffect(() => {
let mounted = true;

if (graph) {
try {
initializeMermaid(theme);
mermaid.mermaidAPI.render(uuid(), graph.trim(), (svgGraph) => {
if (mounted) {
setSvg(svgGraph);
}
});
} catch (e) {
if (mounted) {
setSvg(null);
}
// eslint-disable-next-line no-console
console.error(e);
}
} else {
setSvg(null);
}

return () => {
mounted = false;
};
}, [graph, theme]);

return <div dangerouslySetInnerHTML={{ __html: svg || '' }} />;
}

interface CodeComponentProps {
children: string;
Expand Down Expand Up @@ -471,16 +334,7 @@
DocsCards,
GeneratorInstallation,
NewsletterSubscribe,
TwitterTimelineEmbed,
TwitterShareButton,
TwitterFollowButton,
TwitterHashtagButton,
TwitterMentionButton,
TwitterTweetEmbed,
TwitterMomentShare,
TwitterDMButton,
TwitterVideoEmbed,
TwitterOnAirButton,
Profiles,
Visualizer
});
Expand Down
152 changes: 152 additions & 0 deletions components/MDX/MermaidDiagram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use client';

import mermaid from 'mermaid';
import React, { useEffect, useState } from 'react';

type MermaidTheme = 'light' | 'dark';

const MERMAID_THEME_VARIABLES: Record<MermaidTheme, Record<string, string>> = {
light: {
primaryColor: '#EDFAFF',
primaryBorderColor: '#47BCEE',
secondaryColor: '#F4EFFC',
secondaryBorderColor: '#875AE2',
fontFamily: 'Inter, sans-serif',
fontSize: '18px',
primaryTextColor: '#242929',
tertiaryColor: '#F7F9FA',
tertiaryBorderColor: '#BFC6C7',
lineColor: '#BFC6C7',
mainBkg: '#EDFAFF',
secondBkg: '#F4EFFC',
tertiaryBkg: '#F7F9FA',
clusterBkg: '#F7F9FA',
clusterBorder: '#BFC6C7',
edgeLabelBackground: '#FFFFFF'
},
dark: {
primaryColor: '#1E293B',
primaryBorderColor: '#38BDF8',
secondaryColor: '#2E2459',
secondaryBorderColor: '#A87EFC',
fontFamily: 'Inter, sans-serif',
fontSize: '18px',
primaryTextColor: '#F8FAFC',
tertiaryColor: '#121825',
tertiaryBorderColor: '#475569',
lineColor: '#94A3B8',
mainBkg: '#1E293B',
secondBkg: '#2E2459',
tertiaryBkg: '#121825',
clusterBkg: '#121825',
clusterBorder: '#475569',
edgeLabelBackground: '#1E293B'
}
};

// Cache the theme Mermaid was initialized with across client-side page transitions.
let initializedMermaidTheme: MermaidTheme | null = null;

/**
* @description Returns the Mermaid theme that matches the current website theme.
*/
function getMermaidTheme(): MermaidTheme {
if (typeof document === 'undefined') {
return 'light';
}

return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
}

/**
* @description Initializes the Mermaid library for the selected theme.
*/
function initializeMermaid(theme: MermaidTheme) {
if (initializedMermaidTheme === theme) {
return;
}

initializedMermaidTheme = theme;
mermaid.initialize({
startOnLoad: false,
theme: 'base',
// Keep Mermaid styling fully controlled by MERMAID_THEME_VARIABLES.
themeCSS: '',
themeVariables: MERMAID_THEME_VARIABLES[theme]
});
}

let currentId = 0;

/**
* @description Generates a unique identifier.
* @returns {string} - A unique identifier.
*/
const uuid = (): string => `mermaid-${(currentId++).toString()}`;

interface MermaidDiagramProps {
graph: string;
}

/**
* @description This component renders Mermaid diagrams.
* Extracted from MDX.tsx to enable lazy-loading via next/dynamic,
* removing ~1.5 MB of Mermaid from the initial JavaScript bundle
* on pages that don't contain diagrams.
*
* @param {MermaidDiagramProps} props - The props for the MermaidDiagram component.
* @param {string} props.graph - The Mermaid graph to render.
*/
function MermaidDiagram({ graph }: Readonly<MermaidDiagramProps>) {
const [svg, setSvg] = useState<string | null>(null);
const [theme, setTheme] = useState<MermaidTheme>('light');

useEffect(() => {
setTheme(getMermaidTheme());

const observer = new MutationObserver(() => {
setTheme(getMermaidTheme());
});

observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });

return () => observer.disconnect();
}, []);

/**
* @description Renders the Mermaid diagram.
*/
useEffect(() => {
let mounted = true;

if (graph) {
try {
initializeMermaid(theme);
mermaid.mermaidAPI.render(uuid(), graph.trim(), (svgGraph) => {
if (mounted) {
setSvg(svgGraph);
}
});
} catch (e) {
if (mounted) {
setSvg(null);
}
// eslint-disable-next-line no-console
console.error(e);
}
} else {
setSvg(null);
}

return () => {
mounted = false;
};
}, [graph, theme]);

return <div dangerouslySetInnerHTML={{ __html: svg || '' }} />;
}

// Named export mirrors the original inline component name in MDX.tsx.
// Default export required by next/dynamic.
export { MermaidDiagram };
export default MermaidDiagram;
Loading