Quick background:

I often use static site generators for landing pages, marketing pages etc. 
Hugo is normally my goto product for this as the themes are good and it is 
easy enough to change them, but recently I felt that there has to be a 
better way when you want something (just slightly) beyond a static page. I 
also have limited time to learn things, and my Go templating was very 
rusty. I use more Clojure(script) these days, so thought that there must be 
a simple way to generate static pages from something like hiccup, with a 
bit of Clojurescript interaction where needed. I also wanted to have a 
sensible dev setup so that I wouldn't have to constantly stop and start 
servers. Finally I've tried to move to shadow cljs for Clojurescript, and 
I've tried to use deps.edn rather than lein/boot, so I wanted to keep doing 
this. I started with looking at Juxt Edge as I've had some success with 
this for servers in the past, but it seemed a bit heavy handed for my 
requirements. I found the following very helpful pages:

https://github.com/magnars/stasis
https://cjohansen.no/building-static-sites-in-clojure-with-stasis
http://nickgeorge.net/programming/building-my-static-clojure-website/
and post that started this discussion above.

I have the following directory structure:

src/clj/mypages    for Clojure
src/cljs/mypages  for Clojurescript  
resources/public/css
resources/public/js
resources/public/images

Using the following:
deps.edn

{:paths ["src/clj" "resources" "src/cljs"]
 :deps
 {org.clojure/clojure       {:mvn/version "1.10.1"}
  org.clojure/clojurescript {:mvn/version "1.10.597"}
  ring                      {:mvn/version "1.8.0"}
  ring/ring-defaults        {:mvn/version "0.3.2"}
  hiccup/hiccup             {:mvn/version "1.0.5"}
  stasis                    {:mvn/version "2.5.0"}
  optimus                   {:mvn/version "0.20.2"}
  com.taoensso/timbre       {:mvn/version "4.10.0"}}

 :aliases
 {:cljs
  {:extra-deps {thheller/shadow-cljs {:mvn/version "2.8.93"}}
   :main-opts ["-m" "shadow.cljs.devtools.cli" "release" "build"] }
  :run
  {:main-opts ["-m" "mypages.server"]}
  :build
  {:main-opts ["-m" "mypages.static"]}}}

shadow-cljs.edn

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

 :dependencies []

 :builds
 {:build     {:target     :browser
              :output-dir "resources/public/js/"
              :asset-path "/public//js/"
              :modules    {:main {:init-fn mypages.helpers/init}}
              :dev        {:devtools        {:repl-pprint true}}
              :devtools   {:console-support false}}}}

some standard ring code for development web server:

src/clj/mypages/server.clj

(ns mypages.server
  (:require [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]
            [taoensso.timbre :as log]
            [mypages.pages.web :refer [dev-app prod-app]]))

(defonce server (atom nil))

(defn start-server! [& [port mode]]
  (log/debug "Mode: " mode)
  (reset! server
          (run-jetty
           (case mode
             "dev" (wrap-reload #'dev-app)
             "prod" (wrap-reload #'prod-app)
             (wrap-reload #'dev-app))
           {:port (if port (Integer/parseInt port) 8080)
            :join? false})))

(defn stop-server! []
  (when @server
    (.stop @server)
    (reset! server nil)))

(def -main start-server!)

stasis needs some code to help generate the markup, and we use optimus to 
tidy up, optimise and serve assets. I only serve some css out of the 
resources/public dir, but you could dump some html in there is you wanted 
to. This is in src/clj/mypages/pages/web.clj

(ns mypages.pages.web
  (:require [stasis.core :as stasis]
            [optimus.assets :as assets]
            [optimus.optimizations :as optimizations]
            [optimus.prime :as optimus]
            [optimus.strategies :refer [serve-live-assets]]
            [mypages.pages.layout :as layout]))

(defn home-page [request]
  (layout/landing-page request))

(defn get-assets []
  (assets/load-assets "public" [#".*\.(jpg|svg|png|js)$"]))

(defn get-public []
  (merge (stasis/slurp-directory "resources/public" #".*\.(html|css)$" 
:encoding "UTF-8")
         {"/" home-page}))

(defn get-dev []
  (merge (get-public)
         {"/signup/" layout/redirect}))

(defn create-app [f]
  (optimus/wrap
   (stasis/serve-pages f)
   get-assets
   optimizations/all
   serve-live-assets))

(def dev-app
  (create-app get-dev))

(def prod-app
  (create-app get-public))

I have some template like functions that allow me to wrap the content 
(which I could possibly write in Markdown later e.g. blog posts) in 
src/clj/mypages/pages/layout.clj:

(ns mypages.pages.layout
  (:require [hiccup.page :refer [html5]]
            [stasis.core :as stasis]))

(defn redirect [request]
  (html5
   [:head
    [:meta {:http-equiv "refresh" :content "0; 
URL='https://server.com/auth/signup'"}]]))

(defn landing-page [request content]
  (html5
   [:head
    [:meta {:charset "utf-8"}]
    [:meta {:name    "viewport"
            :content "width=device-width, initial-scale=1.0"}]
    [:title "MyPages"]
    [:link {:rel "stylesheet" :href 
"https://cdn.jsdelivr.net/npm/bulma@0.8.2/css/bulma.min.css"}]
    [:link {:rel "stylesheet" :href "/css/styles.css"}]
    [:script {:type "text/javascript" :src "/js/main.js"}]
    [:script {:defer true :src 
"https://use.fontawesome.com/releases/v5.3.1/js/all.js"}]]
   [:body [:p content]])

Then some code to generate the static pages when I want them in 
src/clj/mypages/static.clj

(ns mypages.static
  (:require
   [stasis.core :as stasis]
   [optimus.export]
   [optimus.optimizations :as optimizations]
   [mypages.pages.web :as web]))

(def export-dir "static")

(defn export! []
  (let [assets (optimizations/all (web/get-assets) {})]
    (stasis/empty-directory! export-dir)
    (optimus.export/save-assets assets export-dir)
    (stasis/export-pages (web/get-public) export-dir {:optimus-assets 
assets})))

(def -main export!)

Finally I have my Clojurescript in src/cljs/mypages/helpers.cljs

(ns mypages.helpers)

(defn ^:export somefunc []
  (js/alert "Not available yet .... sorry"))

(defn ^:export init []
  (js/alert "All ready now"))

To build the Clojurescript you can use either of:

npx shadow-cljs release build

or

clojure -A:cljs


To work on the static pages, use:

clojure -A:run

This will reload when you change them.

If you are making changes to the cljs regularly there is probably a neat of 
using the shadow-cljs dev web server and doing some sort of reload, but I 
haven't needed that yet, so have not done this. If anyone does, then please 
post up how.

I add pages and required clojurescript and once this is ready I build the 
static site by first building the Clojurescript (as above), then build the 
static pages:

clojure -A:build

I then package it all up into a Docker with nginx serving the site. I use a 
Makefile in the CI to do this.

The setup is fairly simple and could do with some refinement (hot reload of 
js would be next). I like that it gets me to something working quickly and 
it would be easy to being in something like Reagent if a few React 
components are needed. I really wanted something that was small and 
generated fast loading sites. I use re-frame for SPAs, but for some work 
this is overkill and this setup is better. I hope that this is of use, 
please ask any questions.

Regards
Jag


On Wednesday, 29 April 2020 19:37:45 UTC+1, Blake Miller wrote:
>
> Jag,
>
> I think what you described is worth sharing. I like the simplicity of that 
> approach and the efficiency of the final artifact.
>
> -Blake
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/clojure/d829c8b3-a41a-4d32-8964-a648a5280b14%40googlegroups.com.

Reply via email to