Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions scripts/dev-benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const viteArgs = process.argv.slice(2);
const host = "127.0.0.1";
const port = 3020;
const start = performance.now();

const child = Bun.spawn({
cmd: ["bun", "run", "dev", "--", "--host", host, "--port", String(port), ...viteArgs],
cwd: process.cwd(),
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
env: process.env,
});

let ready = false;
let stdout = "";
let stderr = "";

async function collect(readable: ReadableStream<Uint8Array>, target: "stdout" | "stderr") {
const decoder = new TextDecoder();

for await (const chunk of readable) {
const text = decoder.decode(chunk, { stream: true });

if (target === "stdout") {
stdout += text;
if (stdout.includes("Local:")) {
ready = true;
}
} else {
stderr += text;
}
}

const tail = decoder.decode();
if (!tail) return;

if (target === "stdout") {
stdout += tail;
if (stdout.includes("Local:")) {
ready = true;
}
} else {
stderr += tail;
}
}

const stdoutTask = collect(child.stdout, "stdout");
const stderrTask = collect(child.stderr, "stderr");

async function waitForReady(timeoutMs: number) {
const start = performance.now();

while (!ready) {
if (child.exitCode !== null) {
throw new Error(`vite dev exited early with code ${child.exitCode}`);
}
if (performance.now() - start > timeoutMs) {
throw new Error("timed out waiting for vite dev");
}
await Bun.sleep(100);
}
}

async function request(pathname: string) {
const requestStart = performance.now();
const response = await fetch(`http://${host}:${port}${pathname}`);
await response.arrayBuffer();

return {
pathname,
status: response.status,
ms: Number((performance.now() - requestStart).toFixed(2)),
};
}

function formatDuration(ms: number) {
return `${(ms / 1000).toFixed(2)}s`;
}

function printTable(
rows: Array<{
step: string;
durationMs: number;
status: string;
}>,
) {
const headers = ["Step", "Duration", "Status"];
const body = rows.map((row) => [row.step, formatDuration(row.durationMs), row.status]);
const widths = headers.map((header, index) =>
Math.max(header.length, ...body.map((row) => row[index].length)),
);

const formatRow = (row: string[]) =>
row.map((cell, index) => cell.padEnd(widths[index])).join(" ");

console.log(formatRow(headers));
console.log(widths.map((width) => "-".repeat(width)).join(" "));
for (const row of body) {
console.log(formatRow(row));
}
}

try {
await waitForReady(30_000);
const readyMs = Number((performance.now() - start).toFixed(2));

const docs = await request("/docs/");
const totalToDocsMs = Number((performance.now() - start).toFixed(2));
const anotherPage = await request("/docs/support/dashboard");
const totalMs = Number((performance.now() - start).toFixed(2));

if (docs.status >= 400 || anotherPage.status >= 400) {
console.error("vite dev benchmark hit an error response");
if (stdout) console.error(stdout.trim());
if (stderr) console.error(stderr.trim());
process.exitCode = 1;
}

printTable([
{ step: "Dev ready", durationMs: readyMs, status: "-" },
{ step: "First /docs/ request", durationMs: docs.ms, status: String(docs.status) },
{ step: "Start -> first /docs/", durationMs: totalToDocsMs, status: String(docs.status) },
{ step: "Second docs page", durationMs: anotherPage.ms, status: String(anotherPage.status) },
{ step: "Full benchmark", durationMs: totalMs, status: "-" },
]);
} finally {
child.kill("SIGTERM");
await child.exited;
await stdoutTask;
await stderrTask;
}
6 changes: 5 additions & 1 deletion source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ export const docs = defineDocs({
docs: {
async: isDevelopment,
postprocess: {
includeProcessedMarkdown: true,
includeProcessedMarkdown: !isDevelopment,
},
},
});

export default defineConfig({
mdxOptions: {
// Shiki highlighting is one of the most expensive parts of the MDX pipeline.
// Keep it in builds, but skip it in local dev so first-page SSR doesn't have
// to highlight hundreds of code-heavy docs up front.
rehypeCodeOptions: isDevelopment ? false : undefined,
remarkPlugins: (existing) => [
remarkImagePaths,
remarkLinkPaths,
Expand Down
5 changes: 4 additions & 1 deletion src/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ export const source = loader({
});

export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await page.data.getText("processed");
// Dev disables processed markdown generation to avoid paying that extra MDX
// postprocess cost on the first docs request, so fall back to raw text there.
const textType = process.env.NODE_ENV === "development" ? "raw" : "processed";
const processed = await page.data.getText(textType);

return `# ${page.data.title}

Expand Down
16 changes: 14 additions & 2 deletions src/routes/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { createFileRoute } from "@tanstack/react-router";
import { source } from "@/lib/source";
import { createFromSource } from "fumadocs-core/search/server";

const server = createFromSource(source, { language: "english" });
let server:
| ReturnType<typeof createFromSource>
| null = null;

function getServer() {
if (server) return server;

// Building the local Orama index is expensive; keep it off the main dev
// startup path and only initialize it when /api/search is actually hit.
server = createFromSource(source, { language: "english" });

return server;
}

export const Route = createFileRoute("/api/search")({
server: {
Expand All @@ -13,5 +25,5 @@ export const Route = createFileRoute("/api/search")({
});

export function handleSearchGet(request: Request) {
return server.GET(request);
return getServer().GET(request);
}
5 changes: 4 additions & 1 deletion src/routes/llms[.]mdx.docs.$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ export const Route = createFileRoute("/llms.mdx/docs/$")({
const slugs = params._splat?.split("/") ?? [];
const page = source.getPage(slugs);
if (!page) throw notFound();
// Dev disables processed markdown generation to keep the first docs
// request fast, so the LLM endpoint serves raw markdown in development.
const textType = process.env.NODE_ENV === "development" ? "raw" : "processed";

return new Response(await page.data.getText("processed"), {
return new Response(await page.data.getText(textType), {
headers: {
"Content-Type": "text/markdown",
"Access-Control-Allow-Origin": "*",
Expand Down
Loading