mafinar

mafinar

ClojureScript + LiveView

I did not add this to a “this weekend I’ll learn” like my few other journals as I am decided on using this in the long term.

Last I worked with Clojure was first around 2011 (for a few months, to implement a TCP server but other programmers revolted and had to move to Netty + Java) and then ClojureScript in 2014, (with ClojureScript + Reagent, I actually learned Reagent before React), that stretched almost a year, but again, the other developers revolted and wanted to use Angular instead (I know right?), so that was just one project.

Last weekend while trying to embed Alpine 3 to my project, combined with dependa bot alerts, I wondered if I could go without “Webpack” and used “Parcel” instead, in the spirit of science, I did that and it went smooth. Alpine 3 worked too, seems like I needed Webpack 5 or something (but I am past caring about that now). Then it occured to me, hey, remember ClojureScript? Why not try that out, since I had enough JS fatigue for the hour?

I went on to Clojure land and discovered this thing called Shadow CLJS, along with some cool things like Spec, Transducers etc, folks were busy! So, I surfed the web for articles, found one, and with it, I converted my app.js to src/main.cljs, wrapped all my hooks with (deftype)-s, and an hour or two later, it worked!

I did keep the Parcel though, because I did not know how to copy SCSS in ClojureScript (yet).

Last weekend was a blast, sure, I worked hard. But when things failed, they had logical reasoning for it. And while Elixir and the Lisp are different on the surface, the data centric idea remained similar. So it was context switching without context switching for me. I had three hooks, some charts and a heat map, those were promptly converted. Wasn’t as easy as they were when I first made them, but was much more fun, and readable. Good times.

Therefore I decided to stick to it. This project will get more hooks, so more cljs coming along the way. My other project KarmaWerks (A LiveView + DGraph + JS ClojureScript) is getting a revival next month. And my short bike tracking weekend work (MilesToGo - realtime personal location tracking app something I want to track my next month’s vacation/hiking with) is going to keep me occupied during the weekends. All Open Source things! So lots of fun coming our way!

So here’s what I’ll do. I’ll update my work progress in this place. I will work with KarmaWerks AFTER I am done with a smallest workable unit of MilesToGo, the Covid19 dashboard is like somewhat workable already and is used as the experimentation field for the whole thing. I’ll speak on progress of whatever it is I am working with at the time. To remind myself of how things were should I get back to them after 2 years!

And before I leave, my gratitude to the authors of the two articles that helped me set up Parcel and ClojureScript with Phoenix:

  1. https://www.dwolla.com/updates/webpack-to-parcel/ (Setting up Parcel + Phoenix)
  2. https://darioghilardi.com/how-to-setup-a-phoenix-and-clojurescript-project/ (Setting up ClojureScript + Phoenix)

Thank you for getting me set-up without having to scratch my head.

Most Liked

mafinar

mafinar

OFFTOPIC. I also realized, it’s been a year since I joined DevTalk! It’s been a good year in a great forum with awesome people! Learned a lot of things! Thank you folks!

mafinar

mafinar

Add ClojureScript to MilesToGo

MilesToGo is a personal location tracking app I am making to track cars, bikes etc that you own and then checkout all the places you visited. Currently it only has authentication. But I intend to have a nice little implementation in a month. I will mostly be talking about that in here!

In this post I am going to describe the steps I took to include ClojureScript to MilesToGo. The steps are:

  • Remove Webpack, node_module, and all JS code
  • Add Parcel (mostly for SCSS packaging)
  • Add ClojureScript along with its app.js equivalent with all live-view initialization

So let’s start

Removing JS Code

  • First, I removed everything under the assets/js/ folder, along with package-lock.json and webpack.config.js
  • In package.json I removed everything in devDependencies

At this point the system is broken in the front-end section. iex -S mix phx.server will spawn the Elixir process fine, but will complain that it couldn’t start the Node watcher. You will still be able to start and see the app as it is, because priv/static/*, so I went ahead and deleted those too. Now it’s all a mess! Let’s clean it.

Add Parcel

This bit is optional, I could just keep webpack and let it do the work, but Parcel is simpler and since all I need it for is to pack the styles, why not keep it minimal and less complected?

  • cd into assets/
  • npm install --save-dev parcel (And hello vulnerability warning!)
  • Made package.json’s script-s look like this:
{
  ...
  "scripts": {
    ...
    "deploy": "cp -R static/* ../priv/static/ && parcel build js/assets.js --dist-dir ../priv/static/css --public-url /dist --no-cache",
    "watch": "cp -R static/* ../priv/static/ && parcel watch js/assets.js --dist-dir ../priv/static/css --public-url /dist"
  },
  ...
}
  • What should’ve been app.js is now asseta.js. Because that is what it acts like. And asset manager. So I went ahead and created an assets.js with just one line of code: import css from "../css/app.scss";

By now I had Parcel set up. But Phoenix needs to know when/how to reload these things.

  • Now, add npm install --save-dev parcel-plugin-stdin because well, to kill node processes and mimic --watch-stdin functionality upon server stops. (Or something along the line, I didn’t care to read more about it at this time)
  • Added npm: ["run", "watch", cd: Path.expand("../assets", __DIR__)] in dev.exs in the first MilesToGo.Endpoint config. Replacing the webpack friendly one.
  • In root.html.leex, I renamed <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> to <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/assets.css") %>"/>
  • If I run Phoenix now, I will see all the colors coming back. None of the live-views though. We need to get ClojureScript for that.

Add ClojureScript

  • CD into assets/
  • I never thought I’d be loving an npm command this much. I went ahead and typed npm install --save-dev shadow-cljs to kick things off. Also, no vulnerabilities.
  • Next, I initialized shadow-cljs with node_modules/.bin/shadow-cljs init. A wild shadow-cljs.edn appeared.
  • This totally needs to be a watcher in dev.exs. So, added the following watcher to dev.exs right below the Parcel one (This will change a few steps later though):
   node: [
      "node_modules/.bin/shadow-cljs",
      "watch",
      "app",
      cd: Path.expand("../assets", __DIR__)
    ]
  • I realized if I watch all the js patterns inside priv/static/ I will be overwhelmed at all the files that will be generated, however, I do need to watch just for one file, the resultant app.js, so I went ahead and removed the js from the patterns and added app.js like so:
  ...
  live_reload: [
    patterns: [
      ~r"priv/static/js/app.js$", # Add the app.js
      ~r"priv/static/.*(css|png|jpeg|jpg|gif|svg)$", # Remove the js pattern
      ...
    ]
  ]
  • Now in Clojure land, we need to mention where and how to generate files. And that’s need to be in our shadow-cljs.edn file. So, I went ahead and added things in builds. The file now looks like:
;; shadow-cljs configuration
{:source-paths
 ["src"]

 :dependencies
 []

 :dev-http {9080 "../priv/static/js/"}

 :builds {:app {:output-dir "../priv/static/js/"
                :asset-path "/js",
                :target :browser
                :modules {:app {:init-fn app.main/main!}}
                :devtools {:after-load app.main/reload!}}}}
  • Now let’s test things, and for that, let’s add a ClojureScript file inside assets/src/app/main.cljs. Just to see folks are all friendly with each other:
;; assets/src/app/main.cljs contents. For now. This will house live-view things in a bit.
(ns app.main)

(defn main! []
  (println "App loaded!"))
  • Now the problem with me was, I kept getting error claiming “Already Started”, because killing Phoenix didn’t seem to have killed the java process. So as per the tutorial, I added this file and changed my watcher to reflect it. (The file is /assets/cljs-start.sh)
#!/usr/bin/env bash

# Start the program in the background
exec "$@" &
pid1=$!

# Silence warnings from here on
exec >/dev/null 2>&1

# Read from stdin in the background and
# kill running program when stdin closes
exec 0<&0 $(
  while read; do :; done
  kill -KILL $pid1
) &
pid2=$!

# Clean up
wait $pid1
ret=$?
kill -KILL $pid2
exit $ret

And my watcher looked like (Remember that file I said I’ll change?):

  watchers: [
    ...
    bash: [
      "cljs-start.sh",
      "node_modules/.bin/shadow-cljs",
      "watch",
      "app",
      cd: Path.expand("../assets", __DIR__)
    ]
  ]
  • Now if I run iex -S mix phx.server, open the site and console, I will see a nice "App Loaded" message.

Yay! I successfully managed to add ClojureScript in a Phoenix project, thanks to shadow-cljs

Enter LiveView

  • main.cljs is our person to write whatever ClojureScript we want in. Remember the app.js with all the liveview.connect() etc? We need those in ClojureScript. Without further ado, I’ll just add what’s in the file:
(ns app.main
  (:require
   ["nprogress" :as nprogress]
   ["phoenix_html" :as phoenix_html]
   ["phoenix" :refer [Socket]]
   ["phoenix_live_view" :refer [LiveSocket]]))

(def csrf-token (-> "meta[name='csrf-token']"
                    (js/document.querySelector)
                    (.getAttribute "content")))

(def live-socket-params
  (clj->js {:params {:_csrf_token csrf-token}}))

(set!
 (.. js/window -liveSocket)
 (LiveSocket. "/live" Socket live-socket-params))

(def live-socket (.. js/window -liveSocket))

(defn main! []
  (.connect live-socket))

Now we have a working example of LiveView + ClojureScript.

There are some thing missing out though:

  1. NProgress?
  2. There were some JS kung-fu to manage closing of alerts and browser burgers etc.

I will be back with those soon!

In the meantime, the code is in Github! Here’s the PR. It is incomplete but if you want a more full ClojureScript + LiveView implementation, there’s this.

mafinar

mafinar

Went ahead and removed Parcel too. Installed the Dart Sass and am using that instead. I now have the smallest package.json I ever produced:

{
  "repository": {},
  "description": " ",
  "license": "MIT",
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"
  },
  "devDependencies": {
    "bulma": "^0.9.1",
    "shadow-cljs": "^2.14.5"
  }
}

And the dev.exs reference of npm changed to sass, like:

...
watcher:
    ...
    sass: [
      "--watch",
      "assets/css/app.scss",
      "priv/static/css/assets.css",
    ],

Not sure I did it right but it works.

Popular Backend topics Top

PragmaticBookshelf
Don’t accept the compromise between fast and beautiful: you can have it all. Phoenix creator Chris McCord, Elixir creator José Valim, and...
New
AstonJ
Just done a fresh install of macOS Big Sur and on installing Erlang I am getting: asdf install erlang 23.1.2 Configure failed. checking ...
New
AstonJ
In case anyone else is wondering why Ruby 3 doesn’t show when you do asdf list-all ruby :man_facepalming: do this first: asdf plugin-upd...
New
AstonJ
If you are experiencing Rails console using 100% CPU on your dev machine, then updating your development and test gems might fix the issu...
New
PragmaticBookshelf
Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New
PragmaticBookshelf
This hands-on book will quickly get you building, querying, and comparing graph data models using a robust, concurrent programming langua...
New
Cellane
I’ve been asked by my supervisors at work to finally give everyone in the team presentation about “that Elixir thing you can’t seem to sh...
New
PragmaticBookshelf
Learn Functional Programming by building a complete web application that uses Kotlin, TDD with end-to-end tests, and CQRS and Event Sourc...
New
ManningBooks
Effectively reading and understanding existing code is a developer’s superpower. In this book, you’ll master techniques for code profilin...
New
ManningBooks
Dodge the common mistakes that even senior developers make, take full advantage of static analysis tools, and deliver robust and error-fr...
New

Other popular topics Top

wolf4earth
@AstonJ prompted me to open this topic after I mentioned in the lockdown thread how I started to do a lot more for my fitness. https://f...
New
dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
AstonJ
We have a thread about the keyboards we have, but what about nice keyboards we come across that we want? If you have seen any that look n...
New
AstonJ
There’s a whole world of custom keycaps out there that I didn’t know existed! Check out all of our Keycaps threads here: https://forum....
New
foxtrottwist
Here’s our thread for the Keyboardio Atreus. It is a mechanical keyboard based on and a slight update of the original Atreus (Keyboardio ...
New
PragmaticBookshelf
Rust is an exciting new programming language combining the power of C with memory safety, fearless concurrency, and productivity boosters...
New
AstonJ
I have seen the keycaps I want - they are due for a group-buy this week but won’t be delivered until October next year!!! :rofl: The Ser...
New
wmnnd
Here’s the story how one of the world’s first production deployments of LiveView came to be - and how trying to improve it almost caused ...
New
First poster: joeb
The File System Access API with Origin Private File System. WebKit supports new API that makes it possible for web apps to create, open,...
New
PragmaticBookshelf
Author Spotlight Mike Riley @mriley This month, we turn the spotlight on Mike Riley, author of Portable Python Projects. Mike’s book ...
New