Skip to main content
Bun’s bundler can process HTML files as entrypoints, automatically bundling all referenced assets (scripts, stylesheets, images, etc.) and updating the HTML to reference the bundled files.

Basic usage

Given an HTML file:
index.html
<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./app.ts"></script>
  </body>
</html>
Bundle it with:
bun build ./index.html --outdir ./dist
The output will be:
dist/
  index.html
  app-[hash].js
  styles-[hash].css
And the HTML will be updated to reference the hashed files:
dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <link rel="stylesheet" href="./styles-a1b2c3.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./app-d4e5f6.js"></script>
  </body>
</html>

Supported elements

Bun’s HTML processor recognizes and bundles the following elements:

Scripts

<!-- Module scripts are bundled -->
<script type="module" src="./app.ts"></script>

<!-- Regular scripts are also bundled -->
<script src="./legacy.js"></script>

<!-- Inline scripts are preserved -->
<script>
  console.log("Hello!");
</script>

Stylesheets

<!-- CSS files are bundled -->
<link rel="stylesheet" href="./styles.css" />

<!-- Multiple stylesheets are merged -->
<link rel="stylesheet" href="./reset.css" />
<link rel="stylesheet" href="./theme.css" />

Images

<!-- Images are hashed and copied -->
<img src="./logo.png" alt="Logo" />

<!-- srcset is also processed -->
<img
  src="./hero.jpg"
  srcset="./hero-1x.jpg 1x, ./hero-2x.jpg 2x"
  alt="Hero"
/>

Other assets

<!-- Favicons -->
<link rel="icon" href="./favicon.ico" />
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />

<!-- Fonts -->
<link
  rel="preload"
  href="./font.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

<!-- Manifests -->
<link rel="manifest" href="./manifest.json" />

<!-- Video and audio -->
<video src="./video.mp4" />
<audio src="./audio.mp3" />

External URLs

External URLs (those starting with http://, https://, or //) are preserved as-is:
<!-- These are NOT bundled -->
<script src="https://cdn.example.com/library.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter" />
<img src="https://example.com/remote-image.jpg" />

Public path

Use --public-path to prefix all asset URLs:
bun build ./index.html --outdir ./dist --public-path /assets/
Output
<script type="module" src="/assets/app-d4e5f6.js"></script>
<link rel="stylesheet" href="/assets/styles-a1b2c3.css" />
This is useful for CDN deployments:
bun build ./index.html --outdir ./dist --public-path https://cdn.example.com/

Minification

HTML is minified when --minify is enabled:
bun build ./index.html --outdir ./dist --minify
This removes:
  • Whitespace between elements
  • HTML comments (except conditional comments)
  • Unnecessary quotes around attribute values
  • Optional closing tags

Multiple HTML files

You can bundle multiple HTML files:
bun build ./index.html ./about.html ./contact.html --outdir ./dist
Each HTML file becomes an independent entrypoint with its own set of bundled assets.

Code splitting

When multiple HTML files share JavaScript or CSS, those assets can be code-split:
bun build ./index.html ./about.html --outdir ./dist --splitting
Shared code is extracted into separate chunk files, and each HTML file references the appropriate chunks.

JavaScript API

await Bun.build({
  entrypoints: ["./index.html"],
  outdir: "./dist",
  minify: true,
  publicPath: "/assets/",
});

Dynamic HTML generation

For server-side rendering or dynamic HTML generation, import HTML files in your JavaScript:
server.ts
import html from "./index.html";

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(html, {
      headers: { "Content-Type": "text/html" },
    });
  },
});
When bundled with --target=bun, the HTML import resolves to a manifest object for serving pre-bundled assets efficiently.

Full-stack applications

Combine HTML processing with server-side code:
server.ts
import html from "./index.html";

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    
    // Serve the HTML for the root path
    if (url.pathname === "/") {
      return new Response(html, {
        headers: { "Content-Type": "text/html" },
      });
    }
    
    // API routes
    if (url.pathname === "/api/data") {
      return Response.json({ message: "Hello!" });
    }
    
    return new Response("Not found", { status: 404 });
  },
});

console.log(`Server running at http://localhost:${server.port}`);
Build for production:
bun build ./server.ts --outdir ./dist --target=bun --compile
This creates a standalone executable with all assets embedded.

Templates and components

For more complex HTML generation, use template literals or JSX:
server.tsx
function Layout({ children }: { children: any }) {
  return (
    <html>
      <head>
        <title>My App</title>
        <link rel="stylesheet" href="./styles.css" />
      </head>
      <body>
        <div id="root">{children}</div>
        <script type="module" src="./app.ts" />
      </body>
    </html>
  );
}

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    const html = (
      <Layout>
        <h1>Welcome!</h1>
      </Layout>
    );
    
    return new Response(html, {
      headers: { "Content-Type": "text/html" },
    });
  },
});

Asset hashing

All bundled assets are content-hashed by default:
app-a1b2c3d4.js
styles-e5f6g7h8.css
logo-i9j0k1l2.png
This enables:
  • Long-term caching (files can be cached forever)
  • Cache invalidation (changing content changes the hash)
  • Parallel loading (no version conflicts)

Source maps

Generate source maps for JavaScript bundles:
bun build ./index.html --outdir ./dist --sourcemap=external
Source map files are placed alongside their corresponding bundles:
dist/
  index.html
  app-a1b2c3.js
  app-a1b2c3.js.map

Examples

Static site

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Static Site</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <header>
      <img src="./logo.svg" alt="Logo" />
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
    </header>
    <main id="content"></main>
    <script type="module" src="./app.ts"></script>
  </body>
</html>
bun build ./index.html --outdir ./dist --minify

Single-page application

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>SPA</title>
    <link rel="stylesheet" href="./src/styles/main.css" />
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./src/main.tsx"></script>
  </body>
</html>
src/main.tsx
import { render } from "react-dom";
import { App } from "./App";

render(<App />, document.getElementById("app"));
bun build ./index.html --outdir ./dist --minify --splitting

Progressive Web App

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>PWA</title>
    <link rel="manifest" href="./manifest.json" />
    <link rel="icon" href="./favicon.ico" />
    <link rel="apple-touch-icon" href="./icon-192.png" />
    <meta name="theme-color" content="#000000" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./app.ts"></script>
    <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./sw.js');
      }
    </script>
  </body>
</html>
bun build ./index.html --outdir ./dist --minify --public-path /

Troubleshooting

Paths not resolving

Make sure all asset paths are relative to the HTML file:
<!-- Good -->
<script src="./app.ts"></script>
<img src="./images/logo.png" />

<!-- Bad -->
<script src="app.ts"></script>
<img src="images/logo.png" />

Assets not found

Check that the referenced files exist relative to the HTML file:
project/
  index.html
  app.ts
  images/
    logo.png

External URLs bundled

Ensure external URLs start with http://, https://, or //:
<!-- These are external -->
<script src="https://cdn.example.com/lib.js"></script>
<script src="//cdn.example.com/lib.js"></script>

<!-- This will be bundled -->
<script src="cdn.example.com/lib.js"></script>

Inline scripts interfering

Inline scripts run immediately and might interfere with bundled modules. Use type="module" for modern code:
<!-- Modern approach -->
<script type="module" src="./app.ts"></script>

<!-- Inline module -->
<script type="module">
  import { init } from "./app.ts";
  init();
</script>

Build docs developers (and LLMs) love