mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
419 lines
11 KiB
Markdown
419 lines
11 KiB
Markdown
To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`.
|
|
|
|
```ts
|
|
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}`);
|
|
```
|
|
|
|
```bash
|
|
$ bun run app.ts
|
|
```
|
|
|
|
## HTML imports are routes
|
|
|
|
The web starts with HTML, and so does Bun's fullstack dev server.
|
|
|
|
To specify entrypoints to your frontend, import HTML files into your JavaScript/TypeScript/TSX/JSX files.
|
|
|
|
```ts
|
|
import dashboard from "./dashboard.html";
|
|
import homepage from "./index.html";
|
|
```
|
|
|
|
These HTML files are used as routes in Bun's dev server you can pass to `Bun.serve()`.
|
|
|
|
```ts
|
|
Bun.serve({
|
|
routes: {
|
|
"/": homepage,
|
|
"/dashboard": dashboard,
|
|
}
|
|
|
|
fetch(req) {
|
|
// ... api requests
|
|
},
|
|
});
|
|
```
|
|
|
|
When you make a request to `/dashboard` or `/`, Bun automatically bundles the `<script>` and `<link>` tags in the HTML files, exposes them as static routes, and serves the result.
|
|
|
|
An index.html file like this:
|
|
|
|
```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>
|
|
```
|
|
|
|
Becomes something like this:
|
|
|
|
```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>
|
|
```
|
|
|
|
### How to use with React
|
|
|
|
To use React in your client-side code, import `react-dom/client` and render your app.
|
|
|
|
{% codetabs %}
|
|
|
|
```ts#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");
|
|
},
|
|
});
|
|
```
|
|
|
|
```ts#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 />);
|
|
});
|
|
```
|
|
|
|
```html#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>
|
|
```
|
|
|
|
```css#src/styles.css
|
|
body {
|
|
background-color: red;
|
|
}
|
|
```
|
|
|
|
```tsx#src/app.tsx
|
|
export function App() {
|
|
return <div>Hello World</div>;
|
|
}
|
|
```
|
|
|
|
{% /codetabs %}
|
|
|
|
### Development mode
|
|
|
|
When building locally, enable development mode by setting `development: true` in `Bun.serve()`.
|
|
|
|
```js-diff
|
|
import homepage from "./index.html";
|
|
import dashboard from "./dashboard.html";
|
|
|
|
Bun.serve({
|
|
routes: {
|
|
"/": homepage,
|
|
"/dashboard": dashboard,
|
|
}
|
|
|
|
+ development: true,
|
|
|
|
fetch(req) {
|
|
// ... api requests
|
|
},
|
|
});
|
|
```
|
|
|
|
When `development` is `true`, Bun will:
|
|
|
|
- Include the `SourceMap` header in the response so that devtools can show the original source code
|
|
- Disable minification
|
|
- Re-bundle assets on each request to a .html file
|
|
- Enable hot module reloading (unless `hmr: false` is set)
|
|
|
|
#### Echo console logs from browser to terminal
|
|
|
|
Bun.serve() supports echoing console logs from the browser to the terminal.
|
|
|
|
To enable this, pass `console: true` in the `development` object in `Bun.serve()`.
|
|
|
|
```ts
|
|
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,
|
|
},
|
|
});
|
|
```
|
|
|
|
When `console: true` is set, Bun will stream console logs from the browser to the terminal. This reuses the existing WebSocket connection from HMR to send the logs.
|
|
|
|
#### Production mode
|
|
|
|
Hot reloading and `development: true` helps you iterate quickly, but in production, your server should be as fast as possible and have as few external dependencies as possible.
|
|
|
|
##### Ahead of time bundling (recommended)
|
|
|
|
As of Bun v1.2.17, you can use `Bun.build` or `bun build` to bundle your full-stack application ahead of time.
|
|
|
|
```sh
|
|
$ bun build --target=bun --production --outdir=dist ./src/index.ts
|
|
```
|
|
|
|
When Bun's bundler sees an HTML import from server-side code, it will bundle the referenced JavaScript/TypeScript/TSX/JSX and CSS files into a manifest object that Bun.serve() can use to serve the assets.
|
|
|
|
```ts
|
|
import { serve } from "bun";
|
|
import index from "./index.html";
|
|
|
|
serve({
|
|
routes: { "/": index },
|
|
});
|
|
```
|
|
|
|
{% details summary="Internally, the `index` variable is a manifest object that looks something like this" %}
|
|
|
|
```json
|
|
{
|
|
"index": "./index.html",
|
|
"files": [
|
|
{
|
|
"input": "index.html",
|
|
"path": "./index-f2me3qnf.js",
|
|
"loader": "js",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "eet6gn75",
|
|
"content-type": "text/javascript;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "index.html",
|
|
"path": "./index.html",
|
|
"loader": "html",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "r9njjakd",
|
|
"content-type": "text/html;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "index.html",
|
|
"path": "./index-gysa5fmk.css",
|
|
"loader": "css",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "50zb7x61",
|
|
"content-type": "text/css;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "logo.svg",
|
|
"path": "./logo-kygw735p.svg",
|
|
"loader": "file",
|
|
"isEntry": false,
|
|
"headers": {
|
|
"etag": "kygw735p",
|
|
"content-type": "application/octet-stream"
|
|
}
|
|
},
|
|
{
|
|
"input": "react.svg",
|
|
"path": "./react-ck11dneg.svg",
|
|
"loader": "file",
|
|
"isEntry": false,
|
|
"headers": {
|
|
"etag": "ck11dneg",
|
|
"content-type": "application/octet-stream"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
{% /details %}
|
|
|
|
##### Runtime bundling
|
|
|
|
When adding a build step is too complicated, you can set `development: false` in `Bun.serve()`.
|
|
|
|
- Enable in-memory caching of bundled assets. Bun will bundle assets lazily on the first request to an `.html` file, and cache the result in memory until the server restarts.
|
|
- Enables `Cache-Control` headers and `ETag` headers
|
|
- Minifies JavaScript/TypeScript/TSX/JSX files
|
|
|
|
## Plugins
|
|
|
|
Bun's [bundler plugins](https://bun.com/docs/bundler/plugins) are also supported when bundling static routes.
|
|
|
|
To configure plugins for `Bun.serve`, add a `plugins` array in the `[serve.static]` section of your `bunfig.toml`.
|
|
|
|
### Using TailwindCSS in HTML routes
|
|
|
|
For example, enable TailwindCSS on your routes by installing and adding the `bun-plugin-tailwind` plugin:
|
|
|
|
```sh
|
|
$ bun add bun-plugin-tailwind
|
|
```
|
|
|
|
```toml#bunfig.toml
|
|
[serve.static]
|
|
plugins = ["bun-plugin-tailwind"]
|
|
```
|
|
|
|
This will allow you to use TailwindCSS utility classes in your HTML and CSS files. All you need to do is import `tailwindcss` somewhere:
|
|
|
|
```html#index.html
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>Home</title>
|
|
<link rel="stylesheet" href="tailwindcss" />
|
|
</head>
|
|
<body>
|
|
<!-- the rest of your HTML... -->
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
Or in your CSS:
|
|
|
|
```css#style.css
|
|
@import "tailwindcss";
|
|
```
|
|
|
|
### Custom plugins
|
|
|
|
Any JS file or module which exports a [valid bundler plugin object](https://bun.com/docs/bundler/plugins#usage) (essentially an object with a `name` and `setup` field) can be placed inside the `plugins` array:
|
|
|
|
```toml#bunfig.toml
|
|
[serve.static]
|
|
plugins = ["./my-plugin-implementation.ts"]
|
|
```
|
|
|
|
Bun will lazily resolve and load each plugin and use them to bundle your routes.
|
|
|
|
Note: this is currently in `bunfig.toml` to make it possible to know statically which plugins are in use when we eventually integrate this with the `bun build` CLI. These plugins work in `Bun.build()`'s JS API, but are not yet supported in the CLI.
|
|
|
|
## How this works
|
|
|
|
Bun uses [`HTMLRewriter`](/docs/api/html-rewriter) to scan for `<script>` and `<link>` tags in HTML files, uses them as entrypoints for [Bun's bundler](/docs/bundler), generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files, and serves the result.
|
|
|
|
1. **`<script>` processing**
|
|
- Transpiles TypeScript, JSX, and TSX in `<script>` tags
|
|
- Bundles imported dependencies
|
|
- Generates sourcemaps for debugging
|
|
- Minifies when `development` is not `true` in `Bun.serve()`
|
|
|
|
```html
|
|
<script type="module" src="./counter.tsx"></script>
|
|
```
|
|
|
|
2. **`<link>` processing**
|
|
- Processes CSS imports and `<link>` tags
|
|
- Concatenates CSS files
|
|
- Rewrites `url` and asset paths to include content-addressable hashes in URLs
|
|
|
|
```html
|
|
<link rel="stylesheet" href="./styles.css" />
|
|
```
|
|
|
|
3. **`<img>` & asset processing**
|
|
- Links to assets are rewritten to include content-addressable hashes in URLs
|
|
- Small assets in CSS files are inlined into `data:` URLs, reducing the total number of HTTP requests sent over the wire
|
|
|
|
4. **Rewrite HTML**
|
|
- Combines all `<script>` tags into a single `<script>` tag with a content-addressable hash in the URL
|
|
- Combines all `<link>` tags into a single `<link>` tag with a content-addressable hash in the URL
|
|
- Outputs a new HTML file
|
|
|
|
5. **Serve**
|
|
- All the output files from the bundler are exposed as static routes, using the same mechanism internally as when you pass a `Response` object to [`static` in `Bun.serve()`](/docs/api/http#static-routes).
|
|
|
|
This works similarly to how [`Bun.build` processes HTML files](/docs/bundler/html).
|
|
|
|
## This is a work in progress
|
|
|
|
- This doesn't support `bun build` yet. It also will in the future.
|