Still - A composable static site builder

Hello! Version 0.6.1 is out which improves runtime memory and performance. We were keeping too much stuff in memory and, in some situations, the dev server could reach 500mb for a small website. That’s now fixed and the dev server is now leaner and faster because of it. In the process, we also changed it so files are served from memory instead of disk. Each file is compiled when it’s asked for, so we already have it in memory. Instead of saving to disk to serve immediately after, we just serve the file and don’t write the disk. It doesn’t look like much, but it’s definitely improving the server’s performance. Next up will be importmaps!

Any feedback or bug reports are welcome!


It looks like Still is using a markdown converter based on the NIF’s hoedown. The compilation prints some scary looking warning messages:

/usr/bin/make -C src/hoedown libhoedown.a
make[1]: Entering directory '/home/derek/projects/mysite/deps/markdown/src/hoedown'
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/autolink.o src/autolink.c
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/buffer.o src/buffer.c
src/buffer.c: In function ‘hoedown_buffer_printf’:
src/buffer.c:249:6: warning: implicit declaration of function ‘vsnprintf’; did you mean ‘vsprintf’? [-Wimplicit-function-declaration]
  249 |  n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
      |      ^~~~~~~~~
      |      vsprintf
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/document.o src/document.c
src/document.c: In function ‘char_link’:
src/document.c:1194:5: warning: this ‘else’ clause does not guard... [-Wmisleading-indentation]
 1194 |     else nb_p--; i++;
      |     ^~~~
src/document.c:1194:18: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘else’
 1194 |     else nb_p--; i++;
      |                  ^
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/escape.o src/escape.c
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/html.o src/html.c
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -Wno-static-in-inline -c -o src/html_blocks.o src/html_blocks.c
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/html_smartypants.o src/html_smartypants.c
src/html_smartypants.c: In function ‘smartypants_quotes’:
src/html_smartypants.c:102:2: warning: implicit declaration of function ‘snprintf’ [-Wimplicit-function-declaration]
  102 |  snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote);
      |  ^~~~~~~~
src/html_smartypants.c:7:1: note: ‘snprintf’ is defined in header ‘<stdio.h>’; did you forget to ‘#include <stdio.h>’?
    6 | #include <ctype.h>
  +++ |+#include <stdio.h>
    7 | 
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/stack.o src/stack.c
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -Isrc -fPIC -c -o src/version.o src/version.c
ar rcs libhoedown.a src/autolink.o src/buffer.o src/document.o src/escape.o src/html.o src/html_blocks.o src/html_smartypants.o src/stack.o src/version.o
make[1]: Leaving directory '/home/derek/projects/mysite/deps/markdown/src/hoedown'
cc -g -O3 -ansi -pedantic -Wall -Wextra -Wno-unused-parameter -I/usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include -Isrc/hoedown/src -fPIC -shared  -o priv/ src/markdown.c src/hoedown/libhoedown.a
In file included from /usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include/erl_nif.h:31,
                 from src/markdown.c:5:
/usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include/erl_drv_nif.h:118:23: warning: ISO C90 does not support ‘long long’ [-Wlong-long]
  118 | typedef unsigned long long ErlNapiUInt64;
      |                       ^~~~
/usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include/erl_drv_nif.h:119:21: warning: ISO C90 does not support ‘long long’ [-Wlong-long]
  119 | typedef signed long long ErlNapiSInt64;
      |                     ^~~~
src/markdown.c:241:5: warning: missing initializer for field ‘flags’ of ‘ErlNifFunc’ {aka ‘struct enif_func_t’} [-Wmissing-field-initializers]
  241 |     { "to_html", 2, to_html },
      |     ^
In file included from src/markdown.c:5:
/usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include/erl_nif.h:124:14: note: ‘flags’ declared here
  124 |     unsigned flags;
      |              ^~~~~
src/markdown.c:242:5: warning: missing initializer for field ‘flags’ of ‘ErlNifFunc’ {aka ‘struct enif_func_t’} [-Wmissing-field-initializers]
  242 |     { "set_nif_threshold", 1, set_nif_threshold }
      |     ^
In file included from src/markdown.c:5:
/usr/local/stow/otp-24/lib/erlang/erts-12.0.4/include/erl_nif.h:124:14: note: ‘flags’ declared here
  124 |     unsigned flags;
      |              ^~~~~

I am just curious, what was the reason for using this instead of Earmark, Speed? Missing functionality in Earmark?

I know there was a reason but I really don’t remember. I only remembered we tried a few libraries, including one from Rust, but that’s it. I guess it’s time for me to revisit that! I see there are some updates in earmark

By the way, the still_snowpack readme said:

This is no longer the recommended way to use Snowpack. See the docs for more information.

However, I cannot find any information on how to use snowpack in the docs of Still.

I have not documented it yet and I really need to make another release since there’s a ton of new stuff :s I think the best approach right now is to simply run a watcher like you do with phoenix if I’m not mistaken

1 Like

A general watcher shall be enough. I like snowpack myself but I don’t want to be tied to it.

Since this topic is having some activity, I’ll also take this opportunity to share some thoughts: I’m starting to wonder if this project makes any sense. It seems like I’m just building something that you can already do using Phoenix and curl. We could even combine Phoenix and a well-configured CDN to cache/invalidate responses and have a mix of static and dynamic pages. On top of that, there are plenty of site generators out there that can do some things that are impossible for Still without Javascript, like server-rendered syntax highlight for code. What do you think?

We could even combine Phoenix and a well-configured CDN to cache/invalidate responses and have a mix of static and dynamic pages.

The point of a static site generator is to have a 100% static site. It costs zero to host and need no maintenance. Once you start mixing, you might as well go full dynamic.

But if you just do Phoenix + curl? You already get a dev server with errors, asset pipeline, etc. And when you’re done, you run curl and get a production build. Wouldn’t it be the same?

Yes, It would be the same, but …

The value of a static site generator, to me is:

  • It knows how to build all pages. Some pages may not be discoverable by curl, only when javascript has been evaluated, or even only when the user interacted with the page (it should not but it is common practice)
  • It has a lot lot of helpers in templates
  • It makes internal linking easy across markdown files.
  • It can call a JS build for a specific page (for a dev blog, it’s cool to have a page with its own webpack/rollup build and a bunch of JS modules only for that page)
1 Like

On top of what @lud has mentioned, it would be pretty bad for developer ergonomics. It might work though, if you can hide the crawling step in a robust and integrated flow.

Hello! It has been a while, so I thought I would write some updates:

  • Handling static assets is faster because we now only copy them to the build folder when they are modified. Ideally, we would serve them from the input folder and not copy them, but this is the first step!
  • There’s now support for a “data” folder. Essentially, data files in the “data” folder are easily accessible from any template. You can use this to load JSON, YAML, etc, to use on your pages.
  • There’s also support for pagination, which allows generating multiple files from a single template. This is the most powerful feature I added and allows for things like per-category pages in blogs. Any List can be used to create multiple pages.

There were a few more internal changes, but progress is slow. Still, I think this is pretty neat!


This looks interesting! Wonder if you’ve looked at solid | Hex for another templating option?

Hey! I haven’t, but it seems pretty simple to add. Although, from a user perspective, I don’t think it’s that different from just using EEx, right?