Building a website with Emacs

I would like to make a confession… I detest most websites I visit on a daily basis. I wanted to make a difference with my little personal corner of the internet. What do you know? Emacs can totally do that.

You see, Emacs packs an exceptional mode, called Org Mode, that comes with many wonderful functionalities, that which powers this website goes by org-publish-project-alist.

In short, you specify the location of a particular thing, and where it should go upon export, as a result, we have the utmost freedom to design our own file hierarchy - here's what I've come up with:

~/projects/grtcdr.github.io
├── org/
│   ├── blog/
│   ├── css/
│   ├── data/
│   ├── img/
│   ├── index.org
│   └── contact.org
│
└── html/      ------  This directory is generated by org-publish
    ├── blog/
    ├── css/
    ├── data/
    ├── img/
    ├── index.html
    └── contact.html

1. An example to get you started

I'd like to introduce you to the most basic form of a working org-publish-project-alist.

(require 'ox-html)

(setq org-publish-project-alist
      '(("example.com/org"
         :base-directory "~/website/org/"
         :base-extension "org"
         :publishing-directory "~/website/html/"
         :publishing-function org-html-publish-to-html)))

What happens when you create a .org document within your :base-directory – save it – hit M-x org-publish RET, is nothing short of magic!

Org, being the smarty-pants tool it is, takes the original file and publishes it to :publishing-directory in HTML format.

That's exactly what we would need if we had tens or hundreds of .org documents and wanted to automate the export process.

Things to keep in mind:

  • :base-extension expects an extension, without a . prefix.
  • :publishing-function depends on the type of content you intend to export.

Let's have a look at another example, though this time, we'll export some stylesheets, too.

(setq org-publish-project-alist
     '(("example.com/org"
         :base-directory "~/website/org/"
         :base-extension "org"
         :publishing-directory "~/website/html/"
         :publishing-function org-html-publish-to-html)
       ("example.com/css"
         :base-directory "~/website/css/"
         :base-extension "css"
         :publishing-directory "~/website/css/"
         :publishing-function org-publish-attachment)))

Now we have two different publishing projects, one for org documents and another for css.

The main differences being, the :publishing-function and the project identifier, i.e. the CAR of the association list. My advice, pick meaningful names for your project identifiers. It doesn't necessarily have to start with a URL, that's just my preference.

Okay! This is enough to get your website rolling, although you might want to see the next section for a more complicated setup.

2. Initialization

I always like to experiment with the structure of my $HOME, which leaves me with a temporarily broken setup sometimes.

I took the steps to remedy this with a few functions, which I'll use with the :base-directory and :publishing-directory properties, the more sensitive area of this setup.

(defvar site-base-directory
  "~/projects/grtcdr.github.io/"
  "The base of the site where files will are to be processed and exported.")

(defun site-content-directory (context &optional directory)
  "Prefixes the provided DIRECTORY with the ’site-base-directory’ given the CONTEXT."
  (cond ((equal context 'publish) (concat site-base-directory "html/" directory))
        ((equal context 'base) (concat site-base-directory "org/" directory))))

site-base-directory is the base location of my site, and site-content-directory will build a path depending on the parameters I pass. This makes it a lot safer if I ever decide to move things around.

3. Setup

Now I'd like to present my setup. It does all of the heavy lifting for me, and allows me to focus on more important things such as chatting with strangers on IRC.

(use-package ox-publish
  :custom
  (org-publish-project-alist
   `(("grtcdr.github.io/content"
      :base-directory ,(site-content-directory 'base)
      :base-extension "org"
      :publishing-directory ,(site-content-directory 'publish)
      :publishing-function org-html-publish-to-html
      :exclude "\\(README\\|setup\\).org"
      :recursive t
      :sitemap-filename "index.org"
      :sitemap-title "grtcdr's website"
      :with-author nil
      :with-creator nil
      :with-date nil
      :with-email nil
      :with-title nil
      :with-toc nil
      :section-numbers nil)
     ("grtcdr.github.io/img"
      :base-directory ,(site-content-directory 'base "img/")
      :base-extension "png\\|jpe?g"
      :publishing-directory ,(site-content-directory 'publish "img/")
      :publishing-function org-publish-attachment)
     ("grtcdr.github.io/blog"
      :base-directory ,(site-content-directory 'base "blog/")
      :base-extension "org"
      :publishing-directory ,(site-content-directory 'publish "blog/")
      :publishing-function org-html-publish-to-html
      :exclude ".\\(setup\\|navigation\\).org"
      :with-date t
      :with-title nil
      :with-toc nil
      :section-numbers nil)
     ("grtcdr.github.io/css"
      :base-directory ,(site-content-directory 'base "css/")
      :base-extension "css"
      :publishing-directory ,(site-content-directory 'publish "css/")
      :publishing-function org-publish-attachment)
     ("grtcdr.github.io/data"
      :base-directory ,(site-content-directory 'base "data/")
      :base-extension "txt\\|pdf"
      :publishing-directory ,(site-content-directory 'publish "data/")
      :publishing-function org-publish-attachment)
     ("grtcdr.github.io"
      :components ("grtcdr.github.io/img"
                   "grtcdr.github.io/data"
                   "grtcdr.github.io/css"
                   "grtcdr.github.io/content"
                   "grtcdr.github.io/blog")))))

There's a project identifier for every component of the website, and the last one groups them all together, so that I don't have to selectively export every single one.

If you'd like to explore this topic in greater detail, please read the wonderful documentation provided by the Org project.