A small, cross-platform static site generator for Scala 3 — Hugo-style, built on a stack of small libraries:
- markdown — CommonMark 0.31.2 parser
- squiggly — Hugo/Liquid-style template engine
- toml — site config (
site.toml) - scala-yaml — YAML frontmatter
- path + cross_platform — file I/O across JVM / Scala.js / Scala Native
- emoji —
:smile:→ 😄
Two ready-to-build examples ship with the source:
sbt 'juicerJVM/run build --source examples/minimal'
sbt 'juicerJVM/run build --source examples/docs-site'Each builds into <source>/public/. For live preview during authoring:
sbt 'juicerJVM/run serve --source examples/docs-site --port 8080'then open http://localhost:8080/.
libraryDependencies += "io.github.edadma" %%% "juicer" % "0.2.0"The CLI is the typical way to use it; the library API exposes
io.github.edadma.juicer.App.build(...) and App.run(args) for callers
that want to embed it in another build pipeline.
mysite/
├── site.toml # site config (overlays a built-in baseline)
├── content/ # markdown sources with YAML frontmatter
│ ├── _index.md
│ └── posts/
│ └── hello-world.md
├── layouts/_default/ # squiggly templates that wrap content
│ ├── file.html # used for individual pages
│ └── folder.html # used for *_index.md* pages
├── partials/ # reusable squiggly fragments
├── shortcodes/ # [= name args =] preprocessor templates
└── static/ # copied as-is into the output
A markdown file looks like:
---
title: Hello, World
date: 2026-05-07
tags:
- intro
---
This is the body.YAML frontmatter (between --- lines) is parsed by scala-yaml; the body
goes through markdown with auto-generated heading IDs; the result is
rendered into the page's layout via squiggly.
Juicer Site Generator v0.2.0
Usage: juicer [options] [command]
-b, --baseurl <URL> base site URL (overrides site.toml)
-c, --config <name> base site configuration (default 'standard';
others: 'simple', 'norme')
-h, --help prints this usage text
-v, --verbose verbose output
--version prints the version
Commands:
build build the site
-s, --source <path> site source directory (default ./)
-d, --dest <path> destination directory (default ./public)
config show the resolved build configuration
-s, --source <path> site source directory
serve build the site, then serve it on localhost
-s, --source <path> site source directory
-d, --dest <path> destination directory
--host <host> host to bind (default 'localhost')
-p, --port <port> port to listen on (default 8080)
serve is JVM-only — it uses com.sun.net.httpserver under the hood.
build and config work on JVM, Scala.js, and Scala Native.
site.toml overlays one of three built-in baselines (selected by -c).
The standard baseline (default) expects the layout shown above. The
baselines themselves are in
BaseConfigs.scala.
title = "My Site"
author = "Ed"
baseURL = "https://example.com"
# Optional: drive the sidebar / nav order. Items ending in a markdown
# extension reference a content file (path relative to content/);
# anything else is a section label.
nav = [
"Getting Started",
"_index.md",
"guide/installation.md",
"Reference",
"guide/cheatsheet.md",
]Layouts and partials use squiggly syntax: {{ .field }},
{{ for x <- .items }}, {{ if cond }}, {{ partial 'name' . }},
{{ define name }} … {{ end }}. The page-rendering context exposes:
| key | what |
|---|---|
site |
site config (TOML) plus toc and start nav data |
page |
the page's YAML frontmatter (any-data shape) |
content |
the rendered markdown body (HTML) |
toc |
the page's heading tree |
sub |
flattened sub-headings (for an "On this page" list) |
| name | what |
|---|---|
relURL '...' |
site-relative URL — prepends baseURL.path |
absURL '...' |
absolute URL — prepends baseURL.base + baseURL.path |
markdownify '...' |
render a string as markdown into HTML |
emojify '...' |
substitute :shortcode: → emoji glyph |
- Auto heading IDs — every
<h*N*>gets anidattribute slugified from its text content, suitable for permalinks. - TOC tree —
{{ for h <- .toc.headings }}walks each top-level heading; each entry haslevel,contents,id, and a nestedsubof the same shape.
Inside markdown content, the bracket-equals preprocessor expands
[= name args =] into the template at shortcodes/name.html. A self-closing
shortcode is [= name args / =]; a paired one wraps content between
[= name =] and [= /name =]. Inside the template, positional args are
exposed as args[i] and named args as key.
build.sbt cross-builds for JVM, Scala.js, and Scala Native. JVM is the
primary target right now; Scala.js and Scala Native compile from the same
sources but are less battle-tested. The serve command is intentionally
JVM-only — a cross-platform server library is on the roadmap (the existing
microserve is currently JVM-only).
sbt juicerJVM/test
sbt juicerJS/test
sbt juicerNative/testThe integration test suite (JuicerBuildSpec) builds small sites under a
temp directory and asserts the rendered HTML structure end-to-end.
