-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
perf: lazy-load Mermaid, YouTube, and TwitterTweetEmbed (#5667, #3186) #5679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
b1b7dfb
815e7a8
4d0b933
fdbf6b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import { useEffect, useRef } from 'react'; | ||||||||||||||||||||||||||||||
| import mermaidAPI from 'mermaid'; | ||||||||||||||||||||||||||||||
| import type { CSSProperties } from 'react'; | ||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| interface MermaidDiagramProps { | ||||||||||||||||||||||||||||||
| chart: string; | ||||||||||||||||||||||||||||||
| id?: string; | ||||||||||||||||||||||||||||||
| style?: CSSProperties; | ||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Security: sanitize diagram content for safe rendering | ||||||||||||||||||||||||||||||
| const sanitizeDiagram = (code: string): string => { | ||||||||||||||||||||||||||||||
| // Strip <script> tags and event handlers to prevent XSS | ||||||||||||||||||||||||||||||
| return code | ||||||||||||||||||||||||||||||
| .replace(/<script[\s\S]*?<\/script>/gi, '') | ||||||||||||||||||||||||||||||
| .replace(/\bon\w+\s*=/gi, 'data-blocked='); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const getMermaidThemeVariables = (isDark: boolean) => ({ | ||||||||||||||||||||||||||||||
| darkMode: isDark, | ||||||||||||||||||||||||||||||
| theme: isDark ? 'dark' : 'default', | ||||||||||||||||||||||||||||||
| themeVariables: isDark | ||||||||||||||||||||||||||||||
| ? { | ||||||||||||||||||||||||||||||
| primaryColor: '#4a9eff', | ||||||||||||||||||||||||||||||
| primaryTextColor: '#f0f0f0', | ||||||||||||||||||||||||||||||
| primaryBorderColor: '#555', | ||||||||||||||||||||||||||||||
| lineColor: '#aaa', | ||||||||||||||||||||||||||||||
| secondaryColor: '#2d2d2d', | ||||||||||||||||||||||||||||||
| tertiaryColor: '#1a1a1a', | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| : { | ||||||||||||||||||||||||||||||
| primaryColor: '#2b6cb0', | ||||||||||||||||||||||||||||||
| primaryTextColor: '#333', | ||||||||||||||||||||||||||||||
| primaryBorderColor: '#ccc', | ||||||||||||||||||||||||||||||
| lineColor: '#666', | ||||||||||||||||||||||||||||||
| secondaryColor: '#f5f5f5', | ||||||||||||||||||||||||||||||
| tertiaryColor: '#e8e8e8', | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function MermaidDiagram({ | ||||||||||||||||||||||||||||||
| chart, | ||||||||||||||||||||||||||||||
| id = 'mermaid-diagram', | ||||||||||||||||||||||||||||||
| style, | ||||||||||||||||||||||||||||||
| className, | ||||||||||||||||||||||||||||||
| }: MermaidDiagramProps) { | ||||||||||||||||||||||||||||||
|
Check warning on line 49 in components/MDX/MermaidDiagram.tsx
|
||||||||||||||||||||||||||||||
| const containerRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||
| const renderedRef = useRef(false); | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Give each diagram a unique id.
Derive the default from ♻️ Proposed fix-import { useEffect, useRef } from 'react';
+import { useEffect, useId, useRef } from 'react';
@@
-export default function MermaidDiagram({
- chart,
- id = 'mermaid-diagram',
- style,
- className,
-}: MermaidDiagramProps) {
+export default function MermaidDiagram({ graph, id, style, className }: Readonly<MermaidDiagramProps>) {
+ const reactId = useId();
+ const diagramId = id ?? `mermaid-${reactId.replace(/:/g, '')}`;
const containerRef = useRef<HTMLDivElement>(null);Then use 🧰 Tools🪛 ESLint[error] 44-49: Replace (prettier/prettier) 🪛 GitHub Check: SonarCloud Code Analysis[warning] 44-49: Mark the props of the component as read-only. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||
| if (!containerRef.current || renderedRef.current) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const isDark = | ||||||||||||||||||||||||||||||
| typeof window !== 'undefined' && | ||||||||||||||||||||||||||||||
| document.documentElement.getAttribute('data-theme') === 'dark'; | ||||||||||||||||||||||||||||||
|
Check failure on line 58 in components/MDX/MermaidDiagram.tsx
|
||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const renderDiagram = async () => { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| const sanitized = sanitizeDiagram(chart); | ||||||||||||||||||||||||||||||
| const svg = await mermaidAPI.render(`${id}-svg`, sanitized); | ||||||||||||||||||||||||||||||
| if (containerRef.current) { | ||||||||||||||||||||||||||||||
| containerRef.current.innerHTML = svg; | ||||||||||||||||||||||||||||||
| renderedRef.current = true; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check the declared and locked mermaid version, and existing render call sites.
fd -H -t f 'package.json' -d 2 --exec grep -n '"mermaid"' {} \;
fd -H -t f 'package-lock.json' -d 1 --exec grep -n -A3 '"node_modules/mermaid"' {} \;
rg -nP '\bmermaid(API)?\.(render|initialize)\s*\(' --type=ts --type=tsx --type=jsRepository: asyncapi/website Length of output: 487 🌐 Web query:
💡 Result: In Mermaid version 9.3.0, the mermaid.render function returns a Promise [1][2]. This Promise resolves to a RenderResult object [1][2][3], which contains the following properties: - svg (string): The generated SVG code [1][4]. - bindFunctions (function, optional): A function that, when called with an element as an argument, binds necessary event listeners (like tooltips or clicks) to the rendered diagram [3][5][4]. - diagramType (string): The type of diagram that was rendered (e.g., 'flowchart', 'sequence') [1][4]. Usage example: const { svg, bindFunctions, diagramType } = await mermaid.render('myId', 'graph TD; A-->B'); document.getElementById('output').innerHTML = svg; bindFunctions?.(document.getElementById('output')); Note that mermaid.render in version 9.3.0 handles the queueing of rendering tasks, ensuring that multiple calls are executed serially to prevent race conditions, unlike the internal mermaidAPI.render method which executes immediately [2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "repo files count:"
git ls-files | wc -l
echo
echo "find mermaid-related package files:"
git ls-files | rg '(^|/)(package\.json|package-lock\.json|yarn.lock|pnpm-lock.yaml)$' || true
echo
echo "mermaid mentions in package manifests/locks:"
for f in $(git ls-files | rg '(^|/)(package\.json|package-lock\.json|yarn.lock|pnpm-lock.yaml)$'); do
printf '\n--- %s ---\n' "$f"
rg -n '"mermaid"|mermaid' "$f" | head -80 || true
done
echo
echo "MermaidDiagram outline and relevant source:"
if git ls-files | rg -q 'components/MDX/MermaidDiagram.tsx'; then
wc -l components/MDX/MermaidDiagram.tsx
cat -n components/MDX/MermaidDiagram.tsx | sed -n '1,180p'
else
fd -i 'MermaidDiagram' .
fi
echo
echo "render call sites in tracked TS/TSX/JS files:"
git ls-files | rg '\.(ts|tsx|js|jsx)$' | xargs -r rg -n '\bmermaid(API)?\.(render|initialize)\s*\(' || trueRepository: asyncapi/website Length of output: 1084 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "package manager indicators:"
git ls-files | rg '(^|/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb|bun\.lock)$' || true
for name in npm_package_lockfile npm yarn pnpm bun; do
[ -f package.json ] || continue
python3 - <<'PY'
import json
from pathlib import Path
p=json.loads(Path("package.json").read_text())
print("name:", p.get("name"))
print("engines:", p.get("engines"))
print("volta:", p.get("volta"))
print("packageManager:", p.get("packageManager"))
print("workspaces:", p.get("workspaces"))
PY
done
echo
echo "Mermaid API import/export shape in lockfile:"
sed -n '20740,20755p' package-lock.json
echo
echo "Import statement:"
sed -n '1,20p' components/MDX/MermaidDiagram.tsxRepository: asyncapi/website Length of output: 1823 🌐 Web query:
💡 Result: In Mermaid version 9.3.0, the mermaidAPI.render function is available but is marked as deprecated for external use in favor of the high-level mermaid.render function [1][2]. TypeScript Usage and Characteristics: 1. Deprecation Status: The mermaidAPI.render function includes a Citations:
🌐 Web query:
💡 Result: In Mermaid v9.3.0, the mermaidAPI.render function is available but is marked as deprecated for external use [1][2][3]. The official recommendation is to use the mermaid.render function instead [1][2][4][3]. The mermaidAPI.render function was historically used to render diagram text into an SVG element [5][6]. However, it is explicitly flagged as deprecated in the source code of v9.3.0 with the following guidance: "use the mermaid.render function instead of mermaid.mermaidAPI.render" [1][2][3]. Key considerations for v9.3.0: 1. Recommended Practice: Developers are encouraged to use the top-level mermaid.render function. This is because the top-level function provides safer handling, such as wrapping rendering calls in an asynchronous queue to prevent conflicts, whereas direct calls to mermaidAPI.render bypass these safeguards [4]. 2. Deprecation Status: While mermaidAPI.render exists in v9.3.0, it is considered internal or legacy, and reliance on it is discouraged in favor of the standard API [1][2]. 3. Usage Pattern (Legacy): Historically, mermaidAPI.render took parameters including an ID, the graph definition text, and a callback function (or an optional SVG containing element) [5][6]. Modern usage with the mermaid.render function is preferred for better compatibility and stability [4]. Citations:
Use the non-deprecated Mermaid render API. The repository pins Mermaid 9.3.0, where 🧰 Tools🪛 ESLint[error] 63-63: Expected blank line after variable declarations. (newline-after-var) [error] 64-67: Expected blank line before this statement. (padding-line-between-statements) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| if (containerRef.current) { | ||||||||||||||||||||||||||||||
| containerRef.current.innerHTML = `<pre style="color:red;padding:1rem;border:1px solid red;border-radius:4px;">Mermaid render error: ${error instanceof Error ? error.message : String(error)}</pre>`; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Do not interpolate the error message into Mermaid parse errors include fragments of the diagram source. That source comes from MDX content. Interpolating it into Build the error node with DOM APIs and set the text with 🔒️ Proposed fix } catch (error) {
if (containerRef.current) {
- containerRef.current.innerHTML = `<pre style="color:red;padding:1rem;border:1px solid red;border-radius:4px;">Mermaid render error: ${error instanceof Error ? error.message : String(error)}</pre>`;
+ const pre = document.createElement('pre');
+
+ pre.className = 'text-red-600 p-4 border border-red-600 rounded';
+ pre.textContent = `Mermaid render error: ${error instanceof Error ? error.message : String(error)}`;
+ containerRef.current.replaceChildren(pre);
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Initialize mermaid once | ||||||||||||||||||||||||||||||
| const initAndRender = async () => { | ||||||||||||||||||||||||||||||
| mermaidAPI.initialize({ | ||||||||||||||||||||||||||||||
| startOnLoad: false, | ||||||||||||||||||||||||||||||
| securityLevel: 'strict', | ||||||||||||||||||||||||||||||
| ...getMermaidThemeVariables(isDark), | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| await renderDiagram(); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| initAndRender(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Observe theme changes for light/dark switching | ||||||||||||||||||||||||||||||
| const observer = new MutationObserver(() => { | ||||||||||||||||||||||||||||||
| const newIsDark = | ||||||||||||||||||||||||||||||
| document.documentElement.getAttribute('data-theme') === 'dark'; | ||||||||||||||||||||||||||||||
|
Check failure on line 90 in components/MDX/MermaidDiagram.tsx
|
||||||||||||||||||||||||||||||
| mermaidAPI.initialize({ | ||||||||||||||||||||||||||||||
| startOnLoad: false, | ||||||||||||||||||||||||||||||
| securityLevel: 'strict', | ||||||||||||||||||||||||||||||
| ...getMermaidThemeVariables(newIsDark), | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| renderedRef.current = false; | ||||||||||||||||||||||||||||||
| renderDiagram(); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| observer.observe(document.documentElement, { | ||||||||||||||||||||||||||||||
| attributes: true, | ||||||||||||||||||||||||||||||
| attributeFilter: ['data-theme'], | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return () => observer.disconnect(); | ||||||||||||||||||||||||||||||
| }, [chart, id]); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||
| ref={containerRef} | ||||||||||||||||||||||||||||||
| id={id} | ||||||||||||||||||||||||||||||
| style={style} | ||||||||||||||||||||||||||||||
| className={className} | ||||||||||||||||||||||||||||||
| aria-label="Mermaid diagram" | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.