Assets @ stwoo.net

Gohyde's asset pipeline (internal/assets) walks the
configured source directories, processes each file (Liquid, Sass, fingerprint),
writes the result to the output directory, and records a manifest.json so
Liquid tags and filters resolve to the final URL.

The pipeline runs before the page render pass, so asset_path / asset_url
and the {% asset %} family always see the current digest in the same build.

Configuration

assets:
  digest: false            # content-hash fingerprinting (true for production)
  destination: assets      # output subdir under the site root
  sources:                 # directories scanned, in order
    - _assets
    - _assets/css
    - _assets/js
    - _assets/images
    - assets
  cdn:
    url: "https://cdn.example.com"   # absolute CDN base; overrides baseurl

URLs, baseurl, and CDN

Asset URLs include the site baseurl so sub-path deploys resolve correctly:

baseurl: "/routeway"
{{ "logo.svg" | asset_path }}      → /routeway/assets/logo.svg
{% asset "app.css" @path %}        → /routeway/assets/app.css

Precedence:

  1. If assets.cdn.url is set, the URL is <cdn.url>/assets/<file> (baseurl is
    not applied — the CDN URL is already absolute).
  2. Otherwise the URL is <baseurl>/<destination>/<file>.

Asset URLs carry only the baseurl, never site.url. For an absolute URL,
prepend {{ site.url }} in the template:

<meta property="og:image"
      content="{{ site.url }}{% asset {{ site.mainlogo }} @path %}" />
<!-- → https://example.com/routeway/assets/logo.svg -->

Fingerprinting & the dev server

digest (default true) appends a content hash to asset filenames
(style.cssstyle-a1b2c3d4.css) for cache-busting. It is forced off
under gohyde serve
regardless of config, so dev URLs stay stable across live
reloads — no churn while you edit. gohyde build fingerprints as configured.
Treat digests as a production concern.

Liquid filters

{{ "css/style.css" | asset_path }}   → /assets/css/style-a1b2c3d4.css   (when digest on)
{{ "css/style.css" | asset_url }}    → same (alias)
{{ "css/style.css" | asset_digest }} → a1b2c3d4

The manifest is written to <destination>/assets/manifest.json.

Asset tags

Four tags emit the right HTML for the asset type:

{% asset "app.css" %}
→ <link rel="stylesheet" href="/assets/app-a1b2c3d4.css">

{% asset "app.js" defer %}
→ <script src="/assets/app-a1b2c3d4.js" defer></script>

{% asset "logo.png" alt="Logo" class="brand" %}
→ <img src="/assets/logo-a1b2c3d4.png" width="200" height="80" alt="Logo" class="brand">

{% javascript "main" async %}      → <script src="/assets/js/main-….js" async></script>
{% stylesheet "main" media="print" %} → <link rel="stylesheet" href="/assets/css/main-….css" media="print">
{% image "hero.jpg" alt="Hero" %}  → <img src="/assets/images/hero-….jpg" …>

URL-only flags

Append any of @uri, @src, @href, @path, @url to get just the resolved
URL instead of a full HTML tag:

<meta property="og:image" content="{{ site.url }}{% asset site.mainlogo @path %}" />
<div style="background:url({% asset 'bg.svg' @url %})"></div>

Dynamic asset names

The first argument can be quoted, a variable, or a {{ … }} expression:

{% asset "logo.svg" %}              ← string literal
{% asset item.cover %}              ← resolved against the loop/page variable
{% asset {{ site.mainlogo }} @path %}   ← config value via {{ … }}

Missing assets

A missing asset logs a warning to stderr and renders an empty string. It
never injects an HTML comment like <!-- asset error --> into the output, and it
never hard-fails the build:

  Warning: asset "missing.svg" not found

Source file processing

The processor is keyed by the file's source extension:

Source file Output Processing
main.js.liquid main.js Liquid-rendered (with site in scope), .liquid stripped
style.scss style.css Compiled by Dart Sass
style.scss.liquid style.css Liquid first, then Sass
app.css app.css Copied (fingerprinted if digest: true)

Files prefixed with _ (Sass partials) are never emitted standalone.

CMS media library: the CMS serves its image library from the first
assets.sources entry ending in images (e.g. _assets/images), falling
back to assets/images. Picking an image from a pipeline source inserts the
bare asset key (for {% asset %}), not an absolute path.

Stale-output gotcha: raw *.liquid / *.scss files appearing in your
output dir are leftovers from an earlier dirty build, not a processing leak.
Clean the destination (rm -rf _site / _builds) and rebuild.

Sass

Gohyde compiles .scss/.sass via Dart Sass when a real sass/dart-sass
binary is on PATH. It verifies the binary with --version and rejects the
legacy Ruby sass gem
(which can't accept --no-source-map). With no real
compiler available, sources pass through unchanged and a warning is printed —
the build still succeeds.

sass:
  sass_dir: _sass
  style: compressed

Tailwind

tailwind:
  enabled: true
  input: assets/css/input.css
  output: assets/css/tailwind.css
  bundles:                          # optional: extra css compilations
    - input: _assets/css/admin.css
      output: assets/admin.min.css

Each bundles entry gets its own CLI run — and its own --watch process under
gohyde serve. A failing bundle doesn't stop the others.

gohyde serve starts tailwindcss --watch only when enabled: true or the
input file exists — so sites that disable Tailwind don't get spammed with
input.css does not exist.