獨立 HTML
將單頁應用打包成一個無外部相依的自包含 .html 檔案
Bun 可以將整個前端打包成一個 .html 檔案,且不相依任何外部資源。JavaScript、TypeScript、JSX、CSS、圖片、字型、影片、WASM:Bun 會將所有內容內聯到一個檔案中。
bun build --compile --target=browser ./index.html --outdir=dist
輸出的是一個自包含的 HTML 文件:沒有相對路徑、沒有外部檔案,也不需要伺服器。
單檔案。隨處上傳。
輸出是一個單獨的 .html 檔案,你可以放到任何地方:
- 上傳到 S3 或任何靜態檔案託管服務——無需維護目錄結構,只需一個檔案
- 從桌面雙擊開啟——它會在瀏覽器中開啟並離線執行,無需本地主機伺服器
- 嵌入到 WebView 中——無需處理相對檔案
- 插入
<iframe>中——只需一個檔案 URL,即可將互動式內容嵌入另一個頁面 - 從任意位置提供服務——任何 HTTP 伺服器、CDN 或檔案共享服務都可以
無需安裝任何東西,無需部署 node_modules,無需協調建置產物,也無需考慮相對路徑。
真正的單檔案
通常,分發網頁意味著需要管理一個資原始檔夾——HTML、JavaScript 包、CSS 檔案和圖片。單獨移動 HTML 檔案而不帶上其他檔案,一切都會出錯。瀏覽器以前也嘗試過解決這個問題。Safari 的 .webarchive 和 Chrome 的 .mhtml 可以將網頁儲存為單個檔案。實際上,只有部分瀏覽器能開啟它們,這就失去了這樣做的意義。
獨立 HTML 輸出是一個普通的 .html 檔案:不是歸檔檔案,也不是資料夾。Bun 使用標準的 <style> 標籤、<script> 標籤和 data: URI,將每張圖片、每種字型以及每一行 CSS 和 JavaScript 都直接嵌入 HTML 中。任何瀏覽器都能開啟它,任何伺服器都能託管它。
你可以像分發 PDF 一樣分發頁面:這是一個可以移動、複製、上傳或分享的單一檔案,無需擔心路徑失效或資源缺失。
快速開始
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
import React from "react";
import { createRoot } from "react-dom/client";
function App() {
return <h1>來自單個 HTML 檔案的問候!</h1>;
}
createRoot(document.getElementById("root")!).render(<App />);
body {
margin: 0;
font-family: system-ui, sans-serif;
background: #f5f5f5;
}
bun build --compile --target=browser ./index.html --outdir=dist
開啟 dist/index.html —— React 應用無需伺服器即可執行。
所有內容均內聯
Bun 會將 HTML 中找到的每個本地資源都進行內聯:任何具有相對路徑的檔案,無論檔案型別是什麼,都會嵌入到輸出檔案中。
什麼內容會被內聯
| 在原始檔中 | 在輸出檔案中 |
|---|---|
<script src="./app.tsx"> | <script type="module">...bundled code...</script> |
<link rel="stylesheet" href="./styles.css"> | <style>...bundled CSS...</style> |
<img src="./logo.png"> | <img src="data:image/png;base64,..."> |
<img src="./icon.svg"> | <img src="data:image/svg+xml;base64,..."> |
<video src="./demo.mp4"> | <video src="data:video/mp4;base64,..."> |
<audio src="./click.wav"> | <audio src="data:audio/x-wav;base64,..."> |
<source src="./clip.webm"> | <source src="data:video/webm;base64,..."> |
<video poster="./thumb.jpg"> | <video poster="data:image/jpeg;base64,..."> |
<link rel="icon" href="./favicon.ico"> | <link rel="icon" href="data:image/x-icon;base64,..."> |
<link rel="manifest" href="./app.webmanifest"> | <link rel="manifest" href="data:application/manifest+json;base64,..."> |
CSS 中的 url("./bg.png") | CSS 中的 url(data:image/png;base64,...) |
CSS 中的 @import "./reset.css" | 合併到 <style> 標籤中 |
CSS 中的 url("./font.woff2") | CSS 中的 url(data:font/woff2;base64,...) |
JS 中的 import "./styles.css" | 合併到 <style> 標籤中 |
圖片、字型、WASM 二進位制檔案、影片、音訊檔案、SVG:Bun 會將相對路徑引用的任何檔案編碼為 base64 格式的 data: URI,並直接嵌入 HTML 中。Bun 會根據副檔名檢測 MIME 型別。
Bun 會原樣保留外部 URL(例如 CDN 連結或絕對 URL)。
與 React 一起使用
React 應用無需額外設定:Bun 會轉譯 JSX 並解析 npm 包。
bun install react react-dom
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>我的應用</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { Counter } from "./components/Counter.tsx";
function App() {
return (
<main>
<h1>單檔案 React 應用</h1>
<Counter />
</main>
);
}
createRoot(document.getElementById("root")!).render(<App />);
import React, { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>計數:{count}</button>;
}
bun build --compile --target=browser ./index.html --outdir=dist
Bun 會將所有 React 程式碼、元件和 CSS 打包到 dist/index.html 中。將這一個檔案上傳到任何地方,它都能正常執行。
與 Tailwind CSS 一起使用
安裝外掛並在 HTML 或 CSS 中引用 Tailwind:
bun install --dev bun-plugin-tailwind
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="tailwindcss" />
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div id="root"></div>
<script src="./app.tsx"></script>
</body>
</html>
import React from "react";
import { createRoot } from "react-dom/client";
function App() {
return (
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
<h1 className="text-2xl font-bold text-gray-800">你好 Tailwind</h1>
<p className="text-gray-600 mt-2">這是一個單檔案 HTML。</p>
</div>
);
}
createRoot(document.getElementById("root")!).render(<App />);
使用 JavaScript API 通過外掛建置:
await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist",
plugins: [require("bun-plugin-tailwind")],
});
bun run build.ts
Bun 會將生成的 Tailwind CSS 以內聯 <style> 標籤的形式直接放入 HTML 檔案中。
工作原理
當你傳入 --compile --target=browser 且入口是 HTML 檔案時,Bun 會:
- 解析 HTML,發現所有
<script>、<link>、<img>、<video>、<audio>、<source>及其他資源引用 - 將所有 JavaScript/TypeScript/JSX 打包為單個模組
- 將所有 CSS(包括
@import鏈和 JS 中匯入的 CSS)打包成一個樣式表 - 將所有相對資源引用轉換為 base64 的
data:URI - 將打包後的 JS 以內聯
<script type="module">形式放在</body>前 - 將打包後的 CSS 以內聯
<style>形式放在<head>內 - 輸出一個不相依任何外部檔案的單個
.html檔案。
壓縮
新增 --minify 來壓縮 JavaScript 和 CSS:
bun build --compile --target=browser --minify ./index.html --outdir=dist
或者使用 JavaScript API:
await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist",
minify: true,
});
JavaScript API
使用 Bun.build() 以程式設計方式生成獨立的 HTML:
const result = await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
outdir: "./dist", // 可選,省略則作為 BuildArtifact 輸出
minify: true,
});
if (!result.success) {
console.error("建置失敗:");
for (const log of result.logs) {
console.error(log);
}
} else {
console.log("建置成功:", result.outputs[0].path);
}
省略 outdir 時,輸出會以 BuildArtifact 的形式儲存在 result.outputs 中:
const result = await Bun.build({
entrypoints: ["./index.html"],
compile: true,
target: "browser",
});
const html = await result.outputs[0].text();
await Bun.write("output.html", html);
多個 HTML 檔案
你可以傳入多個 HTML 檔案作為入口,分別生成獨立 HTML 檔案:
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
環境變數
使用 --env 將環境變數內聯到打包的 JavaScript 中:
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
Bun 會在建置時將 JavaScript 中對 process.env.API_URL 的引用替換為字面值。
限制
- 不支援程式碼拆分——
--splitting不能與--compile --target=browser一起使用 - 大型資源會增加檔案大小,因為它們會被編碼為 base64(與原始二進位制檔案相比,體積增加 33%)
- 外部 URL(CDN 連結、絕對 URL)會保持原樣:Bun 只內聯相對路徑