Logo

dev-resources.site

for different kinds of informations.

How to Set up A Clojure Script and Phoenix Project

Published at
9/9/2022
Categories
clojure
clojurescript
phoenix
elixir
Author
stephcrown
Author
10 person written this
stephcrown
open
How to Set up A Clojure Script and Phoenix Project

Recently I was tasked with setting up a project with ClojureScript and Phoenix. I tried to check the internet for a "how-to" but did not find an up-to-date guide on completing this setup. After some research and trials, I was finally able to get it done so I decided to share the knowledge I have gained.

ClojureScript is a functional language that solves a lot of the shortcomings and inconsistencies of JavaScript. It compiles to JavaScript and it is possible to call any JavaScript function with ClojureScript syntax.

Phoenix is an awesome web framework that is built on Elixir, a general-purpose, functional language built on the Erlang's VM and compiles to Erlang bytecode.

To set up ClojureScript and Phoenix, we can use an existing Phoenix project or set up a new Phoenix project by running

mix phx.new cljs_phoenix --no-ecto
Enter fullscreen mode Exit fullscreen mode

We use the --no-ecto flag because we do not want to set up a database in this project.

We will be making use of the shadow-cljs package to compile our ClojureScript code, so we will install shadow-cljs as a dev dependency in the assets folder of our project.

cd assets && yarn init && yarn add --dev shadow-cljs
Enter fullscreen mode Exit fullscreen mode

Up next, we will initialize a new shadow-cljs project in the assets folder by running

node_modules/.bin/shadow-cljs init
Enter fullscreen mode Exit fullscreen mode

Running the above command displays a prompt Create? [y/n]:. Type y and click enter to proceed.

This generates a shadow-cljs.edn file. This file is what we will use to configure the build of our ClojureScript code and to manage our dependencies. You can liken it to what package.json does for NodeJs projects.

Before we add our configurations to the shadow-cljs.edn file, let us first create a ClojureScript source file. We will create a src folder in the assets folder. This will be the source directory for ClojureScript. Next, we create a namespace, app.main.cljs. Based on the Java Naming Conventions, this namespace will be placed in the app/main.cljs file. So we create the app/main.cljs file in our src folder and paste the following code into it.

(ns app.main)

(def value-a 1)
(defonce value-b 2)

(defn main! []
  (println "App loaded!"))

(defn reload! []
  (println "Code updated.")
  (println "Trying values:" value-a value-b))
Enter fullscreen mode Exit fullscreen mode

This is a simple script that logs some text to the console. At the end of this guide, we will see the texts displayed in our console.

Now let’s revisit the shadow-cljs.edn file to configure our build. We’ll paste the following code into the file.

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

  :dependencies
  []

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

  :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!}}}}
Enter fullscreen mode Exit fullscreen mode

As we can see, this is a map of key-value pairs. This configuration is customizable, so we will go through what each of the configurations does so that you can either use it directly or edit it to suit your needs.

  • The :source-paths configures the classpath. The compiler will make use of this configuration to find Clojure(Script) source files (eg. .cljs ). It is possible for us to use multiple source paths. For example, if we want to separate our tests from the main code, we could include the source path for our test and our main code as below.
{:source-paths ["src/main" "src/test"]
 ...}
Enter fullscreen mode Exit fullscreen mode
  • The :dependencies field will contain the list of dependencies we require in the project. For now, we are not using any dependency so the field is an empty array.

  • The :dev-http field expects a map of port-number to config. shadow-cljs provides additional basic HTTP servers that serve static files. The reason for this is that some browsers may not be able to access files in some folders, but when these files are served over HTTP, the browser can easily access them by <domain>:<port-number>/<path-to-file>. In our configuration, we made our port number 9080 (we can use any available port number). We mapped the port number to "../priv/static". This tells shadow-cljs to serve all the files in "../priv/static" over HTTP, and make them accessible to the browser on port 9080. We can use any path for this. We chose this path because our Phoenix application already has a configuration that makes all the files in priv/static accessible at the "/" path. We can see that in the PhoenixCljsWeb.Endpoint module.

# Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phx.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/",
    from: :cljs_phoenix,
    gzip: false,
    only: ~w(assets fonts images favicon.ico robots.txt)
Enter fullscreen mode Exit fullscreen mode
  • We specify the build configurations in the :builds field. We can specify multiple builds with different ids, but in our current configuration, we only have one build - :app. Let us take a look at the configurations we specified for the :app build.

    • :output-dir specifies the directory used for all compiler output. This is where the entry point JavaScript file and all related JS files will appear
    • :asset-path is the relative path from the web server’s root to the resources. The dynamic loading during development and production needs this path to correctly locate files. Since we are serving our web server in the folder "../priv/static", and our :output-dir is "../priv/static/js", it means that our :asset-path will be "/js"
    • :target specifies the target environment for the compilation
    • :modules configure how the compiled source code are bundled together and how the final .js are generated.

For more information about the build configurations and dependency managements, check the docs.

We will need to update the CljsPhoenixWeb.Endpoint module to add js to the list of folders in priv/static that will be served from "/". This module is in the lib\cljs_phoenix_web\endpoint.ex file

# Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phx.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/",
    from: :cljs_phoenix,
    gzip: false,
-    only: ~w(assets fonts images favicon.ico robots.txt)
+    only: ~w(assets fonts images favicon.ico robots.txt js)
Enter fullscreen mode Exit fullscreen mode

Since shadow-cljs is capable of hot-reloading, we need to remove the .js extension from the patterns watched by the Phoenix live reload. We do this in the config/dev.exs file

# Watch static and templates for browser reloading.
config :cljs_phoenix, PhoenixCljsWeb.Endpoint,
  live_reload: [
    patterns: [
-     ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
+     ~r"priv/static/.*(css|png|jpeg|jpg|gif|svg)$",
      ~r"priv/gettext/.*(po)$",
      ~r"lib/cljs_phoenix_web/(live|views)/.*(ex)$",
      ~r"lib/cljs_phoenix_web/templates/.*(eex)$"
    ]
  ]
Enter fullscreen mode Exit fullscreen mode

After compilation, the build we specified for :app will create an app.js file in the output directory, "priv/static/js"  Since we want to load this in the browser we will include this javascript in our root HTML file, lib\cljs_phoenix_web\templates\layout\root.html.heex.

<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/js/app.js")}></script>
Enter fullscreen mode Exit fullscreen mode

Finally, we need to start the shadow-cljs server every time we start the Phoenix server. We do this by adding the following watcher to the list of watchers in the config/dev.exs file.

config :cljs_phoenix, CljsPhoenixWeb.Endpoint,
  watchers: [
    # ...,
    bash: [
      "node_modules/.bin/shadow-cljs",
      "watch",
      "app",
      cd: Path.expand("../assets", __DIR__)
     ]
  ]
Enter fullscreen mode Exit fullscreen mode

What this does is run node_modules/.bin/shadow-cljs watch app in the assets folder. This will start the shadow-cljs server, and compile the ClojureScript files according to the configurations in the shadow-cljs.edn file. Then it watches for changes in ClojureScript files to update the compiled code.

Now, we are ready to start our Phoenix server. We ensure we are in the root folder of our project (not the assets) folder, then we run

mix phx.server
Enter fullscreen mode Exit fullscreen mode

To be sure that we have successfully set up the project, we go to localhost:4000 in our browser. In the console, we should see a prompt that shadow-cljs is ready. We will also see the "App loaded!" text that we printed in the src/app/main.cljs file.

Whenever we want to release our application, in the assets folder, we will run

shadow-cljs release app
Enter fullscreen mode Exit fullscreen mode
clojurescript Article's
30 articles in total
Favicon
Querido Yo del Futuro: Hoy intentaremos configurar una aplicación fullstack en Clojure
Favicon
Why I chose Clojure/Script for building Vade Studio
Favicon
Converting JS Libraries to Clojure: Part 1
Favicon
Deploy your ClojureScript App to Cloudflare Workers
Favicon
shadow-cljs and running tests
Favicon
Giving new life to existing Om legacy SPAs with re-om
Favicon
Is Clojure the only language you need?
Favicon
Building an Application with ClojureScript
Favicon
How to Set up A Clojure Script and Phoenix Project
Favicon
How to create a library that works with Clojure and ClojureScript
Favicon
Setup shadow-cljs react project
Favicon
Logging readable ClojureScript (.cljs) errors to sentry!!
Favicon
How can I create a ClojureScript web app from scratch with Reagent and npm?
Favicon
Set up SSL/TLS for shadow-cljs https server
Favicon
ClojureScript on Cloudflare Workers
Favicon
Storybook.JS with Shadow-CLJS
Favicon
World Statistics Exercise
Favicon
The Pleasure of Clojure(Script): Part 1
Favicon
Using Specter on tree data structures in Clojure
Favicon
Clojure Re-Frame Exercise
Favicon
¿Por qué Clojure?
Favicon
Implementing the feed
Favicon
Try something new this week
Favicon
ClojureScript async MVU
Favicon
ClojureScript simple MVU loop
Favicon
Integrating ClojureScript with JavaScript tooling
Favicon
Understanding Transducers in JavaScript
Favicon
Casting visual spells with p5.js and ClojureScript, part 1
Favicon
ClojureScript REPL Workflow
Favicon
Developing, Testing and Deploying AWS Lambda Functions written in ClojureScript

Featured ones: