diff --git a/docs/bundler/executables.mdx b/docs/bundler/executables.mdx index 50d1a7cafd..ec4314d6df 100644 --- a/docs/bundler/executables.mdx +++ b/docs/bundler/executables.mdx @@ -413,34 +413,141 @@ cd /home/me/Desktop ## Embed assets & files -Standalone executables support embedding files. +Standalone executables support embedding files directly into the binary. This lets you ship a single executable that contains images, JSON configs, templates, or any other assets your application needs. -To embed files into an executable with `bun build --compile`, import the file in your code. +### How it works + +Use the `with { type: "file" }` [import attribute](https://github.com/tc39/proposal-import-attributes) to embed a file: + +```ts index.ts icon="/icons/typescript.svg" +import icon from "./icon.png" with { type: "file" }; + +console.log(icon); +// During development: "./icon.png" +// After compilation: "$bunfs/icon-a1b2c3d4.png" (internal path) +``` + +The import returns a **path string** that points to the embedded file. At build time, Bun: + +1. Reads the file contents +2. Embeds the data into the executable +3. Replaces the import with an internal path (prefixed with `$bunfs/`) + +You can then read this embedded file using `Bun.file()` or Node.js `fs` APIs. + +### Reading embedded files with Bun.file() + +`Bun.file()` is the recommended way to read embedded files: ```ts index.ts icon="/icons/typescript.svg" -// this becomes an internal file path import icon from "./icon.png" with { type: "file" }; import { file } from "bun"; +// Get file contents as different types +const bytes = await file(icon).arrayBuffer(); // ArrayBuffer +const text = await file(icon).text(); // string (for text files) +const blob = file(icon); // Blob + +// Stream the file in a response export default { fetch(req) { - // Embedded files can be streamed from Response objects - return new Response(file(icon)); + return new Response(file(icon), { + headers: { "Content-Type": "image/png" }, + }); }, }; ``` -Embedded files can be read using `Bun.file`'s functions or the Node.js `fs.readFile` function (in `"node:fs"`). +### Reading embedded files with Node.js fs -For example, to read the contents of the embedded file: +Embedded files work seamlessly with Node.js file system APIs: ```ts index.ts icon="/icons/typescript.svg" import icon from "./icon.png" with { type: "file" }; +import config from "./config.json" with { type: "file" }; +import { readFileSync, promises as fs } from "node:fs"; + +// Synchronous read +const iconBuffer = readFileSync(icon); + +// Async read +const configData = await fs.readFile(config, "utf-8"); +const parsed = JSON.parse(configData); + +// Check file stats +const stats = await fs.stat(icon); +console.log(`Icon size: ${stats.size} bytes`); +``` + +### Practical examples + +#### Embedding a JSON config file + +```ts index.ts icon="/icons/typescript.svg" +import configPath from "./default-config.json" with { type: "file" }; import { file } from "bun"; -const bytes = await file(icon).arrayBuffer(); -// await fs.promises.readFile(icon) -// fs.readFileSync(icon) +// Load the embedded default configuration +const defaultConfig = await file(configPath).json(); + +// Merge with user config if it exists +const userConfig = await file("./user-config.json") + .json() + .catch(() => ({})); +const config = { ...defaultConfig, ...userConfig }; +``` + +#### Serving static assets in an HTTP server + +Use `static` routes in `Bun.serve()` for efficient static file serving: + +```ts server.ts icon="/icons/typescript.svg" +import favicon from "./favicon.ico" with { type: "file" }; +import logo from "./logo.png" with { type: "file" }; +import styles from "./styles.css" with { type: "file" }; +import { file, serve } from "bun"; + +serve({ + static: { + "/favicon.ico": file(favicon), + "/logo.png": file(logo), + "/styles.css": file(styles), + }, + fetch(req) { + return new Response("Not found", { status: 404 }); + }, +}); +``` + +Bun automatically handles Content-Type headers and caching for static routes. + +#### Embedding templates + +```ts index.ts icon="/icons/typescript.svg" +import templatePath from "./email-template.html" with { type: "file" }; +import { file } from "bun"; + +async function sendWelcomeEmail(user: { name: string; email: string }) { + const template = await file(templatePath).text(); + const html = template.replace("{{name}}", user.name).replace("{{email}}", user.email); + + // Send email with the rendered template... +} +``` + +#### Embedding binary files + +```ts index.ts icon="/icons/typescript.svg" +import wasmPath from "./processor.wasm" with { type: "file" }; +import fontPath from "./font.ttf" with { type: "file" }; +import { file } from "bun"; + +// Load a WebAssembly module +const wasmBytes = await file(wasmPath).arrayBuffer(); +const wasmModule = await WebAssembly.instantiate(wasmBytes); + +// Read binary font data +const fontData = await file(fontPath).bytes(); ``` ### Embed SQLite databases @@ -507,22 +614,57 @@ This is honestly a workaround, and we expect to improve this in the future with ### Listing embedded files -To get a list of all embedded files, use `Bun.embeddedFiles`: +`Bun.embeddedFiles` gives you access to all embedded files as `Blob` objects: ```ts index.ts icon="/icons/typescript.svg" import "./icon.png" with { type: "file" }; +import "./data.json" with { type: "file" }; +import "./template.html" with { type: "file" }; import { embeddedFiles } from "bun"; -console.log(embeddedFiles[0].name); // `icon-${hash}.png` +// List all embedded files +for (const blob of embeddedFiles) { + console.log(`${blob.name} - ${blob.size} bytes`); +} +// Output: +// icon-a1b2c3d4.png - 4096 bytes +// data-e5f6g7h8.json - 256 bytes +// template-i9j0k1l2.html - 1024 bytes ``` -`Bun.embeddedFiles` returns an array of `Blob` objects which you can use to get the size, contents, and other properties of the files. +Each item in `Bun.embeddedFiles` is a `Blob` with a `name` property: ```ts -embeddedFiles: Blob[] +embeddedFiles: ReadonlyArray; ``` -The list of embedded files excludes bundled source code like `.ts` and `.js` files. +This is useful for dynamically serving all embedded assets using `static` routes: + +```ts server.ts icon="/icons/typescript.svg" +import "./public/favicon.ico" with { type: "file" }; +import "./public/logo.png" with { type: "file" }; +import "./public/styles.css" with { type: "file" }; +import { embeddedFiles, serve } from "bun"; + +// Build static routes from all embedded files +const staticRoutes: Record = {}; +for (const blob of embeddedFiles) { + // Remove hash from filename: "icon-a1b2c3d4.png" -> "icon.png" + const name = blob.name.replace(/-[a-f0-9]+\./, "."); + staticRoutes[`/${name}`] = blob; +} + +serve({ + static: staticRoutes, + fetch(req) { + return new Response("Not found", { status: 404 }); + }, +}); +``` + + + `Bun.embeddedFiles` excludes bundled source code (`.ts`, `.js`, etc.) to help protect your application's source. + #### Content hash