Bug Description
The docs at /docs/plugins and /docs/plugins/mermaid describe mermaid as an opt-in plugin that consumers install and wire up themselves via @streamdown/mermaid. The DiagramPlugin interface, the separate @streamdown/mermaid package, and an in-bundle "plugin missing" fallback all suggest the intended design is pluggable.
However, as of streamdown@2.5.0, mermaid is listed in dependencies in the published package.json, so it is installed unconditionally for every consumer — even ones that never render a diagram and never pass a mermaid plugin to <Streamdown />. This contradicts the documented plugin model and adds roughly 75 MB of install footprint to every project that depends on streamdown.
Inspecting the published 2.5.0 tarball shows the runtime does not actually import mermaid statically — all mermaid usage is routed through the plugin context — so the hard dependency appears to be unintentional rather than load-bearing.
Steps to Reproduce
- Create a fresh project and install streamdown:
npm init -y
npm i streamdown@2.5.0
- Inspect installed size and the resolved
mermaid version:
du -sh node_modules/mermaid node_modules/streamdown
node -e "console.log(require('streamdown/package.json').dependencies.mermaid)"
- Grep the published dist for any static mermaid import:
grep -RnoE "from ['\"]mermaid['\"]" node_modules/streamdown/dist
Expected Behavior
Per the plugin docs, mermaid should be opt-in. A consumer that does not render mermaid diagrams (and does not pass a mermaid plugin to <Streamdown />) should not have mermaid in their node_modules, lockfile, or supply-chain audit surface. Installing streamdown alone should not pull in mermaid; installing @streamdown/mermaid should.
The in-bundle fallback string — "Mermaid plugin not available. Please add the mermaid plugin to enable diagram rendering." — should be a reachable, supported code path.
Actual Behavior
mermaid@^11.12.2 is listed in dependencies (not peerDependencies, devDependencies, or peerDependenciesMeta.optional) in streamdown@2.5.0's published package.json.
- As a result, every
npm install streamdown unconditionally installs mermaid and its full transitive graph (~75 MB on disk, ~130× the size of streamdown itself at ~580 KB).
- The "plugin missing" fallback path cannot actually be reached, because
mermaid is always resolved as a transitive of streamdown.
- Meanwhile,
dist/chunk-BO2N2NFS.js has zero static imports from 'mermaid'. The only bare-package imports in the built chunk are react, rehype-*, remark-*, unified, unist-util-*, hast-util-to-jsx-runtime, html-url-attributes, marked, clsx, tailwind-merge, remend, and react-dom / react/jsx-runtime. All mermaid usage is routed through the DiagramPlugin context the consumer provides.
- The only remaining coupling is a single type-only import in
dist/index.d.ts:
import { MermaidConfig } from 'mermaid';
used to type DiagramPlugin.getMermaid(config?: MermaidConfig). This does not require mermaid at runtime.
Code Sample
Relevant excerpt from `node_modules/streamdown/package.json` (2.5.0):
{
"name": "streamdown",
"version": "2.5.0",
"dependencies": {
"clsx": "^2.1.1",
"hast-util-to-jsx-runtime": "^2.3.6",
"html-url-attributes": "^3.0.1",
"marked": "^17.0.1",
"rehype-harden": "^1.1.8",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"tailwind-merge": "^3.4.0",
"unified": "^11.0.5",
"mermaid": "^11.12.2",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.0",
"remend": "1.3.0"
},
"devDependencies": {
"@streamdown/cjk": "1.0.3",
"@streamdown/math": "1.0.2",
"@streamdown/mermaid": "1.0.2"
}
}
Relevant excerpt from `dist/index.d.ts`:
import { MermaidConfig } from 'mermaid';
interface DiagramPlugin {
getMermaid: (config?: MermaidConfig) => MermaidInstance;
language: string;
name: "mermaid";
type: "diagram";
}
interface PluginConfig {
cjk?: CjkPlugin;
code?: CodeHighlighterPlugin;
math?: MathPlugin;
mermaid?: DiagramPlugin;
renderers?: CustomRenderer[];
}
Reproduction output on my machine:
$ du -sh node_modules/mermaid node_modules/streamdown
75M node_modules/mermaid
580K node_modules/streamdown
$ node -e "console.log(require('streamdown/package.json').dependencies.mermaid)"
^11.12.2
$ grep -RnoE "from ['\"]mermaid['\"]" node_modules/streamdown/dist
node_modules/streamdown/dist/index.d.ts:4:import { MermaidConfig } from 'mermaid';
# (no matches in any .js file)
Streamdown Version
2.5.0
React Version
19.2.5
Node.js Version
23.7.0
Browser(s)
No response
Operating System
None
Additional Context
Why this matters
- Install size / cold-start cost. Every consumer (including serverless, edge, Docker images, Lambda layers, CI caches,
vercel/pkg-style bundlers) pays ~75 MB of install footprint for a package they may never use. For projects deploying to Cloudflare Workers, Vercel Edge, or Lambda, this is significant.
- Supply-chain surface area. Mermaid transitively pulls in a large graph (d3, dompurify, cytoscape, etc.) that consumers who don't render diagrams should not have to audit, pin, or patch.
- Docs / reality mismatch. The plugin docs are explicit that mermaid is opt-in. New users read the plugin guide, wire up
@streamdown/mermaid, and are surprised to see mermaid in their lockfile regardless.
- Defeats the point of the plugin architecture. The
DiagramPlugin interface, the runtime fallback string, and @streamdown/mermaid existing as a separate package all suggest the intended design was pluggable. The package.json is the only thing blocking it.
Proposed fix
Move mermaid out of dependencies. A few options, in rough order of preference:
- Drop
mermaid entirely from streamdown's package.json. Let @streamdown/mermaid own the dep. Re-export MermaidConfig from @streamdown/mermaid (or inline a narrow structural type in streamdown's .d.ts) so streamdown has no type-level reference to mermaid either.
- Optional peer dep. Declare
mermaid in peerDependencies + peerDependenciesMeta: { mermaid: { optional: true } }. Consumers that want built-in mermaid install it; others don't. This keeps the type import working without forcing installation on npm ≥ 7.
- Type-only indirection. If (1) / (2) are too disruptive, at minimum replace
import { MermaidConfig } from 'mermaid' with a local structural type so there is no type-level coupling, and document that consumers using @streamdown/mermaid get the real typed config through the plugin package.
Option (1) seems most aligned with how the @streamdown/* split and the plugin docs are already framed.
Environment
- Installed as
dependencies[streamdown] alongside React 19 / React Router v7 on Cloudflare Workers.
- Verified against the published tarball's
dist/ and package.json.
Bug Description
The docs at /docs/plugins and /docs/plugins/mermaid describe mermaid as an opt-in plugin that consumers install and wire up themselves via
@streamdown/mermaid. TheDiagramPlugininterface, the separate@streamdown/mermaidpackage, and an in-bundle "plugin missing" fallback all suggest the intended design is pluggable.However, as of
streamdown@2.5.0,mermaidis listed independenciesin the publishedpackage.json, so it is installed unconditionally for every consumer — even ones that never render a diagram and never pass amermaidplugin to<Streamdown />. This contradicts the documented plugin model and adds roughly 75 MB of install footprint to every project that depends on streamdown.Inspecting the published 2.5.0 tarball shows the runtime does not actually import
mermaidstatically — all mermaid usage is routed through the plugin context — so the hard dependency appears to be unintentional rather than load-bearing.Steps to Reproduce
mermaidversion:du -sh node_modules/mermaid node_modules/streamdown node -e "console.log(require('streamdown/package.json').dependencies.mermaid)"grep -RnoE "from ['\"]mermaid['\"]" node_modules/streamdown/distExpected Behavior
Per the plugin docs,
mermaidshould be opt-in. A consumer that does not render mermaid diagrams (and does not pass amermaidplugin to<Streamdown />) should not havemermaidin theirnode_modules, lockfile, or supply-chain audit surface. Installingstreamdownalone should not pull inmermaid; installing@streamdown/mermaidshould.The in-bundle fallback string —
"Mermaid plugin not available. Please add the mermaid plugin to enable diagram rendering."— should be a reachable, supported code path.Actual Behavior
mermaid@^11.12.2is listed independencies(notpeerDependencies,devDependencies, orpeerDependenciesMeta.optional) instreamdown@2.5.0's publishedpackage.json.npm install streamdownunconditionally installsmermaidand its full transitive graph (~75 MB on disk, ~130× the size ofstreamdownitself at ~580 KB).mermaidis always resolved as a transitive ofstreamdown.dist/chunk-BO2N2NFS.jshas zero static imports from'mermaid'. The only bare-package imports in the built chunk arereact,rehype-*,remark-*,unified,unist-util-*,hast-util-to-jsx-runtime,html-url-attributes,marked,clsx,tailwind-merge,remend, andreact-dom/react/jsx-runtime. All mermaid usage is routed through theDiagramPlugincontext the consumer provides.dist/index.d.ts:DiagramPlugin.getMermaid(config?: MermaidConfig). This does not requiremermaidat runtime.Code Sample
Streamdown Version
2.5.0
React Version
19.2.5
Node.js Version
23.7.0
Browser(s)
No response
Operating System
None
Additional Context
Why this matters
vercel/pkg-style bundlers) pays ~75 MB of install footprint for a package they may never use. For projects deploying to Cloudflare Workers, Vercel Edge, or Lambda, this is significant.@streamdown/mermaid, and are surprised to seemermaidin their lockfile regardless.DiagramPlugininterface, the runtime fallback string, and@streamdown/mermaidexisting as a separate package all suggest the intended design was pluggable. Thepackage.jsonis the only thing blocking it.Proposed fix
Move
mermaidout ofdependencies. A few options, in rough order of preference:mermaidentirely fromstreamdown'spackage.json. Let@streamdown/mermaidown the dep. Re-exportMermaidConfigfrom@streamdown/mermaid(or inline a narrow structural type instreamdown's.d.ts) sostreamdownhas no type-level reference tomermaideither.mermaidinpeerDependencies+peerDependenciesMeta: { mermaid: { optional: true } }. Consumers that want built-in mermaid install it; others don't. This keeps the type import working without forcing installation on npm ≥ 7.import { MermaidConfig } from 'mermaid'with a local structural type so there is no type-level coupling, and document that consumers using@streamdown/mermaidget the real typed config through the plugin package.Option (1) seems most aligned with how the
@streamdown/*split and the plugin docs are already framed.Environment
dependencies[streamdown]alongside React 19 / React Router v7 on Cloudflare Workers.dist/andpackage.json.