mirror of
https://github.com/vercel/streamdown.git
synced 2026-09-19 02:18:37 +08:00
0b80aed583
* Plugin trial * Update docs and tests * Update homepage add new section * Update usage.tsx * Split out CJK plugin * Extract plugins into new packages * Misc fixes * Fix lint / formatting * Fix: The Streamdown component's memo comparison doesn't compare the plugins prop, preventing re-renders when plugins change while other props stay the same Co-authored-by: haydenbleasel <hello@haydenbleasel.com> * Fix: TypeScript type mismatch at line 89 in `index.ts`: the visitor function's `index` parameter is typed as `number | undefined`, but the `visit` function expects `number | null`. The `undefined` type is not assignable to `number | null`. This commit fixes the issue reported at /vercel/path0/packages/streamdown-cjk/index.ts:89 ## TypeScript type incompatibility in visitor callback **What fails:** TypeScript compiler fails during DTS (type definitions) build in `packages/streamdown-cjk/index.ts` at line 89 **How to reproduce:** ```bash cd packages/streamdown-cjk pnpm build ``` **Result:** ``` index.ts(89,3): error TS2769: No overload matches this call. Overload 1 of 2, '(tree: Root, check: "link", visitor: BuildVisitor<Root, "link">, reverse?: boolean | null | undefined): undefined', gave the following error. Argument of type '(node: Link, index: number | null, parent?: Parent) => number | undefined' is not assignable to parameter of type 'BuildVisitor<Root, "link">'. Types of parameters 'index' and 'index' are incompatible. Type 'number | undefined' is not assignable to type 'number | null'. Type 'undefined' is not assignable to type 'number | null'. ``` **Root cause:** The visitor callback function passed to the `visit()` function from `unist-util-visit` was declaring the `index` parameter as `number | null`, but the visitor can also receive `undefined` as the index value. The `BuildVisitor` type signature expects the callback to accept `number | null | undefined` to properly match the library's API. **Fix:** Updated the `index` parameter type annotation on line 89 from `number | null` to `number | null | undefined` to match the actual possible values that the visitor receives and the expected `BuildVisitor` type signature. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: haydenbleasel <hello@haydenbleasel.com> * Fix tests * Remove CDN paths * Fix tests * More cleanup * Remove shiki from main bundle * Update index.ts * Cleanup test repo * Cleanup website * Delete next-env files * Update .gitignore * Fix lockfile * Upgrade Next and React * Simplify themes * Update test site * Misc fixes * Type fixes * Create perky-cobras-punch.md --------- Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
116 lines
3.2 KiB
JavaScript
116 lines
3.2 KiB
JavaScript
import { readFileSync, unlinkSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { gzipSync } from "node:zlib";
|
|
import { build } from "esbuild";
|
|
|
|
async function analyzeBundle(name, entryPoint, external) {
|
|
const outfile = join(tmpdir(), `streamdown-${name}.js`);
|
|
|
|
const result = await build({
|
|
entryPoints: [entryPoint],
|
|
bundle: true,
|
|
minify: true,
|
|
format: "esm",
|
|
outfile,
|
|
external,
|
|
loader: {
|
|
".woff2": "empty",
|
|
".woff": "empty",
|
|
".ttf": "empty",
|
|
".css": "empty",
|
|
},
|
|
metafile: true,
|
|
logLevel: "silent",
|
|
});
|
|
|
|
const outputKey = Object.keys(result.metafile.outputs)[0];
|
|
const totalBytes = result.metafile.outputs[outputKey]?.bytes || 0;
|
|
|
|
// Get gzipped size
|
|
const bundleContent = readFileSync(outfile);
|
|
const gzippedBytes = gzipSync(bundleContent).length;
|
|
|
|
// Cleanup
|
|
try {
|
|
unlinkSync(outfile);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
return { totalBytes, gzippedBytes, metafile: result.metafile };
|
|
}
|
|
|
|
function formatSize(bytes) {
|
|
if (bytes >= 1024 * 1024) {
|
|
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
}
|
|
return `${(bytes / 1024).toFixed(0)}KB`;
|
|
}
|
|
|
|
async function main() {
|
|
console.log("\n=== Streamdown Bundle Analysis ===\n");
|
|
|
|
// Core bundle (what users get with just `import { Streamdown } from 'streamdown'`)
|
|
console.log("1. CORE (no plugins) - externals: react, shiki, mermaid, katex");
|
|
const core = await analyzeBundle("core", "dist/index.js", [
|
|
"react",
|
|
"react-dom",
|
|
"shiki",
|
|
"mermaid",
|
|
"katex",
|
|
"rehype-katex",
|
|
"remark-math",
|
|
]);
|
|
console.log(` Minified: ${formatSize(core.totalBytes)}`);
|
|
console.log(` Gzipped: ${formatSize(core.gzippedBytes)}`);
|
|
|
|
// Shiki plugin only
|
|
console.log("\n2. SHIKI PLUGIN - externals: react, shiki");
|
|
const shikiPlugin = await analyzeBundle(
|
|
"shiki-plugin",
|
|
"dist/plugins/shiki/index.js",
|
|
["react", "react-dom", "shiki"]
|
|
);
|
|
console.log(` Minified: ${formatSize(shikiPlugin.totalBytes)}`);
|
|
console.log(` Gzipped: ${formatSize(shikiPlugin.gzippedBytes)}`);
|
|
|
|
// Full bundle with shiki bundled (what Cloudflare would see)
|
|
console.log("\n3. CORE + SHIKI BUNDLED (Cloudflare scenario)");
|
|
const withShiki = await analyzeBundle("with-shiki", "dist/index.js", [
|
|
"react",
|
|
"react-dom",
|
|
"mermaid",
|
|
"katex",
|
|
"rehype-katex",
|
|
"remark-math",
|
|
]);
|
|
console.log(` Minified: ${formatSize(withShiki.totalBytes)}`);
|
|
console.log(` Gzipped: ${formatSize(withShiki.gzippedBytes)}`);
|
|
console.log(
|
|
" Cloudflare Workers limit: 1MB compressed (free) / 10MB (paid)"
|
|
);
|
|
|
|
// Top dependencies in full bundle
|
|
console.log("\n4. TOP 15 LARGEST FILES (in full bundle):\n");
|
|
const inputs = Object.entries(withShiki.metafile.inputs)
|
|
.map(([path, data]) => ({ path, bytes: data.bytes }))
|
|
.sort((a, b) => b.bytes - a.bytes)
|
|
.slice(0, 15);
|
|
|
|
for (const { path, bytes } of inputs) {
|
|
const shortPath = path.replace(
|
|
/node_modules\/\.pnpm\/[^/]+\/node_modules\//g,
|
|
""
|
|
);
|
|
console.log(` ${formatSize(bytes).padStart(8)} - ${shortPath}`);
|
|
}
|
|
|
|
console.log("");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("Bundle analysis failed:", err.message);
|
|
process.exit(1);
|
|
});
|