In a precedent post you can read the explanation about what is KLIPSE and what was the motivation to build it. In this post, you will find a basic tutorial to explain step by step how KLIPSE is build.
Final render of the tutorial:
Of course, the design of the app not the subject of the tutorial.
Tutorial Summary
1- Init Project
Create a new project directory, switch into it, add a project.clj
configuration file to it.
mkdir cljs-compiler
cd cljs-compiler
touch project.clj
(defproject cljs-compiler "0.1.0-SNAPSHOT"
:description "Eval clojurescript to javascript!"
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]]
:plugins [[lein-cljsbuild "1.1.2" :exclusions [[org.clojure/clojure]]]]
:cljsbuild {:builds
[{:id "dev"
:source-paths ["src"]
:compiler {
:main cljs-compiler.core
:asset-path "js"
:output-to "resources/public/js/main.js"
:output-dir "resources/public/js"}}]})
Create a resources/public/index.html
file with the following contents:
mkdir -p resources/public
touch resources/public/index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Eval clojurescript to javascript!</title>
</head>
<body>
<script src="js/main.js"></script>
</body>
</html>
Create a file src/cljs_compiler/core.cljs
with the following contents:
mkdir -p src/cljs_compiler
touch src/cljs_compiler/core.cljs
(ns cljs_compiler.core)
(enable-console-print!)
(println "Hello world!")
lein cljsbuild once dev
Now open the index.html
file in a chrome browser, open the console, great! you can see our “Hello world!” log.
2- figwheel
It is a good idea to use figwheel to make your devlopment more funny. Add figwheel plugin to your project.clj
and :figwheel true
configuration to your dev compilation configuration:
(defproject cljs-compiler "0.1.0-SNAPSHOT"
:description "Eval clojurescript to javascript!"
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]]
:plugins [[lein-figwheel "0.5.0-6"]
[lein-cljsbuild "1.1.2" :exclusions [[org.clojure/clojure]]]]
:cljsbuild {:builds
[{:id "dev"
:source-paths ["src"]
:figwheel true
:compiler {
:main cljs-compiler.core
:asset-path "js"
:output-to "resources/public/js/main.js"
:output-dir "resources/public/js"}}]})
Launch figwheel from the terminal:
lein figwheel
or
rlwrap lein figwheel
Open http://localhost:3449/
in chrome, open console, there is your “Hello world!” log. Now, update core.cljs
:
(ns cljs_compiler.core)
(enable-console-print!)
(defn hello [] (println "Hello world!"))
And in the terminal, in the figwheel session:
(in-ns 'cljs_compiler.core)
(hello)
;; repl => nil
;; chrome console => "Hello world!"
3- compiler functions
Add to dependencies the cljs.js
namespace.
Learn more about cljs.js
here.
(ns cljs_compiler.core
(:require
[cljs.js :as cljs]))
You’ll need a callback function as util to handle compilation errors.
(defn callback [{:keys [value error]}]
(let [status (if error :error :ok)
res (if error
(.. error -cause -message)
value)]
[status res]))
The function _compilation
will receive a clojurescript source as a string.
Return [:error “reason”] in case of error and [:ok “js-code”] in case of success.
(defn _compilation [s]
(cljs/compile-str
(cljs/empty-state)
s
callback))
The function _eval
will receive a clojurescript source as a string and evaluate it.
Return [:error “reason”] in case of error and [:ok “js-code”] in case of success.
(defn _eval [s]
(cljs/eval-str
(cljs/empty-state)
s
'test
{:eval cljs/js-eval}
callback))
The function _evaluation-js
return the jsonify _eval
result.
(defn _evaluation-js [s]
(let [[status res] (_eval s)]
[status (.stringify js/JSON res nil 4)]))
The function _evaluation-clj
return the stringify _eval
result.
(defn _evaluation-clj [s]
(let [[status res] (_eval s)]
[status (str res)]))
In this step you can test the compilation functions using the repl.
(in-ns 'cljs_compiler.core)
(_compilation "(+ 1 2"))
;; => [:ok "(2 + 2);\n"]
4- om.next
Om.next is a great client framework based on React. It wasn’t necessary to use it for this small app but it was a cute opportunity to discover it.
Add om.next dependencies to project.clj
{
;; ...
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]
[org.omcljs/om "1.0.0-alpha22"]]
;; ...
}
and to core.cljs
(ns cljs_compiler.core
(:require
[cljs.js :as cljs]
[goog.dom :as gdom]
[om.next :as om :refer-macros [defui]]
[om.dom :as dom]))
You can use an atom to store your local data
(defonce app-state (atom
{:input ""
:compilation ""
:evaluation-js ""
:evaluation-clj ""}))
a read function to read your local data
(defn read [{:keys [state]} key params]
{:value (get @state key "")})
a mutation multimethod to mutate your local data
(defmulti mutate om/dispatch)
(defmethod mutate 'input/save [{:keys [state]} _ {:keys [value]}]
{:action (fn []
(swap! state assoc :input value))})
(defmethod mutate 'cljs/compile [{:keys [state]} _ {:keys [value]}]
{:action (fn []
(swap! state update :compilation
(partial _compilation value)))})
(defmethod mutate 'js/eval [{:keys [state]} _ {:keys [value]}]
{:action (fn []
(swap! state update :evaluation-js
(partial _evaluation-js value)))})
(defmethod mutate 'clj/eval [{:keys [state]} _ {:keys [value]}]
{:action (fn []
(swap! state update :evaluation-clj
(partial _evaluation-clj value)))})
a parser to say om.next what is your read/mutate functions
(def parser (om/parser {:read read
:mutate mutate}))
a reconciler to merge the result of your mutations to your local data.
(def reconciler
(om/reconciler
{:state app-state
:parser parser}))
an om transact function, adding for convenience
(defn process-input [compiler s]
(om/transact! compiler
[(list 'input/save {:value s})
(list 'cljs/compile {:value s})
(list 'js/eval {:value s})
(list 'clj/eval {:value s})]))
and of course test all that using the repl:
(in-ns 'cljs_compiler.core)
(process-input reconciler "(+ 2 2)")
(parser {:state app-state} '[:input
:compilation
:evaluation-js
:evaluation-clj])
;; => {:input "(+ 2 2)",
;; :compilation [:ok "(2 + 2);\n"],
;; :evaluation-js [:ok "4"],
;; :evaluation-clj [:ok "4"]}
OK! now you need UI components: Build an om component that contains 4 textarea, one for input and one for each compile/eval results.
(defui CompilerUI
static om/IQuery
(query [this]
'[:compilation :evaluation-js :evaluation-clj])
Object
(render [this]
(as->
(om/props this) $
(dom/div nil
(input-ui this)
(compile-cljs-ui $)
(evaluate-clj-ui $)
(evaluate-js-ui $)))))
and the 4 textareas
(defn input-ui [reconciler]
(dom/section nil
(dom/textarea #js {:autoFocus true
:onChange #(process-input
reconciler
(.. % -target -value))})))
(defn compile-cljs-ui [{:keys [compilation]}]
(let [[status result] compilation]
(dom/section nil
(dom/textarea #js {:value result
:readOnly true}))))
(defn evaluate-clj-ui [{:keys [evaluation-clj]}]
(let [[status result] evaluation-clj]
(dom/section nil
(dom/textarea #js {:value result
:readOnly true}))))
(defn evaluate-js-ui [{:keys [evaluation-js]}]
(let [[status result] evaluation-js]
(dom/section nil
(dom/textarea #js {:value result
:readOnly true}))))
Go to http://localhost:3449/, you have an awesome clojurescript compiler !!! Now let’s go! fork it!