Skip to content

Asset Pipeline

The asset pipeline bundles your JavaScript and CSS with Bun, fingerprints the output files for cache busting, and generates a manifest so your templates can reference the correct URLs. A companion middleware serves the fingerprinted files and resolves asset paths at runtime.

  1. Generate the starter files

    Terminal window
    pidgn assets setup

    This creates an assets/ directory with starter app.js and app.css files, a package.json, and a bunfig.toml.

  2. Install JavaScript dependencies

    Terminal window
    bun install
  3. Build the assets

    Terminal window
    pidgn assets build

    Bun minifies your source files, writes them to public/assets/, and generates fingerprinted copies alongside an assets-manifest.json.

When you run pidgn assets build:

  1. Bun bundles assets/app.js into public/assets/app.js (minified).
  2. assets/app.css is copied to public/assets/app.css.
  3. Each output file is hashed with FNV-1a and a fingerprinted copy is created (e.g. app-2b3e914a.js).
  4. An assets-manifest.json is written mapping original names to fingerprinted names.

The manifest format:

{
"app.js": "app-2b3e914a.js",
"app.css": "app-7f1dc3e8.css"
}

Add the assets middleware to your pipeline to load the manifest and serve fingerprinted files:

const App = pidgn.Router.define(.{
.middleware = &.{
pidgn.errorHandler(.{}),
pidgn.logger,
pidgn.gzipCompress(.{}),
pidgn.assets(.{
.manifest_path = "public/assets/assets-manifest.json",
.prefix = "/static/assets",
}),
pidgn.staticFiles(.{ .dir = "public", .prefix = "/static" }),
},
.routes = &.{ ... },
});
OptionTypeDefaultDescription
manifest_path[]const u8"public/assets/assets-manifest.json"Path to the manifest JSON file generated by pidgn assets build.
prefix[]const u8"/static/assets"URL prefix prepended to resolved asset paths.

The middleware lazily loads the manifest on the first request. Once loaded, it is cached globally for the lifetime of the process.

Use the assetPath function to resolve an asset name to its fingerprinted URL:

const asset_url = pidgn.assetPath("app.js");
// Returns "/static/assets/app-2b3e914a.js"

In a pidgn template:

<script src="{{ assetPath "app.js" }}"></script>
<link rel="stylesheet" href="{{ assetPath "app.css" }}">

If the manifest has not been loaded or the asset name is not found, assetPath returns the name unchanged.

For development, use watch mode to rebuild on every change:

Terminal window
pidgn assets watch

This starts Bun in watch mode and regenerates the manifest whenever a source file changes.

For production, run a single build:

Terminal window
pidgn assets build

The fingerprinted filenames ensure browsers fetch new versions after a deploy, while unchanged files remain cached.

After running pidgn assets setup and pidgn assets build, your project looks like this:

my_app/
assets/
app.js # Source JavaScript
app.css # Source CSS
public/
assets/
app.js # Bundled output
app.css # Copied output
app-2b3e914a.js # Fingerprinted JS
app-7f1dc3e8.css # Fingerprinted CSS
assets-manifest.json
package.json
bunfig.toml
src/
main.zig