Bun

全栈开发服务器

首先,导入 HTML 文件并将它们作为 routes 选项传递给 Bun.serve()

import { sql, serve } from "bun";
import dashboard from "./dashboard.html";
import homepage from "./index.html";

const server = serve({
  routes: {
    // ** HTML imports **
    // Bundle & route index.html to "/". This uses HTMLRewriter to scan the HTML for `<script>` and `<link>` tags, run's Bun's JavaScript & CSS bundler on them, transpiles any TypeScript, JSX, and TSX, downlevels CSS with Bun's CSS parser and serves the result.
    "/": homepage,
    // Bundle & route dashboard.html to "/dashboard"
    "/dashboard": dashboard,

    // ** API endpoints ** (Bun v1.2.3+ required)
    "/api/users": {
      async GET(req) {
        const users = await sql`SELECT * FROM users`;
        return Response.json(users);
      },
      async POST(req) {
        const { name, email } = await req.json();
        const [user] =
          await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
        return Response.json(user);
      },
    },
    "/api/users/:id": async req => {
      const { id } = req.params;
      const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
      return Response.json(user);
    },
  },

  // Enable development mode for:
  // - Detailed error messages
  // - Hot reloading (Bun v1.2.3+ required)
  development: true,

  // Prior to v1.2.3, the `fetch` option was used to handle all API requests. It is now optional.
  // async fetch(req) {
  //   // Return 404 for unmatched routes
  //   return new Response("Not Found", { status: 404 });
  // },
});

console.log(`Listening on ${server.url}`);
bun run app.ts

HTML 导入即路由

Web 从 HTML 开始,Bun 的全栈开发服务器也是如此。

要指定前端的入口点,请将 HTML 文件导入到您的 JavaScript/TypeScript/TSX/JSX 文件中。

import dashboard from "./dashboard.html";
import homepage from "./index.html";

这些 HTML 文件在 Bun 的开发服务器中用作路由,您可以将其传递给 Bun.serve()

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  }

  fetch(req) {
    // ... api requests
  },
});

当您向 /dashboard/ 发出请求时,Bun 会自动捆绑 HTML 文件中的 <script><link> 标签,将它们暴露为静态路由,并提供结果。

像这样的 index.html 文件

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>

变成这样

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>

如何与 React 一起使用

要在客户端代码中使用 React,请导入 react-dom/client 并渲染您的应用程序。

src/backend.ts
src/frontend.tsx
public/dashboard.html
src/styles.css
src/app.tsx
src/backend.ts
import dashboard from "../public/dashboard.html";
import { serve } from "bun";

serve({
  routes: {
    "/": dashboard,
  },

  async fetch(req) {
    // ...api requests
    return new Response("hello world");
  },
});
src/frontend.tsx
import "./styles.css";
import { createRoot } from "react-dom/client";
import { App } from "./app.tsx";

document.addEventListener("DOMContentLoaded", () => {
  const root = createRoot(document.getElementById("root"));
  root.render(<App />);
});
public/dashboard.html
<!DOCTYPE html>
<html>
  <head>
    <title>Dashboard</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/frontend.tsx"></script>
  </body>
</html>
src/styles.css
body {
  background-color: red;
}
src/app.tsx
export function App() {
  return <div>Hello World</div>;
}

开发模式

在本地构建时,通过在 Bun.serve() 中设置 development: true 来启用开发模式。

import homepage from "./index.html";
import dashboard from "./dashboard.html";

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  }

  development: true,

  fetch(req) {
    // ... api requests
  },
});

developmenttrue 时,Bun 将

  • 在响应中包含 SourceMap 头,以便开发工具可以显示原始源代码
  • 禁用压缩
  • 每次请求 .html 文件时重新捆绑资产
  • 启用热模块重新加载(除非设置了 hmr: false

将浏览器控制台日志回显到终端

Bun.serve() 支持将浏览器控制台日志回显到终端。

要启用此功能,请在 Bun.serve()development 对象中传递 console: true

import homepage from "./index.html";

Bun.serve({
  // development can also be an object.
  development: {
    // Enable Hot Module Reloading
    hmr: true,

    // Echo console logs from the browser to the terminal
    console: true,
  },

  routes: {
    "/": homepage,
  },
});

当设置 console: true 时,Bun 会将浏览器中的控制台日志流式传输到终端。这会重用 HMR 的现有 WebSocket 连接来发送日志。

生产模式

热重载和 development: true 有助于您快速迭代,但在生产环境中,您的服务器应该尽可能快,并且尽可能少地依赖外部依赖项。

从 Bun v1.2.17 开始,您可以使用 Bun.buildbun build 提前捆绑您的全栈应用程序。

bun build --target=bun --production --outdir=dist ./src/index.ts

当 Bun 的捆绑器从服务器端代码中看到 HTML 导入时,它会将引用的 JavaScript/TypeScript/TSX/JSX 和 CSS 文件捆绑到一个清单对象中,Bun.serve() 可以使用该对象来提供资产。

import { serve } from "bun";
import index from "./index.html";

serve({
  routes: { "/": index },
});

在内部,index 变量是一个清单对象,看起来像这样

运行时捆绑

当添加构建步骤过于复杂时,您可以在 Bun.serve() 中设置 development: false

  • 启用捆绑资产的内存缓存。Bun 将在第一次请求 .html 文件时延迟捆绑资产,并将结果缓存到内存中,直到服务器重新启动。
  • 启用 Cache-Control 头和 ETag
  • 压缩 JavaScript/TypeScript/TSX/JSX 文件

插件

Bun 的捆绑器插件在捆绑静态路由时也受支持。

要为 Bun.serve 配置插件,请在 bunfig.toml[serve.static] 部分添加一个 plugins 数组。

在 HTML 路由中使用 TailwindCSS

例如,通过安装并添加 bun-plugin-tailwind 插件,在您的路由上启用 TailwindCSS

bun add bun-plugin-tailwind
bunfig.toml
[serve.static]
plugins = ["bun-plugin-tailwind"]

这将允许您在 HTML 和 CSS 文件中使用 TailwindCSS 实用程序类。您只需在某个地方导入 tailwindcss

index.html
<!doctype html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <body>
    <!-- the rest of your HTML... -->
  </body>
</html>

或者在您的 CSS 中

style.css
@import "tailwindcss";

自定义插件

任何导出有效捆绑器插件对象(本质上是具有 namesetup 字段的对象)的 JS 文件或模块都可以放在 plugins 数组中

bunfig.toml
[serve.static]
plugins = ["./my-plugin-implementation.ts"]

Bun 将惰性地解析和加载每个插件,并使用它们来捆绑您的路由。

注意:这目前在 bunfig.toml 中,以便在我们最终将其与 bun build CLI 集成时,可以静态地知道正在使用哪些插件。这些插件在 Bun.build() 的 JS API 中有效,但尚不支持 CLI。

工作原理

Bun 使用 HTMLRewriter 扫描 HTML 文件中的 <script><link> 标签,将它们用作 Bun 的捆绑器的入口点,为 JavaScript/TypeScript/TSX/JSX 和 CSS 文件生成优化的捆绑包,并提供结果。

  1. <script> 处理

    • 转译 <script> 标签中的 TypeScript、JSX 和 TSX
    • 捆绑导入的依赖项
    • 生成用于调试的 SourceMap
    • Bun.serve()development 不为 true 时进行压缩
    <script type="module" src="./counter.tsx"></script>
    
  2. <link> 处理

    • 处理 CSS 导入和 <link> 标签
    • 连接 CSS 文件
    • 重写 url 和资产路径,以在 URL 中包含内容寻址哈希
    <link rel="stylesheet" href="./styles.css" />
    
  3. <img> 和资产处理

    • 资产链接被重写为在 URL 中包含内容寻址哈希
    • CSS 文件中的小型资产被内联到 data: URL 中,减少了通过网络发送的 HTTP 请求总数
  4. 重写 HTML

    • 将所有 <script> 标签组合成一个带有内容寻址哈希的 <script> 标签
    • 将所有 <link> 标签组合成一个带有内容寻址哈希的 <link> 标签
    • 输出新的 HTML 文件
  5. 服务

    • 所有来自捆绑器的输出文件都作为静态路由公开,内部机制与将 Response 对象传递给 Bun.serve() 中的 static 相同。

这与 Bun.build 处理 HTML 文件的方式类似。

这是一个正在进行中的工作

  • 这还不支持 bun build。未来也会支持。