Total Pageviews

Wednesday, 25 January 2017

Yesod and Hakyll together

I currently use Yesod along with Keter for the seattlehaskell.org web site and I’m very pleased with it. The site has a very small dynamic portion, specifically the bit which queries the Meetup API to list upcoming meetings on the web site’s front page.
In order to support future static content, I really want to use a static content generator and the obvious choice for a Haskell-based and Haskell-themed web site is Hakyll. So, the problem is that I would like the dynamic portions of my web site to use Yesod, the static portions to use Hakyll and, yet, I would like to keep the two visually consistent. Over the course of a few blog posts, I will describe the various steps I have taken to make this happen. Note, that I haven’t launched the new variant of the site yet and, so, some of the approaches I will describe are still experimental.

Part 1: Apache configuration

I use Apache as the front end web server and, so, the first problem is how to serve such a “blended” web site. I have Keter serving my Yesod application on port 3000 and I would like all my static content to be presented under the virtual subdirectory content. So, given a domain name of mydomain.com, here are a few examples of the routing that I would like to configure:
  • Routed to Keter/Yesod:
    • http://mydomain.com/ (dynamic portion of Yesod app)
    • http://mydomain.com/about (dynamic portion of Yesod app)
    • http://mydomain.com/static/css/bootstrap.css (static portion of Yesod app)
  • Routed to Apache’s document root:
    • http://mydomain/content/
    • http://mydomain/content/about
    • http://mydomain/content/pages/foo
To achieve this, I use the following Apache configuration:
<VirtualHost *>
ServerName mydomain.com
ServerAlias www.mydomain.com
ServerAdmin admin@mydomain.com
ProxyPreserveHost On
ProxyPass /content !
ProxyPass / http://0.0.0.0:3000/
ProxyPassReverse / http://0.0.0.0:3000/
Alias /content /var/www/mydocroot/
<Directory /var/www/mydocroot/>
<IfVersion >= 2.4>
Require all granted
</IfVersion>
<IfVersion < 2.4>
Order allow,deny
Allow from all
</IfVersion>
DirectoryIndex index.html
RewriteEngine on
RewriteBase /content/
# Rewrite rules described here:
# http://stackoverflow.com/questions/1992183/how-to-hide-the-html-extension-with-apache-mod-rewrite
# Rewrite URLs without .html extension
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
# Redirect URLs with .html extension
RewriteCond %{THE_REQUEST} ^[A-Z]+\ (/[^\ ]*)\.html[?\ ]
RewriteRule (.*)\.html$ /content/$1 [R=307,L]
</Directory>
</VirtualHost>
view raw apache2.conf hosted with ❤ by GitHub
This sets up reverse proxing to http://0.0.0.0:3000/ for all URLs whose root directory is not content. All URLs under content are rooted to the document root. This configuration also does nice URL rewriting so that the .html file name extension is not needed for files that exist in the document root. Furthermore, if the request does include .html, the server will issue an HTTP 307 redirect to the extensionless URL - thus ensuring one version of truth.
I already have this reverse proxying set up at seattlehaskell.org with the Keter-hosted Yesod application up and running. Currently, the static content consists of a single “Hello World” file. This will eventually be replaced with my Hakyll-generated static content.

from http://blog.rcook.org/blog/2016/yesod-and-hakyll-together-part-1/
-------------


There’s no code today. Just lots and lots of words. Code will follow soon!
Both Yesod and Hakyll are well-documented and have reasonable tutorials: both Michael Snoyman’s book and Jasper Van der Jeugt’s site are great resources.

Template system mismatches

What is not documented is how to combine the two. Part 1 of this series describes my Apache configuration for serving content from the two parts of the web site. After figuring that out, the most difficult challenge was making the two parts of the sites look and feel as if they were part of a single coherent site. The single biggest complexity is that both frameworks employ different templating systems by default. Yesod employs Shakespearean templates, specifically Hamlet, Lucius and Cassius. Hakyll has its own straightforward, but effective, system. I enumerate some of the interesting characteristics below:
  • Shakespearean templates
    • Strongly-typed templates that are parsed at compile time
    • Dynamic “runtime” variants of the templates are supported, but are decidedly “second-class” compared to the strongly-typed variants as one might expect given that Yesod’s strong emphasis on type safety
    • Hamlet (the HTML templates) use a Haml-based syntax
  • Hakyll templates
    • HTML-based
    • Fully dynamic much more like the kinds of templates that non-Haskell developers are typically used to

Philosophical thoughts

Both systems have their pros and cons. However, on a personal note, I have a soft spot for Hamlet’s syntax. However, I don’t object to using real HTML. Ultimately, the question came down to the following: do I adapt my Yesod app to consume Hakyll templates or do I adapt my Hakyll site generator to handle the templates from my Yesod app. Given my unwillingness to compromise the type safety of my Yesod app, I decided to do the latter.

Possible approaches

There were three main approaches I considered:
  • Add an “empty” page to my Yesod app whose sole purpose is to serve up its default layout template rendered as HTML containing Hakyll-style placeholders: in this model, I’d run a local Yesod development instance while running my Hakyll generator: the Hakyll generator would pull its default layout template from the Yesod app using the Haskell equivalent of wget and then use that to render the static site
  • Add Hamlet support directly to my Hakyll site generator
I toyed with the first approach briefly and then decided that this was not personally very satisfying. Both Yesod and Hakyll are highly modular and configurable frameworks, so I decided to power through things and simply use the shakespeare in my Hakyll app.
As I’ll describe in future instalments of this series, this was not entirely straightforward, but did ultimately result in a reasonably elegant solution to the problem.

from http://blog.rcook.org/blog/2016/yesod-and-hakyll-together-part-2/

No comments:

Post a Comment