How to Set up A Clojure Script and Phoenix Project
Tags: clojure, clojurescript, phoenix, elixir
Published: 2022-09-09 22:45:21

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 

```bash
mix phx.new cljs_phoenix --no-ecto
``` 

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.  

```bash
cd assets && yarn init && yarn add --dev shadow-cljs
```

Up next, we will initialize a new `shadow-cljs` project in the `assets` folder by running
```bash
node_modules/.bin/shadow-cljs init
```

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.

```clj
(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))
```

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.
```clj
;; 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!}}}}
```

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.
```clj
{:source-paths ["src/main" "src/test"]
 ...}
```
- 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.

```clj
# 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)
```

- 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](https://shadow-cljs.github.io/docs/UsersGuide.html). 

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

```clj
# 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)
```

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

```clj
# 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)$"
    ]
  ]
```

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`.

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

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__)
     ]
  ]
```

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 
```bash
mix phx.server
```
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 
```bash
shadow-cljs release app
```