AWS DNS MX record setup for gsuite - undocumented pitfall

I am starting work on a new project, and wanted to set up a "work" email account for a domain of mine, conferacity.com (there is nothing at that address at time of writing, but maybe check back in a few weeks!). 

I bought the domain a few years ago, through AWS Route 53. I set up a basic account at gsuite, and then tried to follow their instructions for AWS Route 53. Seemed straightforward, but couldn't seem to get it working.

Took me a day to figure it out, but the problem seems to be that Route 53 gives you a different set of nameservers on your domain page from that on your hosted zone for the same domain!

When you have a domain in Route 53, you are automatically set up with a set of four nameservers, you can see them listed in the Route 53 Dashboard if you click on domains and then the specific domain you are interested in, conferacity.com in my case. 

In order to set up your MX records, you need to create a hosted zone from the Route 53 Dashboard. When a hosted zone is first created, it will have only two types of records, NS which is a list of nameservers, and SOA which means "Start of Authority". 

The important thing to note is that the nameservers that AWS sets up for you in NS in the hosted zone for your domain are NOT THE SAME as the ones listed in the domains part of the Route 53 Dashboard - and they need to be the same in order to work correctly.

I suspect it would work either way, but I think it's probably best to copy the nameservers listed on the domains page to the NS record in the hosted zones page. 

Once I did that and waited five minutes, gsuite was able to validate my domain.

Using devcards with shadow-cljs

I decided to learn to use Devcards. Devcards is easy to set up with Figwheel, but my current setup for clojurescript uses shadow-cljs.

This is not terribly difficult once you know how, but I decided it was worth collating the snippets of info needed to make this work. 

The following is in fairly exhaustive detail, and includes a few wrong turns including error messages, so as to hopefully be a useful resource for when things go wrong, as well as when things go right!

Step One - Create new project

We'll start by creating a new shadow-cljs project, using the quick start instructions. I'll use the same filenames and directory structure used in those instructions, to make it easier to follow. We should therefore end up with a shadow-cljs.edn file that looks like this:

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

 :dependencies
 []
 
 :dev-http {8082 "public"}

 :builds
 {:frontend
  {:target :browser
   :output-dir "public/js"
   :asset-path "/js"
   :modules {  :main {:init-fn acme.frontend.app/init}}}}}

I've added the :dev-http {8082 "public"} in order to control which port the dev http server will start on. I've also included the :output-dir and :asset-path keys and values - although the values shown are the default values, I think it's easier to figure out later what needs changing for future projects if you have them listed explicitly. 

Our directory structure now looks like this:

$ tree .

.
├── node_modules
│   └── asn1.js
    └── ...
├── package-lock.json
├── package.json
├── public
│   └── index.html
├── shadow-cljs.edn
└── src
    ├── main
    │   └── acme
    │       └── frontend
    │           └── app.cljs
    └── test

Note that ... in the diagram means "other files or directories listed that were removed from the output when pasting here, to make it easier to see the more important files"

Our single namespace is the cljs file src/main/acme/frontend/app.cljs and should look like this:

(ns acme.frontend.app)

(defn init []
  (println "Hello World"))

The Figwheel docs describe some of the next few steps - I have incorporated them below along with some explanation and any setup specific to shadow-cljs. 

Step Two - Edit the clojurescript build options, add :compiler-options {:devcards true} to the map corresponding to the :frontend key in shadow-cljs.edn file.

In shadow-cljs, this is done in the shadow-cljs.edn file, as explained in the docs:

Most of the standard ClojureScript Compiler Options are either enabled by default or do not apply. So very few of them actually have an effect. A lot of them are also specific to certain :target types and do not apply universally (e.g. :compiler-options {:output-wrapper true} is only relevant for :target :browser).

We also need to add our dependencies. 

Our shadow-clsj.edn should then look like this:

{:source-paths   ["src/dev"
                  "src/main"
                  "src/test"]

 :dependencies    [[devcards "0.2.6"]
                   [reagent "1.0.0-alpha2"]]

 :dev-http {8082 "public"}

 :builds    {:frontend
               { :target :browser
                 :output-dir "public/js"
                 :asset-path "/js"
                 :modules 
                     {:main 
                       {:init-fn acme.frontend.app/init}}
                 :compiler-options {:devcards :true}}}}

The first dependency is the devcards library, the second is reagent, which we will use to generate react components. 

See my previous post if you need more details on setting up the shadow-cljs.ednconfig.

Step Three - require devcards.core in any files (namespaces) that use devcards

E.g

(ns acme.frontend.app
  (:require
   [devcards.core :as dc :refer [defcard]))

Step Four - include code to start the Devcards UI in the entry point function we defined as :init-fn in shadow-cljs.edn

(defn init []
  (dc/start-devcard-ui!))

Step Five - Installing CLJSJS dependencies...via npm

If we enter the terminal at the root of our acme project, and try and compile our program, we get an error:

$ npx shadow-cljs watch :frontend

shadow-cljs - config: /Users/myname/acme-app/shadow-cljs.edn
shadow-cljs - updating dependencies
shadow-cljs - dependencies updated
running: npm install --save --save-exact react@16.13.0 react-dom@16.13.0
+ react@16.13.0
+ react-dom@16.13.0
added 7 packages from 2 contributors and audited 106 packages in 1.158s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

shadow-cljs - HTTP server available at http://localhost:8082
shadow-cljs - server version: 2.9.8 running at http://localhost:9630
shadow-cljs - nREPL server started on port 57191
shadow-cljs - watching build :frontend
[:frontend] Configuring build.
[:frontend] Compiling ...
[:frontend] Build failure:
The required JS dependency "marked" is not available, it was required by "cljsjs/marked.cljs".

Dependency Trace:
    acme/frontend/app.cljs
    devcards/core.cljs
    devcards/util/markdown.cljs
    cljsjs/marked.cljs

Searched for npm packages in:
    /Users/ghufran/Documents/programming/clojure/clojurescript/acme-app/node_modules

See: https://shadow-cljs.github.io/docs/UsersGuide.html#npm-install

Although it may seem like the terminal is frozen, shadow-cljs is still running in the terminal window, so we can open another terminal window for the next step - once we have done so, shadow-cljs should automatically pick up the change and re-compile our program

We can see that shadow-cljs has automatically installed react and react-dom using npm. However, The required JS dependency "marked" is not available, it was required by "cljsjs/marked.cljs".

The problem is that devcards is relying on cljsjs to provide javascript libraries (like marked), whereas shadow-cljs accesses npm packages directly. This is because shadow-cljs 'knows' to use npm to fulfil the cljsjs dependencies - but we still need to install those npm dependencies. We could keep doing this iteratively, adding each npm dependency based on the error messages we get each time, but to save time, I have included the two npm dependencies it turns out that need at this stage (marked and create-react-class) to be installed in a single command below:

$ npm install  marked create-react-class
+ create-react-class@15.6.3
+ marked@1.1.0
added 14 packages from 87 contributors and audited 120 packages in 1.457s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

In our original terminal window, we should now see our program build successfully.

shadow-cljs - config: /Users/myname/acme/shadow-cljs.edn
shadow-cljs - HTTP server available at http://localhost:8082
shadow-cljs - server version: 2.9.8 running at http://localhost:9630
shadow-cljs - nREPL server started on port 63451
shadow-cljs - watching build :frontend
[:frontend] Configuring build.
[:frontend] Compiling ...
[:frontend] Build completed. (166 files, 0 compiled, 0 warnings, 3.65s)

If we open a browser window at the address listed for the HTTP server (http://localhost:8082 in this case), we see...nothing. We need to open the developer tools, one of which is the console. When we do that, we see some errors in the console:

An error occurred when loading devcards.system.js
env.evalLoad @ main.js:2231
(anonymous) @ main.js:2395
main.js:2232 ReferenceError: React is not defined
    at eval (system.cljs:321)
    at eval (system.cljs:321)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:836)
    at Object.env.evalLoad (main.js:2229)
    at main.js:2395
env.evalLoad @ main.js:2232
(anonymous) @ main.js:2395
main.js:2231 An error occurred when loading devcards.core.js
env.evalLoad @ main.js:2231
(anonymous) @ main.js:2406
main.js:2232 ReferenceError: React is not defined
    at eval (core.cljs:117)
    at eval (core.cljs:117)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:836)
    at Object.env.evalLoad (main.js:2229)
    at main.js:2406
env.evalLoad @ main.js:2232
(anonymous) @ main.js:2406
app.cljs:6 acme.frontend.app is running
shadow.module.main.append.js:4 An error occurred when calling (acme.frontend.app/init)
eval @ shadow.module.main.append.js:4
goog.globalEval @ main.js:836
env.evalLoad @ main.js:2229
(anonymous) @ main.js:2408
main.js:2231 An error occurred when loading shadow.module.main.append.js
env.evalLoad @ main.js:2231
(anonymous) @ main.js:2408
main.js:2232 TypeError: devcards.system.start_ui is not a function
    at Function.eval [as cljs$core$IFn$_invoke$arity$1] (core.cljs:71)
    at Function.eval [as cljs$core$IFn$_invoke$arity$0] (core.cljs:64)
    at Object.acme$frontend$app$init [as init] (app.cljs:7)
    at eval (shadow.module.main.append.js:4)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:836)
    at Object.env.evalLoad (main.js:2229)
    at main.js:2408
env.evalLoad @ main.js:2232
(anonymous) @ main.js:2408
browser.cljs:20  shadow-cljs: WebSocket connected!
browser.cljs:20  shadow-cljs: load JS clojure/walk.cljs
browser.cljs:20  shadow-cljs: load JS cljs/spec/gen/alpha.cljs
browser.cljs:20  shadow-cljs: load JS cljs/spec/alpha.cljs
browser.cljs:20  shadow-cljs: load JS cljs/repl.cljs
browser.cljs:20  shadow-cljs: load JS cljs/user.cljs
browser.cljs:20  shadow-cljs: REPL session start successful

The console.log statement in our init function ran, as we can see the acme.frontend.app is running statement in the console. However, there was also an error: ReferenceError: React is not defined. Now that we have the shadow-cljsjs library included in our shadow-cljs.edn dependencies, as well as the reagent library, which should include react, this implies that while the library is in our app, we may not have required it correctly in our namespace. Similarly, the main.js:2232 TypeError: devcards.system.start_ui is not a function likely has the same cause. 

Remember, devcards is expecting cljsjs to fulfil it's dependencies, so we put the cljsjs versions of those two javascript libraries, [cljsjs.react] and [cljsjs.react.dom] in the (:require ... ) statement.

(ns acme.frontend.app
  (:require [devcards.core :as dc :refer [defcard]
            [cljsjs.react]
            [cljsjs.react.dom]))

(defn init []
  (js/console.log "acme.frontend.app is running")
  (devcards.core/start-devcard-ui!))

And...we still get the same error. What's going on here? It turns out that we need to make sure the order of the :require vectors is the same as the order that they will be required. I've never come across this in clojure, I suspect it may be something specific to the way shadow-cljs handles cljsjs dependencies. Anyway, we can fix it by changing the require statement like this:

(ns acme.frontend.app
  (:require
   [cljsjs.react]
   [cljsjs.react.dom]
   [devcards.core :as dc :refer [defcard]))


(defn init []
  (js/console.log "acme.frontend.app is running")
  (dc/start-devcard-ui!))

Now when we go back to the browser, everything seems to load without any problems:

acme.frontend.app is running
shadow-cljs: WebSocket connected!
shadow-cljs: load JS clojure/walk.cljs
shadow-cljs: load JS cljs/spec/gen/alpha.cljs
shadow-cljs: load JS cljs/spec/alpha.cljs
shadow-cljs: load JS cljs/repl.cljs
shadow-cljs: load JS cljs/user.cljs
shadow-cljs: REPL session start successful

So we can add some examples from the reagent-specific parts of the devcards docs. However, you should note that the format of the namespace declaration given in the devcards docs doesn't work in shadow-cljs at present, probably because of the cljsjs loading issue mentioned earlier:

;; from http://rigsomelight.com/devcards/#!/devdemos.reagent

(ns xxx
    (:require [devcards.core]
              [reagent.core :as reagent])
    (:require-macros [devcards.core :as dc
                                    :refer [defcard defcard-rg]]))

So this will again give errors, in this case, ReferenceError: devcards is not defined. Instead, use the format below:

(ns acme.frontend.app
  (:require
   [cljsjs.react]
   [cljsjs.react.dom]
   [reagent.core :as r]
   [devcards.core :as dc :refer [defcard defcard-rg]))


(defn init []
  (js/console.log "acme.frontend.app is running")
  (dc/start-devcard-ui!))


;; examples from http://rigsomelight.com/devcards/#!/devdemos.reagent


(defcard reagent-example-1
  (r/as-element [:h1 "Reagent Example"]))


(defn on-click [ratom]
  (swap! ratom update-in [:count] inc))

(defonce counter1-state (r/atom {:count 0}))

(defn counter1 []
  [:div "Current count: " (@counter1-state :count)
   [:div
    [:button {:on-click #(on-click counter1-state)}
    "Increment"]]])

(defcard-rg counter1
   [counter1])

You should now see the devcards user interface, as shown here:

Click on the link for acme.frontend.app, and you should see the devcards we defined in our acme.frontend.app namespace: 


Adding hot reload to a shadow-cljs project

So at the end of my last post, I noted that "hot reloading" wasn't working. Turns out I had misunderstood the docs

The React and ClojureScript ecosystems combine to make this kind of thing super useful. The shadow-cljs system includes everything you need to do your hot code reload, without needing to resort to external tools.

In order to use it you simply run:

shadow-cljs watch build-id

I should have continued reading the next section, Lifecycle Hooks!. After reviewing my last post, the author of shadow-cljs, Thomas Heller also sent me a link to a blog post which was also super helpful.

So, let's get started and add code reloading!

In shadow-cljs, the ^:dev/after-load keyword is metadata that when added to the function definition, marks it as a function that needs to be run after the code has been reloaded. So I think I'm correct in saying that shadow-cljs is automatically reloading the code in the browser on any saved change, but it's not running any code unless we annotate the function definition with the metadata, to tell it to do so. 

Currently, our entrypoint function is:

(defn ^:export run []
  (rdom/render [indecision-app] (js/document.getElementById "app")))

We change it to:

(defn ^:export ^:dev/after-load run []
  (rdom/render [indecision-app] (js/document.getElementById "app"))
  (js/console.log "'run' called"))

In other words, we add the metadata, and just add a logging statement we can more easily see in the browser console when a code reload takes place. 

It's also possible to annotate a function that only gets called once, when the page first loads, e.g. to set up some state, using the ^:dev/before-load metadata on the function definition - see the links at the top of this post for more details.

By the way, if you're wondering what the ^:export metadata does, it's so that we (and I presume, shadow-cljs) can call the function from javascript in the browser

Setting up a Reagent Clojurescript project with Shadow-Cljs and Cursive

Setting up a clojurescript project with shadow-cljs  figwheel shadow-cljs and cursive

The first thing you should do is make sure you have gone through the official clojurescript tutorial. This explains how to use the clojurescript compiler to convert clojurescript to javascript, as well as get a REPL running that you can use to interact with your running program in the browser, or on NodeJS. 

Other than the REPL part, this is actually pretty similar to the current JS workflow for most developers, where they use Babel to convert from ES6/JSX (i.e. a more convenient language to program in) into standard js that would be annoying to write by hand, but will run in all browsers, or on Node. 

Shadow-cljs is a tool that will do the same thing you just did in the official tutorial, but with hot reloading of code into the browser. This means that when you make changes to the code, it will automatically be compiled to clojurescript, and reloaded into the browser so you can see changes in the browser almost immediately. Figwheel is another tool that does this. 

I'm not really qualified to determine the differences between Shadow-cljs and Figwheel, but my heuristic (= "rule of thumb") when choosing technologies is 

Use what most people use, unless you can articulate (and understand!) exactly why you should use something different

--hmm, state of clojure survey 2019 says 70% use leiningen with lein-cljsbuild, 65% use figwheel and 21% use shadow-cljs - I guess I should be using figwheel...

There are actually two versions of figwheel, lein-figwheel and figwheel-main. Figwheel-main is the newer re-write, and doesn't use leiningen. But it turns out that running a cursive clojure repl with figwheel doesn't work at this time without using leiningen, something I'm trying to avoid at this time (I only want to use one build tool, not two!) What about shadow-cljs?

[EDIT: Thomas Heller, the author of shadow-cljs kindly took at look at this post and suggested some improvements.

The first is that the first few steps below can be done more quickly by following the shadow-cljs quick start instructions. So you should definitely go read that. I'm going to leave this part of the post unchanged, just to make clear what exactly is happening at each step, in exhaustive detail! So if you just want to get up and running quickly, just follow the quick start instructions]

Step One - Make a new directory that will contain our project files

mkdir myproject
cd myproject

Step Two - Install shadow-cljs

Run the following command in the terminal, from the root of the project directory (i.e. from inside the myproject directory:

echo {} > package.json

This will create an empty package.json file. The package.json file is used by npm to make a record of all the project dependencies we want to install in the project. 

npm install shadow-cljs

This installs a copy of shadow-cljs into the project directory, into the automatically generated node_modules directory. You'll see that our package.json file is now as shown:

{
  "dependencies": {
    "shadow-cljs": "^2.9.8"
  }
}

and a package-lock.json file has been automatically created - don't edit this manually, npm will change this automatically to reflect changes in the dependencies of your project. 

Step Three - Set up directory structure for html and js files, and make index.html

I'm making a directory called public containing an index.html file, and a directory called scripts which will contain the javascript files which are going to be loaded into the browser. 

.
├── deps.edn
├── package-lock.json
├── package.json
├── public
    ├── index.html
    └── scripts

Our index.html file is super basic to start with:

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
    </body>
</html>

I plan on using Reagent, a wrapper around React so we can write our code in clojurescript instead of javascript. Then we will compile our clojurescript code into javascript code, and place it in the public/scripts/ folder, with a name of main.js. So we need a way for the contents of main.js to get loaded into the browser, we do that with a script tag as follows:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8" />
    </head>
    <body>
        <script src="scripts/main.js"></script>
    </body>
</html>

Note that the address of main.js in the script tag is relative to the browser file, index.html

We also need to add a <div> with an id, that we can use to tell react / reagent where to render the react code. So we end up with this:

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <div id="app"></div>
        <script src="scripts/main.js"></script>
    </body>
</html>

Step Four - Make a directory for our clojurescript files

We could just make a src directory with a core.cljs file in it, but then we might run into problems later.. As explained in the shadow-cljs manual,

It is therefore recommended to be very disciplined about the names you choose and properly namespacing everything. It may seem repetitive to always use (ns your-company.components.foo) over (ns components.foo) but it will save you from lot of headaches later on.

So in our case, our directory structure will be 

└── src
    └── com
        └── myname
            └── core.cljs

, not

└── src
    └── core.cljs

And the start of our core.cljs file will be

(ns com.myname.core)

corresponding to our directory structure

So now our full directory structure should look like this:

.
├── package-lock.json
├── package.json
├── public
│   ├── index.html
│   └── scripts
└── src
    └── com
        └── myname
            └── core.cljs

Step Five - Tell shadow-cljs what to do

We do this by making a shadow-cljs.edn file at the root of our project, which will contain a map with our configuration. You can generate a template by using npx shadow-cljs init which gives us: 

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

 :dependencies
 []

 :builds
 {}}

We need to fill out values for each of the three keys, :source-paths:asset-path, :dependencies  and :builds.

Our source clojurescript files are in the src folder, and right now, we don't want to set up separate dev and test source paths. so we change the value for the :source-paths key to

{:source-paths ["src"]
...}

The :dependencies key is fairly self-explanatory (just note that the value for :dependencies is a vector, and each dependency is another vector within it.)

{:source-paths ["src"]
:dependencies           [[reagent "1.0.0-alpha2"]]}

Finally, we want to have one build, which we name :app. The corresponding value for the key :app is a map 

{:source-paths ["src"]
:dependencies           [[reagent "1.0.0-alpha2"]]
:builds {:app { :target     :browser
                :output-dir "public/scripts/"
                :asset-path "/scripts/"
                :modules    {:core {:init-fn com.myname.core/main}}}}}

The :target :browser is not strictly required, as shadow-cljs defaults to a browser target if the :targetkey and value are absent. 

The :output-dir is the directory where shadow-cljs will put the newly generated javascript files, which it creates by translating our clojurescript in the src directory. So we set this to public/scripts/ in our case.

The :asset-path is the relative path where the browser can find things like the js files being output by the shadow-cljs compiler, relative to the index.html file. Since my public folder is where my index.html is being served from, and my :output-dir is public/scripts/, we need to use the relative path from /public to public/scripts, which is /scripts

Every build needs at least one module. The :core key in the map sets the name of this module to :core, which means the output js file in :output-dir will be called core.js, while the {:init-fn com/myname/core/main} sets the "entrypoint" of the program to the function main in src/com/myname/core.cljs. The entrypoint of a the program is the first part of the program to run. 

There are two keys we are going to include which are optional, but which are nevertheless extremely useful: :dev-http and :nrepl

If we add :dev-http {8081 "public"} to shadow-cljs.edn, then shadow-cljs will start an http server on localhost:8081, serving files from the "public" folder when we later call npx shadow-cljs watch :app. And if we define a specific port for the nrepl (network repl) to run on, using :nrepl {:port 55555}, it will save us a bit of work each time we start up the repl, in Cursive. 

So our final shadow-cljs.edn file looks like this:

{:source-paths ["src"]
:dev-http {8081 "public"}
:nrepl {:port 55555}
:dependencies           [[reagent "1.0.0-alpha2"]]
:builds {:app { :target     :browser
                :output-dir "public/scripts/"
                :modules    {:core {:init-fn com.myname.core/main}}}}}

Step Six - Open the project in Intellij IDEA (with Cursive plugin)

Click "Create new project" from this screen:

Choose "Bare project" (because we don't want to use Leiningen or tools.deps at this point, just shadow-cljs) 

In the "project location" field, hit the icon with the three dots on the right, then choose the project directory we created in the steps above. You should see the contents of the project directory. 

Without selecting any of the items in the directory, click "open"

Then click "use library" on the left-hand side of the page, then click "create" to the right of the drop-down menu which currently says [No library selected]

Go to and select node_modules > shadow-cljs-jar > bin > shadow-cljs.jar, then click "open"

The drop-down menu next to "use library" should now say "Clojure SDK", and the rest of the settings should be automatically set to the correct values for our project. 

[EDIT: so it turns out, per thheller that we should avoid this - instead, we should have shadow-cljs generate a pom.xml file and use that and Maven to do dependency management in IDEA/Cursive.

First, go the preferences for IDEA plugins, File>Settings>Plugins on Windows/Linux, Intellij_IDEA>Preferences>plugins on macOS. Make sure that Maven is installed

Then type the following command in the terminal:

npx shadow-cljs pom
shadow-cljs - config: /Users/myname/myproject/shadow-cljs.edn

If you do ls in your project directory, there should be a pom.xml file there. 

We now want to Choose "Open / Import" from this screen: 

Navigate to the project folder, and open the pom.xml file. When asked "Open as File" or "Open as Project", click "Open as Project"]

Step Seven - run shadow-cljs

Open a terminal, and type npx shadow-cljs watch :app. This does the following:

  • npx tells the npx package (part of npm) to run shadow-cljs in our node_modules directory. 
  • The watch command:
    • starts the compilation process from clojurescript in our src directory to javascript in our public/scripts/ directory,
    • starts a server, and 
    • loads the index.html page in the publicdirectory
    • starts an nrepl, a network repl that we can use to interact with the javascript runtime in the browser, by writing clojurescript in the repl, which gets translated to js and sent to the console in the browser in real-time.
  • :app is the name of the "build" that we created in Step 5, in the shadow-cljs.edn file. 

Step Eight - Set up a repl

When you typed npx shadow-cljs watch :app, you should have seen something like the following in your terminal:

shadow-cljs - config: /Users/myname/myproject/shadow-cljs.edn
shadow-cljs - HTTP server available at http://localhost:8081
shadow-cljs - server version: 2.9.8 running at http://localhost:9630
shadow-cljs - nREPL server started on port 55555
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (153 files, 0 compiled, 0 warnings, 2.88s)

Click Run > Edit Configurations... You should see the following screen: 

Click on the + sign in the top-left corner, then choose Clojure REPL > Remote 

  • You can change the name to anything you like (I just use "repl")
  • Click nRepl for the Connection Type
  • Choose "Connect to Server", and in Host, type 127.0.0.1 (this is 'localhost', but connection seemed to fail when I wrote that value in this field - so stick to the numeric IP address)
  • In Port, type the number that shadow-cljs printed out above (and which we set as the value of the nrepl port in shadow-cljs.edn above): nREPL server started on port 55555 - so in this case, we enter 55555

  • Click OK

You should now see the word "repl" (or whatever you named your repl) in the top right of the IDEA window, with a small green triangle 'play button' next to it. Click the 'play' button to start a repl client that connects to the nrepl server started earlier by shadow-cljs

You can then type clojure code in the bottom window and when you have entered a complete clojure command (e.g (+ 1 2)), hit the return key, you should see it evaluated in the top window. However, you'll see that this is in fact a clojure repl, not a clojurescript repl. So we need to enter two more commands in order to get a clojurescript repl that is connected to the browser. In the repl, type the following (the bit on the line after => is what the repl prints out after you enter the command. So you type in (shadow/watch :app) and on the next line, the repl will print => :already-watching)

(shadow/watch :app)
=> :already-watching
(shadow/repl :app)
To quit, type: :cljs/quit
=> [:selected :app]

After typing the second command, you should find that the dropdown menu in the repl window has changed from "clj" (clojure) to "cljs" (clojurescript).

See the Cursive repl documentation for more information, including how to send a single clojurescript expression or an entire file directly from the editor to the running repl we just set up. 

Step Nine - open the browser!

You'll note that shadow-cljs also printed the following: shadow-cljs - HTTP server available at http://localhost:8081. So we can open a browser, and navigate to http://localhost:8081

The screen will be blank, because we haven't written any reagent code, and the rest of the index.html file is empty. But our repl is connected to this page, which we can test by entering the following into the repl:(js/alert "Hi from shadow-cljs") which creates an alert in the browser window. 

If we open the developer tools in the browser to view the js console, we can also use console.log from the repl:

(js/alert "Hi from shadow-cljs")
=> nil
(js/console.log "hi from the repl")
=> nil

which both return nil in the repl, but whose effects should be visible in the browser window. 

Step Ten - Render some reagent / react to the browser

If we go to the Reagent webpage we can see an example of a reagent component. We can copy this component, "simple-component" to our source file  core.cljs

(ns com.myname.core)

(defn simple-component []
  [:div
   [:p "I am a component"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])

But nothing shows up, why? If you have used React, you'll recall that first we need to create some html using jsx - in our case, instead we use reagent's hiccup-like syntax

But we also need to run the equivalent of ReactDOM.render(), passing it the jsx fragment we created, as well as passing it a reference to the DOM node where we want it to show up. In Reagent, it looks pretty similar - so after importing the relevant libraries, our core.cljs looks like this:

(ns com.ghufran.core
  (:require [reagent.core :as r]
            [reagent.dom :as rdom]))

(defn simple-component []
  [:div
   [:p "I am a component"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])

(defn ^:export run []
  (rdom/render [simple-component] (js/document.getElementById "app")))

Hmm, still not showing up in the browser. Recall that we in shadow-cljs.edn, we had to set the entrypoint for the program. We want the run function to run, so we change the relevant part of shadow-cljs.edn from 

:modules    {:core {:init-fn com.myname.core/main}}

to 

:modules    {:core {:init-fn com.myname.core/run}}

For changes in shadow-cljs.edn, you may need to refresh the page in order for the new configuration to load

At which point we should see:

Success! We can now start working our way through the reagent tutorial before moving on to re-frame...

[EDIT: After posting this, I found out that the hot reloading isn't working - oops! Thomas Heller's post is helpful - seen next post for getting hot reloading working!]

Getting started

I always liked the quote by Thomas Sowell:

From time to time, I get a letter from some aspiring young writer, asking about how to write or how to get published. My usual response is that the only way I know to become a good writer is to be a bad writer and keep on improving. 

I guess I should get started then...

I tried learning Reagent a few years ago, but found I had a lot of trouble decoding the errors without understanding Javascript and React. I spent the next couple of years doing a 'dual-MS' in Math and Statistics at Cal State East Bay, so didn't get a lot of time to code. (Btw, if anyone is trying to learn math / stats later in life like I did and you want advice, or just to chat, feel free to contact me at ghufran.syed at gmail)

I just graduated a couple of weeks ago, and I now have a lot more time to get back to coding (I also have a day job, which I'll talk about another time). So I decided to invest time in learning at least the basics of javascript. I subscribe to O'Reilly Learning, and found an awesome video course there by Andrew Mead, called The Modern Javascript Bootcamp (also available at Udemy). 

I then started The Complete React Developer Course (w/ Hooks and Redux) a few days ago, and I really like the approach so far - I previously tried the Udacity React nanodegree but only got halfway through due to getting killed with schoolwork around that time. That course was okay, but the Andrew Mead course is so much better! Each additional step is just small enough with enough explanation that the overall progress is fast, even though the whole course is around 40 hours of video. 

Now my aim has been to eventually use React in Clojurescript, using Reagent, before reaching the final goal of using re-frame (phew!). So after doing the first few lessons of the React course, I thought it might be worth trying to do the assignments in parallel, using clojurescript and reagent. That turned out to be a bit more tricky than I anticipated - and what led to me finally starting a blog, just to document what I found, in the hope that some other noob like me might find it helpful. Stay tuned, coming soon...