Publish a website with Emacs
A novice guide to org-mode
Basic principle
Customizing and publishing a static website with Emacs and org-mode is quite easy. Like with almost every static website generator you have rules to convert a specific set of files from one format into another. The two most important converters for website publishing in emacs are
org-html-publish-to-htmlconverts org files to html filesorg-publish-attachmentcopies files from input to output location (identity function, no conversion happens)
As depicted in the following images, org-publish-attachment is used for static
content like stylesheets and images, while org-html-publish-to-html
is used to convert .org files to .html files.
To specify the set of files on which a converter acts, one specifies an
element in the org-publish-project-alist list:
(setq org-publish-project-alist '(("orgfiles" :base-directory "." :base-extension "org" :publishing-directory "./build/html" :recursive t :publishing-function org-html-publish-to-html) ("images" :base-directory "img" :base-extension "png" :publishing-directory "./build/html/img" :publishing-function org-publish-attachment) ("website" :components ("orgfiles" "images"))))
In this listing a converter (a project in org-mode) named orgfiles
is defined for recursively converting all .org files into the folder
./public and a converter named images which copies all files with
the extension .png in the folder ./img into the folder
./public/img. It is this simple!
This configuration can be put into a file publish-html.sh and called
from a Makefile as
./publish-html.sh
this involves some emacs script trickery which is explained here in all it’s detail.
Using GNU Guix
By employing GNU Guix to build the website we can solve several issues:
- document (and collect) all necessary build tools
- reproduce the build in the future, on another machine or architecture
- build in a containerized environment
To achieve this, a manifest file and a channels file is
sufficient. Our manifest file manifest.scm contains the
following
(specifications->manifest '("emacs" "font-linuxlibertine" "font-fira-code"))
which is Emacs for compiling the source and a regular and a monospace font for serving in the website.
The channels file channels.scm is even easier, it serves pinning the
software defined in the manifest file and can be updated with
guix describe --format=channels > channels.scm
Now for building the website, a containerized environment can be
started with guix shell (inside the directory where manifest.scm
resides) and subsequently the build command in listing 1 can be called.
Customizing the appearance
Naturally you want to tweak the appearance of your website to your desired style. There are several ways to achieve this
For most use cases 1 is sufficient, but if you want more
control over the HTML elements you have to use 2. We want go
into detail for 2 (look up
org-export-define-derived-backend) and will focus on 1
instead.
Stylesheet
To add a custom stylesheet there are the following options
org-html-head-extra- append to the head element
org-html-head-include-default-style- include default style
org-html-head- set the head element
and in action they look like
(defvar html-head-extra (concat (sxml-to-xml `(link (@ (href "/css/reset.css") (rel "stylesheet")))) (sxml-to-xml `(link (@ (href "/css/fonts.css") (rel "stylesheet")))) (sxml-to-xml `(link (@ (href "/css/site.css") (rel "stylesheet")))) (sxml-to-xml `(link (@ (href "/css/code.css") (rel "stylesheet")))))) (setq org-html-include-default-style nil org-html-head-extra html-head-extra)
Layout
There are several options to adapt the HTML layout. According to the manual you can
org-html-container-element- change the element tag inside the content container
org-html-divs- set the element tag for pre-, postamble and content
org-html-doctype- change the doctype format
org-html-html5-fancy- use HTML5 tag elements in certain places
org-html-postamble-format- set the content of the postamble element
org-html-preamble-format- set the content of the preamble element
org-html-viewport- change the viewport meta tag in head
to add a custom site header set the preamble to
(defvar html-preamble-format `(("en" ,(concat (sxml-to-xml `(h1 (a (@ (href "/index.html")) "Reza Housseini"))) (sxml-to-xml `(nav (ul (li (a (@ (href "/about.html")) "About")) (li (a (@ (href "/index.html")) "Blog")) (li (a (@ (href "/projects.html")) "Projects"))))))))) (setq org-html-preamble t org-html-preamble-format html-preamble-format)
and for a custom footer, set the postamble to
(defvar html-postamble-format `(("en" ,(concat (sxml-to-xml `(p (@ (class "copyright")) "© 2022 %a" (a (@ (class "cc-button") (href "https://creativecommons.org/licenses/by-sa/4.0/")) (img (@ (src"/img/button-creative-commons-by-sa-4.0-80x15.png")))))) (sxml-to-xml `(p "The text and images on this site are free culture works " "available under the " (a (@ (href "https://creativecommons.org/licenses/by-sa/4.0/")) "Creative Commons Attribution Share-Alike 4.0 International") " license")) (sxml-to-xml `(p "Made with %c, source code is available on " (a (@ (href "https://git.sr.ht/~rhou/blog")) "sourcehut") " and was inspired by " (a (@ (href "https://dthompson.us")) "David Thompson's") " website.")) (sxml-to-xml `(p "Created at %d, last modified at %C.")))))) (setq org-html-postamble t org-html-postamble-format html-postamble-format)
The result can be inspected on this site.
Adding a RSS feed
A feed is a XML document with a list-like structure which in case of a blog has an entry for each blog post. The content of this entry can be choosen arbitrarely but in most cases it contains part or the whole blog post.
ox-rss.el
By using ox-rss.el we can stick to the same procedure as described
in the first section:
("rss" :base-directory "./posts" :base-extension "org" :html-link-home "https://reza.housseini.me/" :exclude ".*" ;; To exclude all files... :include ("sitemap.org") ;; ... except sitemap.org. :html-link-use-abs-url t :rss-extension "xml" :publishing-directory "./build/html" :publishing-function (org-rss-publish-to-rss) :section-numbers nil :table-of-contents nil)
we just add an entry to our org-publish-project-alist variable how
to generate our RSS feed.
Acknowledge
- The inspiration for the styling came mostly from Dave Thompson’s website
- Thomas Ingram’s blog introduced me to org-mode sitemap’s
- Basti did a good job explaining emacs script trickery
- A nice blog with an introduction to
ox-rss.el