воскресенье, 15 мая 2016 г.

Using an external JavaScript library in your ClojureScript application

My recent playing with ClojureScript and Om was a great trip into the field of functional front-end programming. However it was disrupted by a small yet irritating problem. I wanted to consume an external JavaScript library - the client for Trello API - in my Om application and call a method of the object defined there. It all worked fine while I was developing the thing, but the first attempt to deploy it to the production server resulted in my application not working and spitting errors like "Trello.rf is not a function".

This problem is caused by ClojureScript munging names during compilation, which is disabled - as well as other optimizations - in the default dev profile. Because the idea of using an external JS library is not something completely stupid, there are some ways to avoid munging external names - outlined here and here. The approach with the externs file is quite easy to adopt, but it took me some experimenting to make it work, so I will describe it step-by-step here to have a reference in future.

Inputs first. I have a ClojureScript Om application enabled by cljsbuild 1.1.3. The compiled JavaScript is served from the /resources/public/js folder (or from somewhere under it). My application uses the Trello client JS library to make one call to the Trello.authorize method. The Trello library has to be brought in through the index.html like this:

<script src="https://api.trello.com/1/client.js?key=myappkey"></script>
<script src="js/compiled/trellodonelist.js" type="text/javascript"></script>

To prevent ClojureScript compiler from turning the names defined in the external library into strange symbols we only need to introduce an externs file and supply its name to the compiler. All this is done in three simple steps:

1. Create a plain JavaScript file and store it in a place where the compiler will be able to find it. I chose /resources/public/js/externs.js (the name doesn't matter that much).

2. In the externs.js file touch all the names from external libraries that you plan to refer in your app. For me that looked like:

var Trello = {};
Trello.authorize = function() {};

3. In your html refer the externs file along with the external library and your compiled ClojureScript:

<script src="js/externs.js" type="text/javascript"></script>
<script src="https://api.trello.com/1/client.js?key=myappkey"></script>
<script src="js/compiled/trellodonelist.js" type="text/javascript"></script>

4. In the project.clj file add the path to the externs file in a vector under [:cljsbuild :builds :app :compiler :externs]. You aim for something like this:

(defproject trellodonelist "0.1.0-SNAPSHOT"
  ; various meaningful things
  
  :cljsbuild 
   {:builds
    {:app
      {:source-paths ["src/cljs"]
       :compiler {:main trellodonelist.core
                  :asset-path "js/compiled/out"
                  :output-to "resources/public/js/compiled/trellodonelist.js"
                  :output-dir "resources/public/js/compiled/out"
                  :source-map-timestamp true

                  ; this one
                  :externs ["resources/public/js/externs.js"]}}}}

  ; other meaningful things
)

I had to play a bit with various locations of externs.js and missed one thing or another, but the above setup finally did the trick. Now, even if the ClojureScript is compiled with all the optimizations, the compiler is aware of the names brought in from the external libs and won't change them.

There are some other ways to make external names work and I recommend to read the articles mentioned above to get a better understanding of the reasons behind the issue and a wider set of alternative solutions. For my simple case the self-made externs file seemed the easiest and the most concise approach - maybe it will suit you as well. Happy coding!

Комментариев нет:

Отправить комментарий