Poops 2.0 has no new features. It is the release where the core loop — watch, build, reload — stops carrying old dependencies and old spellings, and where the compat shims that survived the whole 1.x line finally go.
Poops 2.0 has no new features. It is the release where the core loop — watch, build, reload — stops carrying old dependencies and old spellings, and where the compat shims that survived the whole 1.x line finally go.
Most projects need three edits to move, and two of them are optional until 3.0. Here is the whole list.
Node 20 reached end of life in April 2026. Poops now requires Node.js 22 or newer and is tested on 22 and 24, on Ubuntu and Windows.
Live reload no longer runs a second server on its own port. It rides the
serve port: Poops answers /__poops_reload as a server-sent events stream and
appends a small client script to each HTML page it serves — to the response,
so nothing lands in your build output.
Delete this from your layouts:
<script>
document.write(
'<script src="http://' +
(location.host || "localhost").split(":")[0] +
':35729/livereload.js?snipver=1"></' +
"script>",
);
</script>
A snippet you forget is harmless — it 404s quietly — but the livereload_port
template global it reads is gone, so it renders nothing anyway.
Config shrinks to a switch, and now needs serve alongside it:
{
"serve": { "base": "dist" },
"livereload": true
}
livereload.port, livereload.exclude, livereload.extraExts and
livereload.exts are removed, along with the --livereload-port / -l flag.
The last three had already stopped doing anything back in 1.5.1, when the reload
server stopped watching files.
Behaviour is unchanged: one reload per save once the build settles, and a
CSS-only build swaps stylesheets in place instead of reloading the page. That
hot-swap now actually fires when you build with minify — see Fixed below.
ssg to reactorThe last compatibility shim in the codebase. If your config still says ssg,
rename it; Poops tells you so on startup:
[info] Config key "ssg" is renamed to "reactor" in 2.0 — ignored.
options (deprecated, not yet required)markup now has the shape every other entry has — in, out, and everything
else in options:
{
"markup": {
"in": "src/markup",
"out": "dist",
"options": {
"engine": "nunjucks",
"site": { "title": "My Site" },
"dateFormat": "MMM D, YYYY",
"searchIndex": "search-index.json",
"nav": { "out": "nav.json" }
}
}
}
Three renames come with it:
| 1.x | 2.0 |
|---|---|
markup.<key> |
markup.options.<key> |
timeDateFormat |
dateFormat |
llms.output, nav.output, feed.output, … |
…out |
All three are still honoured in 2.x and warn once, naming the replacement. They stop working in 3.0. Nothing breaks silently: if you keep the old spelling, your search index and feeds keep being written.
Two conveniences ride along. serve.base now defaults to the markup out
directory, so most configs can drop it. And if you set target on your
scripts or reactor entries, nothing changes — but the default moved from
es2019 to es2020, because ?. and ?? are ES2020 and lowering them for
browsers that have supported them since 2020 only made bundles bigger.
If you maintain a markup engine, registerFilters({ timeDateFormat, markupOut })
is now registerFilters({ dateFormat, markupOut }). That is the only signature
change, and it is a hard rename rather than a deprecation — it lands before
the engine interface becomes public API in this same release.
markup.options.engine has always accepted any importable module, and
poops-shopify ships a production
engine against it. Nothing declared that interface stable, so a method rename in
a patch release could have broken it silently.
It is now documented in the engine API reference, semver applies to it from 2.0 on, and a contract test asserts both builtin engines still expose the shape — a failing test is a breaking change caught before it ships.
watch has always been a list of directories,
never globs.Symbol.for calls are annotated side-effect free, which makes minified output
slightly smaller.livereload package and its websocket stack are gone from the tree.minify on, the reload chain was told about site.css while the page linked
site.min.css — nothing matched, so every style edit reloaded the whole page.
Both spellings are reported now.filePath kept native separators while a regular page's was
posix, so on Windows a post's link came out as …/edit/main/src/posts\hello.md.dist-old for dist.