2

KLIPSE: how we built it?

 3 years ago
source link: https://blog.klipse.tech/clojure/2016/03/21/klipse-tutorial.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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:

KLIPSE Screenshot 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
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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK