Elmstatic allows you to write the HTML layouts and the styles for the pages of your site in Elm. It’s fairly unopinionated: you can generate whatever HTML you want, however you want — all you need to do is define a suitable main
function in each layout.
A summary of current features:
elm-ui
, html
or any other package that generates Html msg
values)elm-css
stylesheets in Elm code (but you can use plain old stylesheets if you like)/postgres
and /elm
on this site)browser-sync
).Elmstatic requires Elm 0.19 and uses the elm
executable.
The basic workflow is like this:
$ npm install -g elmstatic
$ mkdir mysite
$ cd mysite
$ elmstatic init
$ elmstatic init --elm-markup
$ elmstatic build
$ cd _site
$ http-server
Live reload can be achieved by using Elmstatic watch mode together with browser-sync
(or another HTTP server that supports live reload):
browser-sync
:$ npm install -g browser-sync
$ elmstatic watch
browser-sync
in the output directory (<site dir>/_site
by default): $ cd _site
$ browser-sync start --server --files "." --no-ui --reload-delay 500 --reload-debounce 500
This is the file structure of the Markdown scaffold:
.
├── _layouts
│ ├── Elmstatic.elm
│ ├── Page.elm
│ ├── Post.elm
│ ├── Posts.elm
│ ├── Styles.elm
│ └── Tag.elm
├── _pages
│ ├── about.md
│ └── contact.md
├── _posts
│ ├── 2019-01-01-using-elmstatic.md
│ ├── 2019-01-02-another-post.md
│ └── index.md
├── _resources
│ ├── img
│ │ └── logo.png
│ └── styles.css
├── config.json
└── elm.json
If you’ve used Jekyll (a Ruby-based static site generator), you will find this familiar. If you choose to go with elm-markup
content format, you’ll have .emu
files instead of .md
, and the frontmatter will be a |> Metadata
block in elm-markup format
. Read the announcement of elm-markup
support to find out how to work with elm-markup
.
A site consists of pages linked in any way you like, and posts. The content of pages and posts is written in Markdown or elm-markup, while the HTML layout of all pages and posts is described in Elm.
You can specify the layout of a given page or post in the frontmatter. The frontmatter section also allows you to set other metadata, like title and tags.
The site can also include other files which are stored in _resources
.
_layouts
contains all of the Elm code. There is a module for each separate page layout, a module for styles, and a utility module called Elmstatic
.
Files in _pages
store the Markdown or elm-markup content of each page, while files in _posts
store the content of posts.
_pages
, _posts
and _resources
are optional — you can have a site without any or all of these (but to get rid of _posts
, you will need to change the default config).
config.json
contains the site-specific parameters which Elmstatic inserts into appropriate places. The default config looks like this:
{
"siteTitle": "Author's fancy blog",
"outputDir": "_site",
"copy": {
"/posts": "/"
},
"feed": {
"title": "Author's fancy blog",
"description": "Author's blog posts",
"id": "https://example.com",
"link": "https://example.com/posts",
"image": "https://example.com/img/logo.png",
"copyright": "Copyright: Author",
"language": "en",
"generator": "Elmstatic",
"author": {
"name": "Author",
"link": "https://example.com/about"
}
},
"tags": ["other", "software"]
}
siteTitle
goes into the <title>
tag in HTML.
outputDir
is a relative path from the site’s directory.
copy
is used to give aliases to pages. The scaffold specifies that /posts
will also be available as the root level index page.
This aliasing is done as a postprocessing step, by copying files.
The object under the feed
key specifies parameters for the RSS feed.
The tags
key supplies an array of valid tags for posts. If a post has a tag that’s not in this list, elmstatic
will generate an error. This key is optional. If it’s absent, then no validation is performed on post tags.
Additionally, you can add the optional elm
key to provide a path to the elm
executable relative to the site’s directory. It’s useful if you don’t have elm
available in your PATH. For example, you can install Elm 0.19 locally into your site directory with npm install elm
, which will place elm
into node_modules/.bin/elm
. Then you can point elmstatic
to it by adding the elm
key to config.json
:
"elm": "node_modules/.bin/elm"
elm.json
is a standard issue Elm project file. If you need additional Elm packages in your code, add them here.
This file provides the functions to generate the page layouts. The key function is layout
which is used to define the main
function of each layout module.
The default HTML template generated by this module looks like this:
<!doctype html>
<html>
<head>
<title>${title}</title>
<meta charset="utf-8">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.1/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.1/languages/elm.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.1/styles/default.min.css">
<link href="//fonts.googleapis.com/css?family=Open+Sans|Proza+Libre|Inconsolata" rel="stylesheet" type="text/css">
</head>
<body>
<!-- Content goes here -->
</body>
</html>
Note that there’s nothing special about this file. You can modify it to suit your needs, for example if you need to generate different tags in <head>
, add custom styles, custom JavaScript, analytics or Google Fonts, for example.
This is a layout for pages. Each layout must export a main
function. In this module, it’s defined as:
main : Elmstatic.Layout
main =
Elmstatic.layout Elmstatic.decodePage <|
\content ->
Ok <| layout content.title [ markdown content.content ]
These are layouts for individual posts and lists of posts.
This module exports an elm-css
stylesheet, which is an Html msg
value. This is then included in other layouts.
These are files with page content. They use the Page
layout unless another layout is specified in the frontmatter (eg layout: MyCustomPage
).
Posts are handled very similarly to Jekyll.
The post files have a particular naming scheme: YYYY-MM-DD-kebab-case-post-url-slug.md
. Elmstatic extracts the date of the post and the URL for it from the file name.
Future-dated posts are considered to be draft posts, and excluded from the generated HTML by default. To include draft posts, run elmstatic draft
instead of just elmstatic
.
Additionally, each post file starts with a frontmatter section that looks like this in Markdown:
---
title: "Introducing Elmstatic"
tags: software elm
---
or like this in elm-markup:
|> Metadata
title = Introducing Elmstatic
tags = software elm
The frontmatter in Markdown files is in YAML format.
The title appears both at the top of the post as well as on post list pages.
A post can have one or more tags. For each tag which has associated posts, Elmstatic generates a tag page with a list of posts containing that tag.
The body of the post is written in Markdown or elm-markup format and appears after the frontmatter section.
The default layout used for post pages is Post
.
This file is provided to allow you to customise the title of the post list, and potentially to use a custom layout. By default, the Posts
layout is used.
This directory contains any additional files you’d like to copy over to the root of the site. So, for example, _resources/img/logo.png
becomes available as myblogdomain.com/img/logo.png
on the published site.
This structure is modelled on the output of Jekyll. For the scaffold described above, the output structure will look like this:
_site
├── 2019-01-01-using-elmstatic
│ └── index.html
├── 2019-01-02-another-post
│ └── index.html
├── about
│ └── index.html
├── contact
│ └── index.html
├── img
│ └── logo.png
├── index.html
├── posts
│ ├── 2019-01-01-using-elmstatic
│ │ └── index.html
│ ├── 2019-01-02-another-post
│ │ └── index.html
│ └── index.html
├── rss.xml
├── styles.css
└── tags
├── other
│ └── index.html
└── software
└── index.html
In addition to the root level pages like /contact
and /about
, this site (korban.net) has a couple of subsections: korban.net/postgres
and korban.net/elm
. These are defined via subdirectories of _pages
, and Elmstatic handles them in a particular way.
The structure of _pages
for this site looks like this:
_pages
├── about.md
├── consulting.md
├── contact.md
├── courses.md
├── elm
│ ├── about.md
│ ├── book.md
│ ├── contact.md
│ ├── elmstatic.md
│ └── uicards.md
├── postgres
│ ├── about.md
│ ├── book.md
│ ├── contact.md
│ └── pgdebug.md
└── projects.md
Correspondingly, under _posts
I have:
_posts
├── elm
│ ├── 2018-01-23-decoding-json-to-nested-record-fields-in-elm.md
│ ├── 2018-03-15-how-to-read-elm-types-like-html-msg.md
| └── ...
└── postgres
| ├── 2013-11-11-installing-postgis-with-homebrew.md
| ├── 2014-11-18-visualising-postgis-data-from-shell.md
| └── ...
├── 2009-12-06-add-case-insensitive-finders-by-extending-activerecord.md
├── 2010-03-04-delete-expired-activerecord-based-sessions-regularly.md
└── ...
Because I wanted to have different navigation in the subsections, I created additional layouts for these pages and posts in _layouts
: ElmPage.elm
, ElmPost.elm
, PostgresPage.elm
, PostgresPost.elm
and so on. These layouts are specified in the .md
files in subsections.
Most of the special handling is to do with posts. /posts/elm
only lists posts from the _posts/elm
directory, and /posts/postgres
only lists posts from the _posts/postgres
directory. However, /posts
aggregates all of the posts into a single list, meaning it lists posts from the root level as well as posts from each subsection.
Each section is treated as an additional tag for posts in that section, so Elmstatic also generates /tags/elm
and /tags/postgres
pages.
The easiest option is to use the docs/
folder:
elmstatic init
config.json
to set the output directory to docs
elmstatic build
and add all the files to the repositoryAfter this, the site should become available at https://<your github name>.github.io/<repo name>/
.
elmstatic init
_site
to .gitignore
- with this setup, you don’t need to commit the generated output npm i -g elm@latest-0.19.1 && npm i -g elmstatic && elmstatic build -v
_site
if you haven’t changed the output location in config.json
Now the site should be rebuilt every time you push source changes to GitHub.
.gitlab-ci.yml
file should look like this for deploying to GitLab:
image: node:current
pages:
cache:
paths:
- node_modules
- /root/.elm
- elm-stuff
script:
- npm install -g elm --unsafe-perm=true
- npm install -g elmstatic
- elmstatic
- mv _site public
artifacts:
paths:
- public
only:
- master
.gitignore
should exclude additionally exclude _site
in this scenario:
elm.js
elm-stuff/
_site/
There are a few sites that I’m aware of:
Do you have a public Elmstatic site? Please contact me and let me know so I can add it here!