Bob Nadler, Jr. Bob Nadler, Jr.

ClojureScript with Middleman via Shadow-CLJS

Published about 5 years ago 6 min read
In the middle
Image by Giuseppe Milo

Ruby's Middleman is my preferred tool when I need to generate a static website. In this post I'm going to show you how to set up a Middleman project that uses ClojureScript instead of the default JavaScript or CoffeeScript that ships with Middleman.

First up, we need to create our Middleman project. The Middleman site has instructions for setting up a new project, I'm going to go through them briefly here. Assuming you have middleman installed, run

middleman init cljs-example

Change into the cljs-example directory and confirm that Middleman has been setup properly by running the preview server with

middleman server

Navigate to the URL that the server is running on (by default http://localhost:4567/) and you should see the "Middleman is Running" page. You can stop the server once you've confirmed everything is ok.

Middleman used to ship with support for Sprockets, however, newer versions ship with a feature called external pipelines. This feature allows Middleman to run multiple subprocesses which output content to temporary folders which are then merged into the Middleman sitemap. To configure our project to compile ClojureScript, add the following to the project's config.rb

activate :external_pipeline,
  name: :clojurescript,
  command: "./node_modules/.bin/shadow-cljs #{build? ? :release : :watch} app",
  source: ".tmp"

The above configures an external pipeline that will compile our ClojureScript code into a folder called .tmp using shadow-cljs, which we'll install next.

shadow-cljs is a node library that gives you everything you need to compile ClojureScript code using some good defaults. Since it's a node.js library you need either npm or yarn installed. We're going to use yarn in this example, but either will work.

yarn add --dev shadow-cljs

This will create a package.json and yarn.lock file in your project root. It will also install shadow-cljs and its dependencies into your node_modules folder. shadow-cljs is configured using a file called shadow-cljs.edn. We can generate one like this

./node_modules/.bin/shadow-cljs init .

Using your text editor modify the generated shadow-cljs.edn file to look like this

;; shadow-cljs configuration
{:source-paths ;; 1
 ["source/cljs"]

 :dependencies ;; 2
 []

 :builds ;; 3
 {:app {:target :browser
        :output-dir ".tmp/javascripts"
        :asset-path "/javascripts"
        :modules {:site {:entries [cljs-example.core]}}}}}

The parts of the configuration are as follows:

  1. The path to our ClojureScript source files.
  2. Any dependencies. We'll leave this empty for now.
  3. A build target. We're targeting the browser and our compiled ClojureScript files will be put into the .tmp/javascripts folder. We also need to specify the modules we want compiled. In this example we're compiling a single module that uses the cljs-example.core namespace which will be output to a file named site.js.

We haven't created any ClojureScript files yet, so let's do that now. Create a file named source/cljs/cljs-example/core.cljs (depending on your editor you may need to explicitly create the cljs and cljs-example directories). The file should look like this

(ns cljs-example.core)

(set! (.-innerHTML (.getElementById js/document "intro"))
      "Middleman is Running on ClojureScript")

I'm going to assume you're already familiar with ClojureScript; what the code above will do is replace the text of an element with an ID of intro on the page with "Middleman is Running on ClojureScript".

In order the the text to be replaced, we need to adjust two things: our layout and index files. In the source/layouts/layout.erb file that was generated by Middleman, move the javascript_include_tag method call into the body of the HTML. This will ensure that our compiled ClojureScript code won't be ran until the document has loaded.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Use the title from a page's front matter if it has one -->
    <title><%= current_page.data.title || "Middleman" %></title>
    <%= stylesheet_link_tag "site" %>
  </head>
  <body>
    <%= yield %>
    <%= javascript_include_tag "site" %>
  </body>
</html>

Next we need to add the intro ID to the h1 tag in source/index.html.erb. The file should now look something like this

---
title: Welcome to Middleman
---

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 340" class="middleman-logo" aria-labelledby="middleman-logo__title" role="img">
  <title id="middleman-logo__title">Middleman</title>
  <path class="middleman-logo__top-left-bar" fill-opacity=".45" d="M0 40L200 0v30L0 60z"/>
  <path class="middleman-logo__top-right-bar" fill="#fff" d="M200 0l200 40v20L200 30z"/>
  <path class="middleman-logo__left-m" fill-opacity=".45" d="M0 78v184l45 5V152l45 83 47-83v129l53 7V52l-57 8-43 83-43-70z"/>
  <path class="middleman-logo__right-m" fill="#fff" d="M400 78v184l-45 5V152l-45 83-47-83v129l-53 7V52l57 8 43 83 43-70z"/>
  <path class="middleman-logo__bottom-left-bar" fill-opacity=".45" d="M0 300l200 40v-30L0 280z"/>
  <path class="middleman-logo__bottom-right-bar" fill="#fff" d="M200 340l200-40v-20l-200 30z"/>
</svg>

<h1 id="intro">
  Middleman is Running
</h1>

<%= link_to(
  "Read Documentation",
  "https://middlemanapp.com/basics/templating_language/",
  target: "_blank"
) %>

Finally, we need to remove the source/javascripts directory that Middleman generated so that we don't get conflicts between the ClojureScript compiled files and any JavaScript files that Middleman generated.

rm -r source/javascripts

Our setup should be complete. Start the middleman preview server like before with middleman server. This time in addition to the normal output from Middleman we should also see some messages from shadow-cljs compiling our ClojureScript code. After a few seconds we should see a message indicating that the compilation was successful.

Output from Middleman server command

Now when we open our site in the browser we should see the "Middleman is Running on ClojureScript" message.

Middleman welcome page in browser

That concludes this step by step guide for using ClojureScript with Middleman. If you'd like to see all the source code, I've created a GitHub repository which you can clone and use as a starting point for your own project. At some point, I will probably make a Middleman template that takes care of all this setup. I also have a couple more blog posts planned on using ClojureScript with React via the Reagent library, as well as incorporating Firebase. In the meantime, let me know if this guide was helpful!


Share This Article