Skip to content

SW-2007 Cut heavy eager deps: lazy Plotly, slim Shiki, deferred mermaid/KaTeX#184

Open
owilliams-tetrascience wants to merge 1 commit into
mainfrom
claude/sw-2007-review-d68010
Open

SW-2007 Cut heavy eager deps: lazy Plotly, slim Shiki, deferred mermaid/KaTeX#184
owilliams-tetrascience wants to merge 1 commit into
mainfrom
claude/sw-2007-review-d68010

Conversation

@owilliams-tetrascience

@owilliams-tetrascience owilliams-tetrascience commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes SW-2007: the kit ships preserved ES modules with all dependencies externalized, so a static import of a heavy library lands in every consumer's main chunk. This PR makes the three offenders lazy:

  • Plotly (~4.7 MB, was ~60% of consumer bundles): new src/components/charts/plotly-loader.ts with a memoized dynamic import("plotly.js-dist"); all 12 chart components + ChartTooltip + Chromatogram's plotBuilder now use type-only Plotly imports and draw after loadPlotly() resolves (with cancellation guards; post-draw code paths use the synchronous getLoadedPlotly()). The triple Plotly declaration is consolidated — plotly.js and the never-imported react-plotly.js are removed; plotly.js-dist remains (types still come from @types/plotly.js; the published index.d.ts has zero Plotly references, so consumer typing is unaffected).
  • Shiki — consumers previously got two full copies (~200 grammar modules each): v4 via code-block.tsx and v3 via @streamdown/code inside MessageResponse. New shared slim highlighter src/lib/shiki.ts (shiki/core + JS regex engine + explicit lazy grammar set: bash/javascript/json/markdown/python/sql/tsx/typescript/yaml + aliases, extensible via the newly exported registerCodeBlockLanguage()). code-block.tsx uses it, and a slim custom streamdown plugin (streamdown-code-plugin.ts) replaces @streamdown/code. Unknown languages degrade to plaintext.
  • mermaid + KaTeX (stretch item): MessageResponse/ReasoningContent now obtain streamdown plugins from useStreamdownPlugins(), which dynamic-imports the plugin set — markdown streams immediately; code/math/mermaid rendering upgrades in place when the lazy chunk arrives.

AGENTS.md documents the heavy-deps-must-stay-lazy rule to prevent regressions.

Measured impact (identical minimal consumer fixture — one chart + MessageResponse + CodeBlock — built with Vite 7 at the default Node heap)

origin/main this branch
Consumer main chunk 6.63 MB (1.97 MB gzip) 0.70 MB (0.22 MB gzip)
Chunks emitted 655 68
Total JS emitted 28 MB 9.1 MB

Runtime-verified on the built fixture: main chunk loads first; Plotly, the streamdown plugin chunk, shiki core/engine/themes, and only the grammars actually used are fetched on demand; console clean.

Type of Change

  • Refactor
  • Chore (build, CI, dependencies)

Not marked breaking: the public API is additive (registerCodeBlockLanguage, getSupportedCodeBlockLanguages). Note for reviewers: code highlighting now covers an explicit language set (extensible at runtime) instead of all ~200 bundled grammars — anything outside the set renders as plaintext rather than highlighted.

Checklist

  • yarn lint passes
  • yarn build passes
  • yarn test:all passes (841 unit + 690 storybook)
  • Storybook stories added/updated (chart plays gate their first plot-dependent assertion behind waitFor for the async draw; first story per file gets a 15s timeout for the cold Plotly fetch)
  • Code coverage remains the same or increased

Testing

Existing Zephyr-linked chart/story test cases unchanged (no parameters.zephyr.testCaseId values touched; no assertions changed). Chart unit tests flush the async draw via async act; ChartTooltip/plotBuilder tests prime the loader with await loadPlotly().

Verification

  • Deploys to preview environment for manual verification
  • All CI/E2E checks pass

🤖 Generated with Claude Code

…id/KaTeX

The kit ships preserved ES modules with all deps externalized, so static
imports of heavy libraries land in every consumer's main chunk. On the
identical consumer fixture this cut the main chunk from 6.63 MB (1.97 MB
gzip) to 0.70 MB (0.22 MB gzip) and the build from 655 to 68 chunks at
the default Node heap.

- Plotly: memoized dynamic-import loader (plotly-loader.ts); all charts
  use type-only Plotly imports and draw after loadPlotly() resolves.
  Consolidates the triple declaration: drops plotly.js and unused
  react-plotly.js, keeps plotly.js-dist.
- Shiki: shared slim highlighter (lib/shiki.ts) on shiki/core + JS regex
  engine + explicit lazy grammar set, extensible via new
  registerCodeBlockLanguage(); code-block.tsx uses it, and a slim
  streamdown code plugin replaces @streamdown/code (which statically
  pulled a second full Shiki copy).
- mermaid/KaTeX: MessageResponse/ReasoningContent load streamdown
  plugins through useStreamdownPlugins() (dynamic import), so markdown
  streams immediately and code/math/mermaid upgrade in place.
- Tests: chart unit tests flush the async draw via async act; chart
  story plays gate their first plot assertion behind waitFor.
- AGENTS.md documents the heavy-deps-must-stay-lazy rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 17, 2026 21:31
@owilliams-tetrascience
owilliams-tetrascience requested review from a team as code owners July 17, 2026 21:31
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ts-lib-ui-kit-storybook Ignored Ignored Jul 17, 2026 9:31pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses SW-2007 by moving several heavyweight dependencies (Plotly, Shiki, Streamdown plugin set incl. mermaid/KaTeX) behind dynamic imports so they no longer land in consumers’ main bundle for preserved-ESM builds.

Changes:

  • Introduces a lazy Plotly loader and updates chart components/tests/stories to render asynchronously after Plotly is loaded.
  • Adds a shared “slim Shiki” setup (shiki/core + dynamic grammar/theme imports) and wires it into CodeBlock and a custom Streamdown code plugin.
  • Defers Streamdown plugin loading via useStreamdownPlugins() and removes eager imports of mermaid/math/code plugins from AI components.

Reviewed changes

Copilot reviewed 43 out of 44 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/lib/shiki.ts Adds shared slim Shiki core with dynamic grammar/theme loading and a public language-extension API.
src/index.ts Re-exports Shiki language-extension helpers from the public entrypoint.
src/components/ui/code-block.tsx Switches code highlighting to the shared slim Shiki highlighter.
src/components/charts/StackedChromatogram/StackedChromatogram.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.tsx Lazily loads Plotly before draw; updates cleanup/selection sync for async draw.
src/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.stories.tsx Adds waits/timeouts to accommodate lazy Plotly load.
src/components/charts/ScatterPlotInteractive/tests/ScatterPlotInteractive.test.tsx Updates tests to async act so lazy Plotly draw/effects flush before assertions.
src/components/charts/ScatterPlot/ScatterPlot.tsx Lazily loads Plotly and uses synchronous getLoadedPlotly() only after init.
src/components/charts/ScatterPlot/ScatterPlot.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/ScatterPlot/tests/ScatterPlot.test.tsx Updates tests to async act for lazy Plotly draw.
src/components/charts/plotly-loader.ts Adds memoized dynamic import loader with synchronous accessor for post-load code paths.
src/components/charts/PlateMap/PlateMap.tsx Converts Plotly usage to lazy loader; updates cleanup to purge via loaded Plotly.
src/components/charts/PlateMap/PlateMap.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/PieChart/PieChart.tsx Converts Plotly usage to lazy loader and uses loaded Plotly for purge.
src/components/charts/PieChart/PieChart.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/LinePlot/LinePlot.tsx Converts Plotly usage to lazy loader; keeps resize relayout via loaded Plotly.
src/components/charts/LinePlot/LinePlot.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/LinePlot/tests/LinePlot.test.tsx Updates tests to async act for lazy Plotly draw.
src/components/charts/Histogram/Histogram.tsx Converts Plotly usage to lazy loader; keeps resize relayout via loaded Plotly.
src/components/charts/Histogram/Histogram.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/Electropherogram/Electropherogram.tsx Converts Plotly usage to lazy loader and uses loaded Plotly for purge.
src/components/charts/Electropherogram/Electropherogram.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/Electropherogram/tests/Electropherogram.test.tsx Updates tests to async act for lazy Plotly draw.
src/components/charts/Chromatogram/plotBuilder.ts Switches restyle calls inside handlers to getLoadedPlotly() post-draw.
src/components/charts/Chromatogram/Chromatogram.tsx Converts Plotly usage to lazy loader; updates annotation relayout to use loaded Plotly post-draw.
src/components/charts/Chromatogram/Chromatogram.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/Chromatogram/tests/plotBuilder.test.ts Primes the Plotly loader in tests so handler code can use getLoadedPlotly().
src/components/charts/ChartTooltip/ChartTooltip.tsx Uses getLoadedPlotly() for relayout inside bindTooltip post-newPlot.
src/components/charts/ChartTooltip/tests/ChartTooltip.test.tsx Primes the Plotly loader in tests before rendering the hook.
src/components/charts/BoxPlot/BoxPlot.tsx Converts Plotly usage to lazy loader; keeps resize relayout via loaded Plotly.
src/components/charts/BoxPlot/BoxPlot.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/BarChart/BarChart.tsx Converts Plotly usage to lazy loader; keeps resize relayout via loaded Plotly.
src/components/charts/BarChart/BarChart.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/AreaPlot/AreaPlot.tsx Converts Plotly usage to lazy loader; keeps resize relayout via loaded Plotly.
src/components/charts/AreaPlot/AreaPlot.stories.tsx Updates plays to wait for async Plotly-backed render.
src/components/charts/AreaPlot/tests/AreaPlot.test.tsx Updates tests to async act for lazy Plotly draw and resize flows.
src/components/ai/use-streamdown-plugins.ts Adds lazy plugin loader hook with module-level cache for Streamdown plugins.
src/components/ai/streamdown-plugins.ts Defines full Streamdown plugin set in a lazily-imported module.
src/components/ai/streamdown-code-plugin.ts Replaces @streamdown/code with a slim plugin backed by the shared Shiki setup.
src/components/ai/reasoning.tsx Switches reasoning markdown rendering to lazy-loaded Streamdown plugins.
src/components/ai/message.tsx Switches message markdown rendering to lazy-loaded Streamdown plugins.
package.json Adds Shiki langs/themes packages; removes unused Plotly/react-plotly and streamdown code deps/types.
AGENTS.md Documents the “heavy dependencies must stay lazy” rule to prevent regressions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/shiki.ts
Comment on lines +74 to +77
languageLoaders.set(language, loader);
for (const alias of aliases) {
languageAliases.set(alias, language);
}
Comment on lines +21 to +30
export function loadPlotly(): Promise<PlotlyModule> {
plotlyPromise ??= import("plotly.js-dist").then((mod) => {
// plotly.js-dist is CJS; depending on the consumer's bundler interop the
// API surface is either the namespace itself or its `default` export.
const withDefault = mod as PlotlyModule & { default?: PlotlyModule };
loadedPlotly = withDefault.default ?? withDefault;
return loadedPlotly;
});
return plotlyPromise;
}
Comment on lines +10 to +16
function loadStreamdownPlugins(): Promise<PluginConfig> {
pluginsPromise ??= import("./streamdown-plugins").then((mod) => {
cachedPlugins = mod.streamdownPlugins;
return cachedPlugins;
});
return pluginsPromise;
}
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 96.46% (🎯 83%)
⬇️ -0.26%
22794 / 23630
🟢 Statements 96.46% (🎯 83%)
⬇️ -0.26%
22794 / 23630
🟢 Functions 95.03% (🎯 74%)
⬇️ -0.88%
995 / 1047
🟢 Branches 90.1% (🎯 81%)
⬇️ -0.17%
4363 / 4842
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/ai/message.tsx 98.47%
🟰 ±0%
95.12%
🟰 ±0%
100%
🟰 ±0%
98.47%
🟰 ±0%
131-134
src/components/ai/reasoning.tsx 97.57%
⬇️ -0.02%
96.66%
🟰 ±0%
100%
🟰 ±0%
97.57%
⬇️ -0.02%
39-40, 123-124
src/components/ai/streamdown-code-plugin.ts 35% 100% 20% 35% 38-41, 52-90
src/components/ai/streamdown-plugins.ts 100% 100% 100% 100%
src/components/ai/use-streamdown-plugins.ts 100% 100% 100% 100%
src/components/charts/plotly-loader.ts 77.77% 71.42% 100% 77.77% 39-42
src/components/charts/AreaPlot/AreaPlot.tsx 99.05%
⬇️ -0.95%
91.17%
⬇️ -2.85%
100%
🟰 ±0%
99.05%
⬇️ -0.95%
408, 410-411
src/components/charts/BarChart/BarChart.tsx 98.46%
⬇️ -1.14%
89.58%
⬇️ -4.03%
100%
🟰 ±0%
98.46%
⬇️ -1.14%
155, 336, 338-339
src/components/charts/BoxPlot/BoxPlot.tsx 98.34%
⬇️ -1.23%
85.29%
⬇️ -5.61%
100%
🟰 ±0%
98.34%
⬇️ -1.23%
176, 302, 304-305
src/components/charts/ChartTooltip/ChartTooltip.tsx 100%
🟰 ±0%
90.16%
⬆️ +1.46%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/charts/Chromatogram/Chromatogram.tsx 100%
🟰 ±0%
83.67%
⬆️ +2.42%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/charts/Chromatogram/plotBuilder.ts 100%
🟰 ±0%
100%
⬆️ +1.86%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/charts/Electropherogram/Electropherogram.tsx 100%
🟰 ±0%
96.15%
⬆️ +0.08%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/charts/Histogram/Histogram.tsx 98.72%
⬇️ -0.95%
91.37%
⬇️ -3.45%
100%
🟰 ±0%
98.72%
⬇️ -0.95%
155, 348, 350-351
src/components/charts/LinePlot/LinePlot.tsx 98.9%
⬇️ -1.10%
92.72%
⬇️ -3.43%
100%
🟰 ±0%
98.9%
⬇️ -1.10%
501, 503-504
src/components/charts/PieChart/PieChart.tsx 100%
🟰 ±0%
78.94%
⬆️ +1.17%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/charts/PlateMap/PlateMap.tsx 98.04%
⬆️ +0.02%
89.06%
⬆️ +0.06%
100%
🟰 ±0%
98.04%
⬆️ +0.02%
245-246, 322-323, 328-329, 339-340, 356-358, 492
src/components/charts/ScatterPlot/ScatterPlot.tsx 99%
⬇️ -1.00%
90.19%
⬇️ -3.56%
100%
🟰 ±0%
99%
⬇️ -1.00%
393, 395-396
src/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.tsx 100%
🟰 ±0%
96.49%
⬇️ -1.72%
100%
🟰 ±0%
100%
🟰 ±0%
src/components/ui/code-block.tsx 97.48%
⬇️ -0.11%
81.69%
⬆️ +0.36%
100%
🟰 ±0%
97.48%
⬇️ -0.11%
251-252, 407-409, 478-480, 485-486
src/lib/shiki.ts 84.21% 86.36% 50% 84.21% 56-57, 70-78, 118
Generated in workflow #1033 for commit dce60a7 by the Vitest Coverage Report Action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants