index.ts
// index.ts
import type { Serve } from "bun";
import { stat, readdir } from "node:fs/promises";
import ejs from "ejs";
interface Dir {
url: string;
type: "dir" | "back" | "file" | "unknown";
path: string;
name: string;
}
const BASEURL = "/public"; // 托管项目下public文件夹
const promiseAwait = <T>(promise: Promise<T>) => {
return promise.then(v => [null, v] as const).catch(e => [e, null] as const);
};
//构建dir目录
const buildDir = async (dir: string, pathname: string) => {
const arr: Array<Dir> = [];
const files = await readdir(dir);
if (pathname !== "/") {
arr.push({
url: dir,
type: "back",
name: "../",
path: `javascript:history.back()`,
});
}
for await (const file of files) {
const url = `${dir}/${file}`;
const [, where] = await promiseAwait(stat(url));
if (!where) continue;
const isDir = where.isDirectory();
const isFile = where.isFile();
arr.push({
url,
type: isDir ? "dir" : isFile ? "file" : "unknown",
name: file,
path: `${pathname === "/" ? "" : pathname}/${file}`,
});
}
return arr;
};
// Bun 托管静态文件
const server: Serve = {
async fetch(req) {
const pathname = new URL(req.url).pathname;
const path = `${process.cwd()}${BASEURL}/${pathname}`;
// 判断是文件还是目录
const [error, where] = await promiseAwait(stat(path));
if (!where) {
return new Response(error);
}
const isDir = where.isDirectory();
if (isDir) {
const dirs = await buildDir(path, pathname);
const html = await ejs.renderFile(`${import.meta.dir}/dir.ejs`, { dirs });
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
}
const isFile = where.isFile();
if (isFile) {
const file = Bun.file(path);
const exists = await file.exists();
if (exists) return new Response(file);
}
return new Response(null, { status: 404 });
},
};
export default server;
dir.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Directory</title>
<style>
li {
padding: 6px;
}
li.dir::marker {
content: "📁";
}
li.file::marker {
content: "📄";
}
li.back::marker {
content: "🔙";
}
</style>
</head>
<body>
<ul>
<% for (let i = 0; i < dirs.length; i++) { %> <% const item = dirs[i] %>
<li class="<%= item.type %>">
<a href="<%= item.path %>"> <%= item.name %> </a>
</li>
<% } %>
</ul>
</body>
</html>
usage
bun ./index.ts
example