Zine

Documentation

Warning

Zine is currently alpha software.

Many features are missing and currently Zine is only capable of building relatively simple websites.

All the common features (like i18n, an asset system, etc) are planned, but will take some time to be implemented.

Using Zine today means participating in its development process.

Other Pages

Requirements

Zine is a collection of tools orchestrated by the Zig build system.

Zine only depends on the Zig compiler, see the official website for more information on how to download and install Zig.

Project Setup

Your website only needs the following two files to get started:

build.zig.zon

.{
    .name = "sample website",
    .version = "0.0.0",
    .dependencies = .{
        .zine = .{
            .url = "git+https://github.com/kristoff-it/zine#e33a1d79b09e8532db60347a7ec4bd3413888977",
            .hash = "12209f9be74fcc805c0f086e4a81ccca041354448f5b3592e04b6a6d1b4a95da5a26",
        },
    },
    .paths = .{"."},
}

build.zig

const std = @import("std");
const zine = @import("zine");

pub fn build(b: *std.Build) !void {
    try zine.addWebsite(b, .{
        .title = "Sample Website",
        .host_url = "https://sample.com",
        .layouts_dir_path = "layouts",
        .content_dir_path = "content",
        .static_dir_path = "static",
    });
}  

Once you create the 3 directories mentioned in your build.zig file (layouts, content, static, but they can also be named however you like), you are ready to start working on your website.

Content

The content directory contains your markdown files and their structure will be reflected verbatim in the final site.

Note that SSGs rely heavily on the implication that webservers will automatically serve index.html when a directory (usually indicated by a final / in the URL) is requested. Most static servers will automatically redirect in the absence of a final slash (the Zine dev server does too), but by making sure to include the final slash in your <a> elements you will spare your clients one redirect round trip.

In the future Zine might make links with a missing final / a build error.

Sections

Consider the following content structure:

content
├── about
│   └── contacts.md
├── about.md
├── blog
│   ├── a.md
│   ├── b.md
│   └── index.md
└── index.md

In Zine every index.md file denotes a section.

In this example there are 2 sections:

The first one is the "root" section, defined by content/index.md, containing:

The second section is the "blog" section, defined by content/blog/index.md, containing:

Note how an index.md file defines a section but as a page it is not included in the list of pages that belong to that section.

Lastly, note how pages in a section don't all share the same basepath. In the absence of a nested index.md, all pages are considered siblings regardless of how they are nested inside of different directories.

Frontmatter

Each markdown file has a Ziggy frontmatter delimited by ---.

Ziggy is a data serialization language developed alongside Zine. You can learn more on the official Ziggy website: https://ziggy-lang.io.

It is highly recommended to setup your editor with Ziggy support and to download the Ziggy CLI tool. Unfortunately this won't grant you syntax highlighting for the frontmatter section of your content files (although it will inside of code blocks).

Zine will eventually have its own LSP and related tooling for a fully smooth development experience.

index.md

---
.title = "Homepage",
.date = @date("2020-07-06T00:00:00"),
.author = "Sample Author",
.draft = false,
.layout = "homepage.html",
.tags = [],
--- 
Your **markdown** content goes here.

Some fields, like title, and layout are mandatory, while others have default values. See the Scripty reference relative to Page for more information.

A very important required field is layout. This field must point at the layout that you want to use to style your content. In Zine this field must always be explicitly filled out and there is no implicit convention system (like in Hugo, for example).

Layouts

The layouts directory contains the html layouts that will be applied to your content. We're going see how layouts work later in this document.

Static

The contents of the static directory will be copied verbatim into the output directory.

CLI

$ zig build

Builds your website and places it in zig-out. Pass -p some/path/ to change the output directory.

Use zig build --help for more information about flags supported by zig build.

$ zig build serve

Builds your website and starts the development server. Making changes to any of your input directories (ie content, layouts, static) will automatically trigger a rebuild and a page reload.

Pass -Dport=8080 to set the listening port to 8080.

Layouting

In Zine layouts live under the layouts/ directory.

Here's a basic example where we create the homepage of our sample website.

content/index.md

---
.title = "Home",
.date = @date("2020-07-06T00:00:00"),
.author = "Sample Author",
.draft = false,
.layout = "homepage.html",
.tags = [],
--- 
Hello World!

layouts/homepage.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title var="$site.title"></title>
  </head>
  <body>
    <h1 var="$page.title"></h1>
    <div var="$page.content"></div>
  </body>
</html>

zig-out/index.html (output)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Sample Site</title>
  </head>
  <body>
    <h1>Home</h1>
    <div><p>Hello World!</p></div>
  </body>
</html>

In this example we just saw:

If you run the dev server now (zig build serve -Dport=8080), you should see the output page at https://localhost:8080/.

Adding more pages

Let's imagine now that we want to add a blog section to our website with a first post in it.

shell

$ mkdir content/blog
$ touch content/blog/first-post.md
$ touch layouts/post.html

content/blog/first-post.md

---
.title = "First Post!",
.date = @date("2020-07-06T00:00:00"),
.author = "Sample Author",
.draft = false,
.layout = "post.html",
.tags = [],
--- 
This is my first post!

layouts/post.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title var="$site.title"></title>
  </head>
  <body>
    <h1>Blog</h1>
    <h2 var="$page.title"></h2>
    <h3>by <span var="$page.author"></span></h3>
    <h4>Posted on: <span var="$page.date.format('January 02, 2006')"></span></h4>
    <div var="$page.content"></div>
  </body>
</html>

At this point you should be able to navigate to http://localhost:8080/blog/first-post/ and see the result.

Note how we now need a new layout (post.html) since the blog post is not going to have the same structure at the homepage.

While the structure is indeed different, both post.html and homepage.html share a lot of common boilerplate that would be nice not to have to maintain separately.

What we need now is a way to have both layouts put the common boilerplate into a singular template that then gets extended by each layout to produce two final different layouts.

Extending templates

First, some terminology:

Let's continue the example from the previous section where we try to collect all common boilerplate from homepage.html and page.html.

layouts/templates/base.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title id="title"><super/></title>
  </head>
  <body id="main">
    <super/>
  </body>
</html>

layouts/homepage.html

<extend template="base.html"/>

<title id="title" var="$site.title"></title>

<body id="main">
  <h1 var="$page.title"></h1>
  <div var="$page.content"></div>
</body>

layouts/post.html

<extend template="base.html"/>

<title id="title" var="$site.title"></title>

<body id="main">
  <h1>Blog</h1>
  <h2 var="$page.title"></h2>
  <h3>by <span var="$page.author"></span></h3>
  <h4>Posted on: <span var="$page.date.format('January 02, 2006')"></span></h4>
  <div var="$page.content"></div>
</body>

Let's analyze what we just saw:

<extend/>

When a layout wants to extend a template, it must declare at the very top the template name using the extend tag, like so:

<extend template="foo.html"/>

A layout that extends a template won't have a normal HTML structure, but rather it will be a list of HTML elements that will replace a correspoding super tag from the base template they're extending.

<super/>

The super tag defines an extension point in a template. The direct parent of a super tag must have an id attribute.

Each top level element in a layout must correspond to a super tag in the template being extended.

Since super tags don't have any id of their own, the layout uses the tag and id of the parent element of each super tag to match its content with the correct super tag.

template

<title id="title"><super/> - Sample Site</title>

layout

<title id="title">Home</title>

evaluates to

<title id="title">Home - Sample Site</title>

Why not just give ids to super tags?

Matching via parent elements is admittedly an uncommon choice, but it has some very real upsides over the alternatives.

Let's define a block in a curly brace templating language (mustache,jinja, hugo, etc):

hugo

{{ define "main" }}
  <p> Hello World </p>
{{ end }}

This is the equivalent of a top-level element in a Zine layout that extends a template, but it has a critical disadvantage: you know nothing about where the content will be put.

Unless you have perfect recollection of what "main" is, you won't know if your content should be framed in a container element or not. In fact, all the following declarations in a hugo base template are possible:

ok

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    {{ block "main" . }}{{ end }}
  </body>
</html>

oops, needed a <body> wrapper

<!DOCTYPE html>
<html>
    <head></head>
    {{ block "main" . }}{{ end }}
</html>

oops, too many wrappers

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <p>{{ block "main" . }}{{ end }}</p>
  </body>
</html>

Contrast this with our previous example:

layouts/homepage.html

<extend template="base.html"/>

<title id="title" var="$site.title"></title>

<body id="main">
  <h1 var="$page.title"></h1>
  <div var="$page.content"></div>
</body>

By looking at the layout we know that we are putting content directly into the body element of the template we are extending.

In fact if we were to make a mistake and define "main" as a div element in our layout, we would get a compile error:

shell

$ zig build

---------- MISMATCHED BLOCK TAG ----------
The super template defines a block that has the wrong tag.
Both tags and ids must match in order to avoid confusion
about where the block contents are going to be placed in
the extended template.

note: super template block tag:
(post.html) layouts/post.html:19:2:
    <div id="main">
     ^^^

note: extended template block defined here:
(base.html) layouts/templates/base.html:16:4:
    <body id="main">
     ^^^^
trace:
    layout `post.html`,
    content `about.md`.

So, if we were to give ids to super tags, we would have to introduce another fake html tag and we would end up with the same problem that curly brace templating languages have today.

where would this go?

<content id="main">
  <p> Hello World </p>
</content>

Repeating the parent element is a form of typing for your templates, in a sense.

Extension chains

In the previous section we saw how a layout can extend a template. We are now going to see how templates can extend other templates.

Let's update our terminology one last time. Since both layouts and templates can extend other templates, we need a generic way of referring to the template that is extending and the one being extended:

A template can take on both roles at the same time when in the middle of an extension chain.

layouts/templates/base.html

<!DOCTYPE html>
<html>
  <head id="head">
    <meta charset="UTF-8">
    <title id="title"><super/> - My Blog</title>
    <super/>
  </head>
  <body id="body">
    <super/>
  </body>
</html>

layouts/templates/with-menu.html

<extend template="base.html"/>

<title id="title"><super/></title>

<head id="head">
  <script>console.log("Hello World!");</script>
</head>

<!-- at the top level only comments 
     and block definitions are allowed -->
<body id="body">
    <nav>
        <a>Home</a>
        <a>About</a>
    </nav>
    <div id="content">
        <super/>
    </div>
</body>

layouts/page.html

<extend template="with-menu.html"/>

<title id="title" var="$page.title"></title>

<div id="content" var="$page.content"></div>

Note how with-menu.html is both fulfilling the interface (ie the extension points) of base.html and at the same time it's creating new ones for another super template to fulfill in turn.

In this last example page.html is featuring Scripty, the scripting language used to refer to your content from templates.

Logic

Template logic is based on two main variables: $site and $page, representing the global site configuration and the current page respectively.

Example markdown file:

content/foo/index.md

---
.title = "My Post",
.date = @date("2020-07-06T00:00:00"),
.author = "Sample Author",
.draft = false,
.layout = "post.html",
.tags = ["tag1", "tag2", "tag3"],
--- 
The content

var

Prints the contents of a Scripty variable.

layouts/post.html

<span var="$page.title"></span>  

output

<span>My Post</span>

if

Toggles the body of an element based on the condition.

layouts/post.html

<div if="$page.title.len().eq(1)" id="foo">
    <b>Won't be rendered</b>
</div>  

output

<div id="foo"></div>

loop

Repeats the body of an element based on the condition. Inside an element with a loop attribute, $loop becomes available.

layouts/post.html

<ul loop="$page.tags" id="tags">
   <li var="$loop.it"></li>
</ul>  

output

<ul id="tags">
   <li>tag1</li>
   <li>tag2</li>
   <li>tag3</li>
</ul>  

inline-loop

Repeats the entire element based on the condition. Inside an element with a loop attribute, $loop becomes available.

layouts/post.html

<div inline-loop="$page.tags" var="$loop.it"></div>

output

<div>tag1</div>
<div>tag2</div>
<div>tag3</div>

Scripty Reference

To learn about all the types present in Scripty and their builtin operations, see the full reference.