GSoC: Week 6 - UMD pattern
I have been accepted to this years Google's Summer of Code to work on ClojureScript. The goal of my project is to improve the integration of ClojureScript with the existing JavaScript ecosystem. I will post weekly updates about the progress of the project here. For more details about the project have a look at the ClojureScript GitHub wiki page.
In this post I will write a bit about the midterm evaluations and also about the UMD pattern.
Midterm evaluations
The Google Summer of Code midterm evaluations were due last Friday (July 3rd), meaning that every student and every mentor had to submit an evaluation about the current progress of their project and students had to evaluate their mentor(s) and vice versa. In almost all cases, students will never see the mentor's evaluation and a mentor will never see the student's evaluation. Only the GSoC program administrators have access to all the evaluation data. Students will only receive an email notifying them if they have passed or failed the midterm evaluation. So, it is recommended that students talk directly to their mentor and ask about feedback. My mentor, David Nolen, and I have been trying to do this throughout the project, and I am happy to announce that I have passed my midterm evaluation :).
UMD
To test the CommonJS and AMD support, I checked CLJSJS for JavaScript libraries and tried to include them into a ClojureScript project using the CommonJS or AMD module compiler option. For example, I tried to include d3, hammerjs, Chance and CodeMirror. The following is a small example which uses d3 to change the color of a circle depending on the value given in an input field.
;; build.clj
(require 'cljs.build.api)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:output-dir "out"
:foreign-libs [{:file "libs/d3.js"
:provides ["d3"]
:module-type :commonjs}]
:source-map "out/map.js"
:optimizations :none})
;; core.cljs
(ns hello-world.core
(:require [d3 :as d3]))
(enable-console-print!)
(def center #js {:margin-left "auto"
:margin-right "auto"
:display "block"})
(def body (d3/select "body"))
(def p (-> body
(.append "p")
(.text "Change the color of the circle: ")
(.attr "align" "center")))
(def color-input (-> p
(.append "input")
(.attr #js {:type "text"
:id "color"})))
(def svg (-> body
(.append "svg")
(.attr #js {:width "300px"
:height "300px"})
(.style center)))
(def circle (-> svg
(.append "circle")
(.attr #js {:cx "50%"
:cy "50%"
:r 120})
(.style #js {:fill "black"})))
(defn update-color [color]
(-> circle
.transition
(.delay 250)
(.duration 500)
(.style #js {:fill color})))
(.on color-input "input" #(-> (.-value (.getElementById js/document "color"))
update-color))
Unfortunately, when we compile our example and then try to run it in the browser, we get the following error:
Let's have a look at the source code.
It seems that we get an error when trying to access d3. The compiled JavaScript for this looks as follows.
hello_world.core.body = module$libs$d3.select.call(null,"body");
This looks good so far. Let's have a closer look at the module$libs$d3
.
Oh, what happened there? Why is module$libs$d3
an empty object? If we have a look at the code of the d3 module which was converted with the Google Closure compiler, we can see the following.
if (typeof define === "function" && define.amd) {
define(d3);
} else if (typeof module === "object" && module$libs$d3) {
module$libs$d3 = d3
}
The code is checking if module
is an object and only then assigns whatever the variable d3 holds to our new namespace module$libs$d3
. Unfortunately, module
will not be defined in the browser so our module$libs$d3
namespace will remain empty. Here is the original code of the d3 library.if (typeof define === "function" && define.amd) {
define(d3);
} else if (typeof module === "object" && module.exports) {
module.exports = d3;
}
This is a common JavaScript idiom, which is called UMD (Universal Module Definition). The goal of the UMD pattern is to support AMD as well as CommonJS, since both module specifications have become quite popular over time. Not just d3, but many other libraries use the UMD pattern, in fact all four libraries mentioned above use some variant of the UMD pattern. Unfortunately, this also means, that we can't include any of those libraries using the new module-type
compiler option. For this to work, we will hopefully submit another pull request to the Google Closure compiler soon with some modifications to the ProcessCommonJSModules
class.