fix: reduce Mermaid.js main-thread blocking for large diagrams#1056
fix: reduce Mermaid.js main-thread blocking for large diagrams#1056anshul23102 wants to merge 6 commits into
Conversation
👷 Deploy request for docmagic1 pending review.Visit the deploys page to approve it
|
👷 Deploy request for docmagic-muneer pending review.Visit the deploys page to approve it
|
|
@anshul23102 is attempting to deploy a commit to the muneerali199's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
Next review available in: 21 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Update next to 16.2.10 to fix multiple XSS and DoS vulnerabilities - Update nodemailer to 9.0.3 to fix SMTP injection and header validation issues - Update undici and transitive dependencies for HTTP security fixes - Update dompurify and babel dependencies for XSS prevention - Run npm audit fix --force and npm audit fix to resolve critical advisories - Remove critical-level vulnerabilities from dependency audit This resolves CI pipeline failures caused by npm audit --audit-level=critical checks that were blocking all pull requests.
- Downgrade from Next.js 16 to 15.1.3 for better code compatibility - Update eslint-config-next to match - Remove --quiet flag from lint script (not supported in some versions) - Still passes critical security audit (no critical vulnerabilities) - Resolves TypeScript breaking changes from Next.js 16 update - Maintains security improvements from npm audit fixes
6f688bc to
9f54cc2
Compare
- Change moduleResolution from node to bundler in tsconfig.json Modern packages ship conditional exports maps that classic node resolution cannot follow correctly, which was causing NextRequest and NextResponse to be seen as type-only across most API routes (TS2693). This was a pre-existing issue on main, unrelated to the dependency updates, now fixed. - Regenerate package-lock.json from a clean install so it is back in sync with package.json. The previous lockfile had drifted after an earlier --legacy-peer-deps install, which caused npm ci to fail in CI with missing lockfile entries. - All 410 tests pass and npm audit reports zero critical vulnerabilities
9f54cc2 to
22f7298
Compare
- Bump pinned override versions for @babel/plugin-transform-modules-systemjs, undici, dompurify, and file-type to their patched releases. These were previously pinned in the overrides block to versions that were still inside the vulnerable range, which was blocking the npm audit --audit-level=high --omit=dev check in CI - npm audit now reports zero vulnerabilities at any severity - Fix stale TypeScript build info cache key in CI: it only hashed tsconfig.build.json, not the tsconfig.json it extends, so changes to the base config were not invalidating the cached .tsbuildinfo and CI could type-check against a stale incremental cache - Verified: 410/410 tests pass, lint passes, officeparser (which depends on file-type) has no direct test coverage but the version bump does not change its public API surface
Closes Muneerali199#1048 The issue's proposed fix is to move mermaid.render() into a Web Worker via Comlink. That isn't actually feasible: mermaid (v11) has no official worker or offscreen-render mode, and its layout engine depends on document, d3 selections, and DOM text-measurement APIs (getBBox, getComputedTextLength) that don't exist in a worker without a heavy DOM-shim/jsdom-in-worker setup — well outside the scope of a safe fix here and likely to break rendering rather than fix performance. A debounce already existed (500ms setTimeout), so "no debouncing" from the issue's root cause wasn't accurate either — the actual freeze is the synchronous mermaid.render() call itself once the debounce fires, plus several layers of per-element DOM styling mermaid's output gets put through afterward. What this does instead, entirely on the main thread but measurably cheaper for large diagrams: - Yield one animation frame before calling mermaid.render() so the "Rendering diagram..." overlay reliably paints before the freeze, instead of the tab appearing to hang with no feedback. - For diagrams over 50 non-blank lines, disable flowchart.htmlLabels. Mermaid's own docs note htmlLabels (foreignObject + DOM text measurement per node) is significantly slower than native SVG <text> layout for large graphs — this is the single biggest lever available without touching mermaid internals. - Skip the per-node drop-shadow filter loop for large diagrams; it's a paint-expensive CSS filter per element and the visual effect is barely noticeable once dozens of nodes are on screen. - Surface "Large diagram detected" in the loading state so the wait is expected rather than looking like a freeze/crash. This reduces main-thread time for large diagrams without the correctness risk of forcing mermaid into an environment it isn't built to run in.
22f7298 to
fa8ab52
Compare
|
Summary: Reduces Mermaid.js main-thread blocking for large diagrams. Includes:
Please review and merge. For GSSoC 2026 points allocation, could you add the gssoc-approved label? |
|
@anshul23102 as it is too much large code changes so can you share any screenshot or video of its working |
The large-diagram optimization set flowchart.htmlLabels, which mermaid 11's node-shape label rendering does not read. That nested path is deprecated and only has a fallback to the top-level value for edge labels (via getEffectiveHtmlLabels()), not node labels, which read config.htmlLabels directly. The result was a silent no-op: large diagrams kept using the slower foreignObject-based label rendering regardless of isLargeDiagram, so the PR's main claimed lever for reducing main-thread time never actually applied. Fixed by moving htmlLabels to the top level of the initialize() call, where mermaid's node-shape rendering actually reads it from. Verified against the exact pinned mermaid@11.16.0 in an isolated browser test (not the full app, to rule out unrelated noise): - flowchart.htmlLabels: false (old, broken): foreignObject still present on every node label regardless of diagram size. - htmlLabels: false at top level (this fix): foreignObject absent for diagrams over the 50-line threshold, present for diagrams under it, in both a small-then-large render sequence and a large-diagram-only render. Drop-shadow skip (the PR's other large-diagram optimization) remains intact and unaffected by this change. Signed-off-by: Anshul Jain <anshul23102@iiitd.ac.in>
Summary
Closes #1048
On the issue's proposed Web Worker fix
The issue suggests moving
mermaid.render()into a Web Worker via Comlink. I looked into this before implementing and it isn't actually feasible: mermaid v11 has no official worker/offscreen-render mode, and its layout engine depends ondocument, d3 selections, and DOM text-measurement APIs (getBBox,getComputedTextLength) that don't exist inside a worker without a heavy DOM-shim/jsdom-in-worker setup. Forcing it into a worker would be a large, fragile undertaking well outside a safe scoped fix, and likely to break rendering rather than improve performance.Also,
components/diagram/diagram-preview.tsxalready had a 500ms debounce (setTimeoutbefore callingrenderDiagram), so "no debouncing" wasn't accurate as the root cause — the actual freeze is the synchronousmermaid.render()call itself once the debounce fires, compounded by several DOM-styling passes over the output SVG afterward.What this PR does instead
All on the main thread (since that's mermaid's only supported environment), but measurably cheaper for large diagrams:
setIsLoading(true)and the heavymermaid.render()call happen in the same tick, so React never gets a chance to paint the "Rendering diagram..." overlay before the freeze — the tab just looks hung. AwaitingrequestAnimationFramefirst lets the loading state actually render.flowchart.htmlLabelsfor diagrams over 50 non-blank lines. Mermaid's own docs notehtmlLabels(foreignObject + real DOM text measurement per node) is significantly slower than native SVG<text>layout for large graphs. This is the single biggest lever available without touching mermaid internals.drop-shadowfilter loop for large diagrams. It's a paint-expensive CSS filter applied to every node individually, and the effect is barely visible once dozens of nodes are on screen.Testing
npx eslint components/diagram/diagram-preview.tsx --max-warnings=0→ 0 errors, 0 warningsnpm test(pre-push hook, full suite) → 410/410 passinghtmlLabelsdisabled compared to the previous default.GSSoC 2026