23

Say I wanted to factor out some common code between my client-side *.cljs and my server-side *.clj, e.g. various data structures and common operations, can I do that ? Does it make sense to do it ?

Hendekagon
  • 4,565
  • 2
  • 28
  • 43
  • 3
    It would indeed make sense, like in GWT you can also share Java code on the server and on the client. Would be very cool to hear the answer on this! – Michiel Borkent Oct 20 '11 at 07:49
  • the only way I can think of right now is to put the shared code in a server namespace & directory structure, then add lines to your compile code to copy the files to the client source directory, renaming as *.cljs - because the clojurescript compiler only looks for files named .cljs – Hendekagon Oct 20 '11 at 23:35

5 Answers5

16

I wrote the cljx Leiningen plugin specifically to handle Clojure/ClojureScript code sharing for a Clojure data visualization library. 95% of non-host-interop code looks the same, and cljx lets you automatically rewrite that last 5% by specifying rewrite rules using core.logic. Most of the time, though, it's simple symbol substitutions; clojure.lang.IFn in Clojure is just IFn in ClojureScript, for instance.

You can also use metadata to annotate forms to be included or excluded when code is generated for a specific platform.

Kevin L.
  • 1,663
  • 1
  • 16
  • 21
  • 1
    [cljx](https://github.com/lynaghk/cljx) has been adopted as the code-sharing tool of choice by [cljsbuild](https://github.com/emezeske/lein-cljsbuild) – Stephen Nelson Mar 04 '14 at 04:37
13

Update: as of clojure 1.7, check out Clojure reader conditionals or cljc. I've used cljc with great success to share a lot of code between server and browser very easily.

Great question! I've been thinking a lot about this as well lately and have written a few apps to experiment.

Here's my list of what types things you might want to share and pros/cons of each:

  • Most of my client cljs files contains code that manipulates the dom. So, it wouldn't make sense to share any of that with server
  • Most of the server side stuff deals with filesystem and database calls. I suppose you might want to call the database from the client (especially if you're using one of the no-sql db's that support javascript calls). But, even then, I feel like you should choose to either call db from client or call db from server and, therefore, it doesn't make much sense to share the db code either.
  • One area where sharing is definitely valuable is being able to share and pass clojure data structures (nested combinations of lists, vectors, sets, etc) between client and server. No need to convert to json (or xml) and back. For example, being able to pass hiccup-style representations of the dom back and forth is very convenient. In gwt, I've used gilead to share models between client and server. But, in clojure, you can simply pass data structures around, so there's really no need to share class definitions like in gwt.
  • One area that I feel I need to experiment more is sharing state between client and server. In my mind there are a few strategies: store state on client (single page ajax type applications) or store state on server (like legacy jsp apps) or a combo of both. Perhaps the code responsible for updating state (the atoms, refs, agents or whatever) could be shared and then state could be passed back and forth over request and response to keep the two tiers in synch? So far, simply writing server using REST best practices and then having state stored on client seems to work pretty well. But I could see how there might be benefits to sharing state between client and server.
  • I haven't needed to share Constants and/or Properties yet, but this might be something that would be good to reuse. If you put all your app's global constants in a clj file and then wrote a script to copy it over to cljs whenever you compiled the clojurescript, that should work fine, and might save a bit of duplication of code.

Hope these thoughts are useful, I'm very interested in what others have found so far!

Upgradingdave
  • 12,916
  • 10
  • 62
  • 72
  • Excellent points! I would like to unify the process by having my server code auto-build all my Clojurescript code by passing a subset of its functions into the compiler. I haven't read the compiler code yet, but it must be possible to feed it expressions directly without using .cljs files. – Hendekagon Oct 25 '11 at 23:26
  • Regarding datastruture sharing, the library `fetch` seems to be pretty neat! https://github.com/ibdknox/fetch – leontalbot Aug 06 '14 at 12:37
12

The new lein-cljsbuild plugin for Leiningen has built-in support for sharing pure Clojure code.

André Rüdiger
  • 91
  • 1
  • 11
Evan
  • 129
  • 1
  • 2
  • 2
    [cljsbuild](https://github.com/emezeske/lein-cljsbuild)'s built-in support (crossovers) has been deprecated in favour of [cljx](https://github.com/lynaghk/cljx), see [this](http://stackoverflow.com/a/10369314/1209442) answer. – Stephen Nelson Mar 04 '14 at 04:38
2

Wrote a quick bit of code to copy a subset of my server clojure code over to my clojurescript code, renaming as .cljs before building:

(ns clj-cljs.build
  (use
    [clojure.java.io]
  )
  (require
    [cljs.closure :as cljsc]
  )
)

(defn list-files [path]
 (.listFiles (as-file path))
)

(defn copy-file* [from to]
 ;(println " coping " from " to " to)
 (make-parents to)
 (copy from to)
)    

(defn rename [to-path common-path f]
 (str to-path common-path (.replaceAll (.getName f) ".clj" ".cljs"))
)

(defn clj-cljs* [files common-path to-path]
  (doseq [i (filter #(.endsWith (.getName %) ".clj") files)]
    (copy-file* i (file (rename to-path common-path i)))
  )
  (doseq [i (filter #(.isDirectory %) files)]
    (clj-cljs* (list-files i) (str common-path (.getName i) "/") to-path)
  )
)

(defn build [{:keys [common-path clj-path cljs-path js-path module-name]}]
  (clj-cljs* (list-files (str clj-path common-path)) common-path cljs-path)
  (cljsc/build
    cljs-path
    {
     :output-dir js-path
     :output-to (str js-path module-name ".js")
    }
  )
)

(defn build-default []
  (build
   {
    :clj-path "/home/user/projects/example/code/src/main/clojure/"
    :cljs-path "/home/user/projects/example/code/src/main/cljs/"
    :js-path "/home/user/projects/example/code/public/js/cljs/"
    :common-path "example/common/" ; the root of your common server-client code
    :module-name "example"
   }
  )
)
Hendekagon
  • 4,565
  • 2
  • 28
  • 43
0

This question predates cljc, but since I stumbled upon it, I thought I would mention Clojure reader conditionals.

nha
  • 17,623
  • 13
  • 87
  • 133