@mements/serve
Version:
A lightweight, type-safe server framework for Bun with automatic performance tracking.
491 lines (430 loc) • 15.1 kB
text/typescript
import { build, type BuildConfig } from "bun";
import plugin from "bun-plugin-tailwind";
import { existsSync } from "fs";
import { rm } from "fs/promises";
import path from "path";
import { randomUUID } from "crypto";
import { readdir } from "node:fs/promises";
export type ApiEndpoint = {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
path: string;
body?: any;
query?: Record<string, string | number | boolean>;
response: any;
};
export type GetHealthEndpoint = {
method: 'GET';
path: '/api/health';
query?: undefined;
response: { status: string };
};
export type ApiEndpoints = Record<string, ApiEndpoint>;
type MeasureContext = {
requestId?: string;
level?: number;
parentAction?: string;
};
export type MeasureFn = <T>(
fn: (measure: MeasureFn) => Promise<T>,
action: string,
context?: MeasureContext
) => Promise<T>;
export async function measure<T>(
fn: (measure: typeof measure) => Promise<T>,
action: string,
context: MeasureContext = {}
): Promise<T> {
const start = performance.now();
const level = context.level || 0;
let indent = "=".repeat(level);
const requestId = context.requestId;
let logPrefix = requestId ? `[${requestId}] ${indent}>` : `${indent}>`;
try {
indent = ">".repeat(level);
logPrefix = requestId ? `[${requestId}] ${indent}$` : `${indent}$`;
console.log(`${logPrefix} ${action}...`);
const result = await fn((nestedFn, nestedAction) =>
measure(nestedFn, nestedAction, {
requestId: requestId ? `${requestId}` : undefined,
level: level + 1,
parentAction: action
})
);
const duration = performance.now() - start;
indent = "<".repeat(level);
logPrefix = requestId ? `[${requestId}] ${indent}$` : `${indent}$`;
console.log(`${logPrefix} ${action} ✓ ${duration.toFixed(2)}ms`);
return result;
} catch (error) {
const duration = performance.now() - start;
console.log('===========================');
console.log(`\n${logPrefix} ${action} ✗ ${duration.toFixed(2)}ms`, error);
console.log('===========================');
throw new Error(`${action} failed: ${error}`);
}
}
interface PageConfig {
route: string;
target: string;
handler: (ctx: { requestId: string; measure: MeasureFn }) => Promise<any>;
}
interface ImportConfig {
name: string;
version?: string;
deps?: string[];
}
export interface ServeOptions {
pages: PageConfig[];
api?: Record<string, (req: Request) => Promise<Response>>;
imports: ImportConfig[];
}
type RouteMapping = Record<string, string>;
type ImportMap = { imports: Record<string, string> };
type EntrypointConfig = {
path: string;
serverData?: (ctx: MiddlewareContext & { requestId: string; measure: MeasureFn }) => Promise<any>;
};
const filePathCache: Record<string, string> = {};
const getHeaders = (ext: string) => {
const contentTypes: Record<string, string> = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".png": "image/png",
".jpg": "image/jpeg",
".svg": "image/svg+xml",
};
return {
headers: {
"Content-Type": contentTypes[ext] || "application/octet-stream",
},
};
};
async function servePage(
response: Response,
importMap: ImportMap,
serverData = {},
requestId: string
): Promise<Response> {
return await measure(
async (measure) => {
const rewriter = new HTMLRewriter()
.on("head", {
element(element) {
element.prepend(
`<script type="importmap">${JSON.stringify(importMap)}</script>`,
{ html: true }
);
},
})
.on("body", {
element(element) {
const data = { ...serverData, requestId };
element.append(
`<script>window.serverData = ${JSON.stringify(data)}</script>`,
{ html: true }
);
},
});
const transformedResponse = rewriter.transform(response);
const transformedHtml = await transformedResponse.text();
return new Response(transformedHtml, getHeaders(".html"));
},
"Transform page",
{ requestId, level: 2 }
);
}
export async function serve(config: ServeOptions) {
const isDev = process.env.NODE_ENV !== "production";
const outdir = "./dist";
const assetsDir = "./assets";
const routeMap: RouteMapping = {};
const entrypoints: Record<string, EntrypointConfig> = {};
const pageHandlers: Record<string, (req: Request) => Promise<any>> = {};
await measure(async (measure) => {
for (const page of config.pages) {
let targetPath: string;
if (page.target.startsWith("./")) {
targetPath = page.target;
} else {
targetPath = `./pages/${page.target}/${page.target}.html`;
}
const relativePath = targetPath.startsWith("./") ? targetPath.substring(2) : targetPath;
routeMap[page.route] = relativePath;
entrypoints[page.route] = { path: targetPath, serverData: page.handler };
pageHandlers[page.route] = async (req) => {
const url = new URL(req.url);
const query = Object.fromEntries(url.searchParams);
const requestId = req.headers.get("X-Request-ID") || randomUUID().split('-')[0];
const ctx: { requestId: string; measure: MeasureFn } = {
request: req,
method: req.method,
path: url.pathname,
query,
body: req.method !== "GET" ? await req.json().catch(() => ({})) : undefined,
headers: req.headers,
requestId,
measure,
};
return await page.handler(ctx);
};
}
}, "Initialize routes");
const importMap: ImportMap = { imports: {} };
const versionMap: Record<string, string> = {};
for (const imp of config.imports) {
if (imp.name.startsWith("@")) {
versionMap[imp.name] = imp.version ?? "latest";
} else {
const baseName = imp.name.split("/")[0];
versionMap[baseName] = imp.version ?? "latest";
}
}
for (const imp of config.imports) {
let url: string;
if (imp.name.startsWith("@")) {
url = `https://esm.sh/${imp.name}@${versionMap[imp.name]}`;
} else {
const parts = imp.name.split("/");
const baseName = parts[0];
const subPaths = parts.slice(1);
url = `https://esm.sh/${baseName}@${versionMap[baseName]}`;
if (subPaths.length > 0) url += `/${subPaths.join("/")}`;
}
let queryParts: string[] = [];
if (imp?.deps?.length) {
const depsList = imp.deps
.map((dep) => `${dep}@${versionMap[dep.split("/")[0]]}`)
.join(",");
queryParts.push(`deps=${depsList}`);
}
if (isDev) queryParts.push("dev");
if (queryParts.length) url += `?${queryParts.join("&")}`;
importMap.imports[imp.name] = url;
}
let serverPort = -1;
const buildConfig: BuildConfig = {
entrypoints: Object.values(entrypoints).map((e) => e.path),
outdir,
plugins: [plugin],
minify: !isDev,
target: "browser",
sourcemap: "linked",
packages: "external",
external: Object.keys(importMap.imports),
define: {
"process.env.NODE_ENV": JSON.stringify(isDev ? "development" : "production"),
"process.env.HOST": isDev ? `http://localhost:${serverPort}` : "https://mements.ai",
},
naming: {
chunk: "[name].[hash].[ext]",
entry: "[name].[hash].[ext]",
},
};
if (existsSync(outdir)) {
await rm(outdir, { recursive: true, force: true });
}
async function rebuildPage(pagePath: string, requestId: string): Promise<any> {
if (isDev) {
const baseName = path.basename(pagePath).split(".")[0].toLowerCase();
const entrypoint = Object.values(entrypoints).find((e) =>
path.basename(e.path).split(".")[0].toLowerCase() === baseName
);
if (!entrypoint) return null;
return await measure(
async (measure) => {
try {
const result = await build({ ...buildConfig, entrypoints: [entrypoint.path] });
if (result && result.outputs) {
for (const output of result.outputs) {
const outputBaseName = path.basename(output.path).split(".")[0].toLowerCase();
const ext = path.extname(output.path);
filePathCache[`${outputBaseName}${ext}`] = output.path;
}
}
return result;
} catch (error) {
console.error("Failed to rebuild page:", error);
return null;
}
},
`Rebuild ${baseName}`,
{ requestId }
);
}
return null;
}
const server = Bun.serve({
port: process.env.BUN_PORT,
development: isDev,
async fetch(req) {
const requestId = randomUUID().split("-")[0];
const newHeaders = new Headers(req.headers);
if (!newHeaders.has("X-Request-ID")) {
newHeaders.append("X-Request-ID", requestId);
}
const reqWithId = new Request(req, { headers: newHeaders });
return await measure(
async (measure) => {
const url = new URL(reqWithId.url);
const pathname = url.pathname;
const distPath = path.join(process.cwd(), outdir, pathname);
if (await Bun.file(distPath).exists()) {
return new Response(Bun.file(distPath), getHeaders(path.extname(pathname)));
}
const assetsPath = path.join(process.cwd(), assetsDir, pathname);
if (await Bun.file(assetsPath).exists()) {
return new Response(Bun.file(assetsPath), getHeaders(path.extname(pathname)));
}
if (pageHandlers[pathname]) {
const routeFile = routeMap[pathname];
return await measure(
async (measure) => {
const ext = path.extname(routeFile);
const baseName = path.basename(routeFile, ext);
let htmlFile: any = null;
if (isDev) {
// In dev mode, always rebuild the page
const buildResult = await rebuildPage(routeFile, requestId);
if (buildResult) {
const builtFile = buildResult.outputs.find((o: { path: string }) =>
o.path.endsWith(ext)
);
if (builtFile) {
htmlFile = Bun.file(builtFile.path);
}
}
} else {
// In production mode, check the cache first
const cacheKey = `${baseName.toLowerCase()}${ext}`;
if (filePathCache[cacheKey] && await Bun.file(filePathCache[cacheKey]).exists()) {
htmlFile = Bun.file(filePathCache[cacheKey]);
}
// If not in cache or file doesn't exist, rebuild
if (!htmlFile) {
const buildResult = await rebuildPage(routeFile, requestId);
if (buildResult) {
const builtFile = buildResult.outputs.find((o: { path: string }) =>
o.path.endsWith(ext)
);
if (builtFile) {
htmlFile = Bun.file(builtFile.path);
}
}
}
}
if (!htmlFile) {
throw new Error(`Page not found: ${routeFile}`);
}
let serverData = {};
const handler = pageHandlers[pathname];
if (handler) {
serverData = await measure(
async (measure) => handler(reqWithId),
`serverData ${pathname}`
);
}
return await servePage(
new Response(htmlFile, getHeaders(ext)),
importMap,
serverData,
requestId
);
},
`page ${pathname}`
);
}
if (config.api && pathname in config.api) {
return await measure(
async (measure) => config.api,
`endpoint ${pathname}`
);
}
return new Response("Route Not Found", { status: 404 });
},
`${req.method} ${req.url}`,
{ requestId }
);
},
error(error) {
console.error("Server Error:", error);
return new Response(
`<pre>${error}\n${error.stack}</pre>`,
{ headers: { "Content-Type": "text/html" }, status: 500 }
);
},
});
serverPort = server.port;
await measure(async () => {
const result = await build(buildConfig);
if (result && result.outputs) {
for (const output of result.outputs) {
const outputBaseName = path.basename(output.path).split(".")[0].toLowerCase();
const ext = path.extname(output.path);
filePathCache[`${outputBaseName}${ext}`] = output.path;
}
}
}, "Initial build");
return server;
}
if (require.main === module) {
const exampleConfig: ServeOptions = {
pages: [
{
route: "/",
target: "./pages/index.tsx",
handler: async (ctx) => ({ message: `Hello from ${ctx.path}` }),
},
],
api: {
"/api/health": async (req) =>
new Response(JSON.stringify({ status: "ok" }), {
headers: { "Content-Type": "application/json" },
}),
},
imports: [
{ name: "react", version: "18.2.0" },
{ name: "react-dom/client", version: "18.2.0" },
],
};
serve(exampleConfig);
}
export function generateImports(packageJson: any) {
const dependencies = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {}
};
const knownDependencies: Record<string, string[]> = {
"sonner": ["react", "react-dom"],
"wouter": ["react"],
"framer-motion": ["react"],
};
const imports = Object.entries(dependencies)
.filter(([name]) => {
const backendDependencies = ["@types/", "typescript", "@mements/serve", "zod"];
return !backendDependencies.some(dep => name.startsWith(dep));
})
.map(([name, version]) => {
if (name === "react-dom") {
return [
{ name, version: (version as string).replace(/^\^/, '') },
{ name: "react-dom/client", version: (version as string).replace(/^\^/, ''), deps: [] },
{ name: "react/jsx-dev-runtime", version: packageJson.dependencies.react.replace(/^\^/, ''), deps: [] },
{ name: "react/jsx-runtime", version: packageJson.dependencies.react.replace(/^\^/, ''), deps: [] }
];
}
if (name === "react") {
return {
name,
version: (version as string).replace(/^\^/, ''),
deps: []
};
}
if (knownDependencies[name]) {
return { name, version: (version as string).replace(/^\^/, ''), deps: knownDependencies[name] };
}
return { name };
})
.flat();
return imports;
}