01 January 2016

Tags: elm clojurescript d3 lighttable

In an effort to making management of project dependencies in Elm projects a little easier, the Elm plugin for Light Table the elm-light has a few neat features up it’s sleave. Check out the demo below for a brief overview.

You can find the elm-light plugin here

Demo

Other relevant demos:

Short implementation summary

I’m just going to give a very brief overview of a few key pieces for how the features are implemented here. I might add a more detailed blog post if there is any interest for that in the future.

Package management

The package manager is just a thin wrapper around the elm-package executable.

(defn parse-json-file [json-file]
  (when (files/exists? json-file)
    (-> (->> (files/open-sync json-file)
             :content
             (.parse js/JSON))
        (js->clj :keywordize-keys true))))


(defn remove-pkg [path pkg]
  (let [pkg-file (files/join path "elm-package.json")]
    (-> (u/parse-json-file pkg-file)
        (update-in [:dependencies] (fn [deps]
                                     (-> (into {}
                                               (map (fn [[k v]]
                                                      [(u/nskw->name k) v]) deps))
                                         (dissoc pkg))))
        u/pretty-json
        ((partial files/save pkg-file)))))

To list, update and remove dependencies it parses (and updates) the project file for elm projects; elm-package.json. In addition it parses the exact-dependencies.json file for all resolved dependencies.

Working with json in ClojureScript feels almost seamless to working with native ClojureScript datastructures

View rendering

To render the package listing the plugin uses quiescent and react

(q/defcomponent PackageTable [props]
  (d/table
   {:className "package-table"}
   (d/thead
    {}
    (d/tr
     {}
     (d/th {} "Package")
     (d/th {} "Range")
     (d/th {} "Exact")
     (d/th {} "")))
   (apply d/tbody {}
          (map #(PackageRow (assoc %
                              :on-remove (:on-remove props)
                              :on-browse (:on-browse props)))
               (:packages props)))))

You can find a detailed blog post about some of the benefits of using react for view rendering in Light Table in Implementing a Clojure ns-browser in Light Table with React

Dependency autocompletion

Whan adding dependencies there is a handy autocompleter. This uses a json resource from http://package.elm-lang.org/.

(defn fetch-all-packages
  "Fetch all packages from package.elm-lang.org"
  [callback]
  (fetch/xhr (str "http://package.elm-lang.org/all-packages?date=" (.getTime (new js/Date)))
             {}
             (fn [data]
               (let [pkgs (js->clj (.parse js/JSON data) :keywordize-keys true)]
                 (callback pkgs)))))

Dependency graph

To implement the dependency graph d3 and dagreD3 is used. Both of these ships node-modules. Using node-modules from Light Table plugins is definetely not rocket science !

(def dagreD3 (js/require (files/join u/elm-plugin-dir "node_modules/dagre-d3")))
(def d3 (js/require (files/join u/elm-plugin-dir "node_modules/d3")))


defn create-graph [data]                                                         (1)
  (let [g (.setGraph (new dagreD3.graphlib.Graph)  #js {})]
    (doseq [x (:nodes data)]
      (.setNode g (dep-id x) (node-label x)))
    (doseq [x (:edges data)]
      (.setEdge g (:a x) (:b x) #js {:label (:label x)
                                     :style (when (:transitive x)
                                              "stroke-dasharray: 5, 5;")}))
    g))



(behavior ::on-render                                                           (2)
          :desc "Elm render dependencies"
          :triggers #{:elm.graph.render}
          :reaction (fn [this]
                      (let [svg (.select d3 "svg")
                            g (.select svg "g")
                            renderer (.render dagreD3)]
                        (renderer g (create-graph (:data @this)))
                        (init-zoom svg g)
                        (resize-graph this svg))))
1 The function to create the dependency graph. Helper functions omitted, but not much to it really
2 Light Table behavior that is responsible for rendering the graph

Credits

  • d3.js - Provides awesome graphing features

  • dagreD3 - Create Directed Acyclic Graphs in a breeze

comments powered by Disqus