architecture
background
"flower core" (the flower binary itself) is intentionally minimal. as much work as possible is pushed out to the runtime interpreted clojure.
flower core
Flower core has the following model:
- On
flower new:- if
flower.edndoesn't exist yet, create it. - run
flower configure
- if
- On
flower configure:- if
flower.edndoesn't exist yet, error. - populate a
.build/defaultsdirectory with all built-in default files. see overlay for details. - parse global configuration from
flower.edn - parse per-page configuration from
pages/* - run
build.clj, passing the configuration inflower.reflect/*metadata*.build.cljis expected to callflower.reflect/write-ninja!, and the output sent there is used directly asbuild.ninja; there is no post-processing. finally, record the dependencies used bybuild.cljin adepfile.build.cljis expected to add a dependency edge between the depfile and build.ninja itself.
- if
- On
flower build:- run
flower configure - run
ninja
- run
- On
flower watch:- run
flower configure. exit if it errors. - run
ninja. do not exit if it errors. - start a web server on the out directory (usually
public). - start a live-reload server using web sockets (WSS) on port 35729.
- start a file watcher on the out directory.
- on changes to the out directory, send a live-reload message over the WSS server.
- start a file watcher on the site directory (usually the same as the current directory)
- on changes anywhere in the site, rerun
ninja
- on changes anywhere in the site, rerun
- run
There are a few other non-interactive ("plumbing") commands, described in the section on default-build.clj below.
Note some interesting properties of this model:
- changes to the out directory do not have to come from flower itself. any change to
publicwill cause a live-reload message to be sent. - there is a process boundary between
flowerandninja. most builds of any site occur in aflowersubprocess, not in the flower watcher. this allows modifying flower and see the results live, without having to kill and restart the watcher. the downside is that all data must be serialized to the filesystem, either throughbuild.ninjaor through a temporary file in.build. - all builds happen in parallel, automatically. rebuild detection happens automatically (based on file timestamps).
default-build.clj
The default build plan looks something like this per-page:
- Run
flower split-frontmatter pages/page.md, which generates a structured JSON file in.build. This JSON contains both the frontmatter and the content itself. - Once all frontmatter has been split, run
flower join-frontmatterto combine them in one giant file. This contains only frontmatter, no content. Having a cache like this allows an "early stop" condition if only the page's content has changed, not its frontmatter. - If necessary, rerun
build.cljwith the new frontmatter. - For each file, run
flower transformon all transformers. Save the output topublic.
transformers
Flower's main two interfaces are build.clj (through flower configure) and flower transform. Transformers are clojure files which receive clojure data as input and emit clojure data as output. Within a transformer, you are allowed to do basically anything allowed by the sandbox.
The initial transformer is passed a {:content :string, :frontmatter {}} map. Past that, transformers are allowed to change their inputs and outputs as they please.
The default transformers run in the following order:
preprocess: Expand the template language (see sunflower).markdown: Render markdown files to HTML. Flower tracks the current filetype using:flower/filetype. The initial value is determined by the source file name.embed: Embed the page inside of a template. The default template istemplates/default.html. Usetemplate: nullto explicitly say that no template should be used.- "all user-defined transformers" (order unspecified, configurable with a custom
expressions/transform-sorter.clj) content: Extract thecontentfield from the Clojure map and save it to disk as the final page.