geolessel / vox

The static site builder for Elixir lovers
115 stars 4 forks source link

Vox

The static site generator for Elixir lovers.

Quick start

There is a new site generator that is the quickest way to get started. In order to use it, run

mix archive.install hex vox_new
mix vox.new blog

This will generate a simple scaffolded site that you can customize and tweak as you see fit.

There is also an example blog built with Vox that attempts to take advantage of all the features of Vox as they are built. You can look at the source code for that (and clone it and build it yourself) at geolessel/vox-example.

vox.new options

Manual installation

The package can be installed by adding vox to your list of dependencies in mix.exs.

For now, it is recommended to install directly from GitHub. Note that the mix vox.new generator will generate the "~> 0.1.0" version instead.

def deps do
  [
    {:vox, github: "geolessel/vox"}
  ]
end

Usage

Configure vox in your app (config/config.exs):

import Config

config :vox, src_dir: "site", output_dir: "_html"

Then start writing! Any file with a .eex extension will be compiled and your directory structure beneath your src_dir will be retained. For example, if I have a file named site/blog/posts/2023/hello-world.html.eex then that file will be compiled into _html/blog/posts/2023/hello-world.html.

To build your site, run mix vox.build.

To start a dev server that rebuilds the site on filesystem changes, run mix vox.dev. Note that for now, you will have to manually reload the page you are on to see the changes. Automatic reloading is on the wishlist.

Root template

You must have a src_dir/_root.html.eex file defined. This file contains code that will be in every rendered html file.

Typically, this will contain your initial html, head, and body tags. Render your further child template with

<%= @inner_content %>

Template files

For now, you must have a src_dir/_template.html.eex file defined. The compiler will use that file as the default template. There are two other ways you can set the template for a file.

  1. Bind template in the file's bindings. This is a path relative to the file it is bound in.

    <%
     title = "Special post"
     template = "_special_template.html.eex"
    %>
  2. Change the default of an entire subdirectory by creating a new _template.html.eex in it. When a file is compiled, it will automatically look in its current directory for a _template.html.eex file and us that if it exists. If it does not exist, it will search in the parent directory... and so on until it reaches src_dir/_template.html.eex

Use <%= inner_content %> inside this file to render the content of other files.

Passthrough files

There are some files that need to be included in a site's build but should not be processed (like images, fonts, and other assets). Any file with an extension other than .eex or .html will not be processed. Instead, they will be copied over in the same directory structure and file name as it is in the source directory.

For example, if there was an image located on your filesystem at src_dir/images/logo.png, it will be available in the generated site structure are dest_dir/images/logo.png.

File metadata (also known as front matter or bindings)

At the top of your .eex files, set up the file's front matter inside of <% ... %> blocks. If you want to set metadata for the file, it needs to be at the top of the .eex file.

For now, the syntax is finicky and needs to be precise.

  1. The first line should be a lone <%
  2. Your metadata or bindings will be next. Feel free to use any Elixir data types.
  3. The last line of your metadata declarations should be a lone %>

With these set, you can access them outside (or inside) of this file through map syntax. Take the following file as an example:

<%
  title = "Vox helped me build this"
  author = %{
    name: "Geoffrey Lessel",
    site: "https://builditwithphoenix.com"
  }
  collections = :posts
%>

If you were to iterate through your for post <- @posts do ... collection, you can now access this information like post.author.name.

Collections

One special metadata (binding) you can use is collection. This will put the file in a special collection you can reference later in your templates or other .eex files.

There will be an assign for every collection value you bind in your .eex files. For example, consider the following two files. First is a blog post specifying it belongs in the :posts and :elixir collections.

<%
  title = "Elixir is cool"
  date = ~D[2023-05-05]
  collections = [:posts, :elixir]
%>

<h1>Elixir is my favorite language</h1>

... lots of must-read content ...

Second is an index page listing out all the blog posts and links to their pages. The @posts and @elixir are already bound and ready to use because they were indicated in one or more collections.

<h1>Here are all my blog posts</h1>

<ul>
  <%= for post <- @posts do %>
    <li>
      <a href="https://github.com/geolessel/vox/blob/main/<%= post.destination_path %>"><%= post.title %></a>
    </li>
  <% end %>
</ul>

Partials

If you have a bit of code or markup that is reused often in your site, it might be beneficial to put it into a partial.

To render the contents of a partial file, use the Vox.partial/1 function passing the full path of the file from your source directory. If your partial needs access to variables your current page has access to, pass them in as the optional second argument. To pass on all the assigns that your current page has access to, simply pass assigns as the second argument.

For example, if you specified your source directory as my-blog and you had a partial file located on your filesystem at my-blog/partials/copyright.html, you would insert it into an EEx file like...

<%= Vox.partial("/partials/copyright.html") %>

To let your partial have access to all currently known assigns:

<%= Vox.partial("/partials/header.html.eex", assigns %>

Note that it is important and required to pass the initial slash / in the path.