Permalink
Browse files

The cljs-devmode has been improved. See the README.md for all changes.

  • Loading branch information...
1 parent 816f29a commit 5671b1a4f4283802bd1e66f24146968063d06768 @maxweber maxweber committed Jul 27, 2011
Showing with 266 additions and 70 deletions.
  1. +0 −42 README
  2. +137 −0 README.md
  3. +2 −1 project.clj
  4. +3 −0 src/cljs_devmode/core.clj
  5. +14 −0 src/cljs_devmode/ring_middleware.clj
  6. +74 −27 src/leiningen/cljs_devmode.clj
  7. +36 −0 src/leiningen/cljs_devmode_bootstrap.clj
View
42 README
@@ -1,42 +0,0 @@
-# clojurescript-devmode
-
-A development mode for ClojureScript.
-
-## Usage
-
-Follow the Quick Start Guide of ClojureScript: https://github.com/clojure/clojurescript/wiki/Quick-Start (the bootstrap step is required)
-
-After bootstrapping ClojureScript get cljs-devmode:
-git clone git@github.com:maxweber/cljs-devmode.git
-
-In the cljs-devmode folder invoke:
-lein cljs-devmode $CLOJURESCRIPT_HOME
-
-If you havn't set the $CLOJURESCRIPT_HOME environment variable yet, then use the full path to your ClojureScript installation.
-
-The cljs-devmode leiningen plugin copies all necessary jar files into the lib folder of $CLOJURESCRIPT_HOME to make this development mode work.
-
-To run the hello sample of ClojureScript in development mode do the following:
-- Start the ./script/repl in the $CLOJURESCRIPT_HOME folder.
-- Then invoke this lines on the REPL:
-
- (require '[cljs.closure :as cljsc])
- (defn compile-fn [] (cljsc/build "samples/hello/src"
- {:output-dir "samples/hello/out"
- :output-to "samples/hello/hello.js"}))
- (use 'cljs-devmode.core)
- (start-devmode "samples/hello" compile-fn)
-
-- This brings up a Jetty server on port 9090, so open http://localhost:9090/hello-dev.html to see the hello sample of Clojure Script.
-- On every request cljs-devmode checks, if any .cljs file inside samples/hello has changed. In the case of a change the above defined compile-fn is invoked.
-- So change the greeting message inside samples/hello/src/hello/core.cljs. When you refresh the http://localhost:9090/hello-dev.html you will see the new greeting message.
-
-## TODOs
-
-This is a really primitive prototype for a ClojureScript development mode. I only wrote it to get started with ClojureScript in one of my Clojure web applications. There should be a Ring middleware which forward the .js file request form my web application on localhost:8080 to the ClojureScript server on localhost:9090 transparently, so that there is no need to adapt the paths of the .js file, when you are in the ClojureScript development mode.
-
-## License
-
-Copyright (C) 2011 Maximilian Weber
-
-Distributed under the Eclipse Public License, the same as Clojure.
View
137 README.md
@@ -0,0 +1,137 @@
+# cljs-devmode
+
+A development mode for ClojureScript. It allows you to develop Clojure
+web applications in combination with ClojureScript seamlessly. You can
+develop your normal Clojure web application (with Ring and Compojure
+for example) and whenever you change a ClojureScript source file it is
+automatically recompiled and you can test the changes in your web
+browser.
+
+## Usage
+
+Follow the [Quick Start
+Guide](https://github.com/clojure/clojurescript/wiki/Quick-Start) of
+ClojureScript (the bootstrap step is required)
+
+After bootstrapping ClojureScript get cljs-devmode:
+
+ git clone git@github.com:maxweber/cljs-devmode.git
+
+In the cljs-devmode folder invoke:
+
+ lein cljs-devmode-bootstrap $CLOJURESCRIPT_HOME
+
+If you haven't set the $CLOJURESCRIPT_HOME environment variable yet,
+then use the full path to your ClojureScript installation.
+
+The cljs-devmode-bootstrap leiningen plugin copies all necessary jar
+files into the lib folder of $CLOJURESCRIPT_HOME to make this
+development mode work.
+
+### Start the ClojureScript compiler and cljs-devmode from the REPL
+
+To run the hello sample of ClojureScript in development mode do the
+following:
+
+* Start the ./script/repl in the $CLOJURESCRIPT_HOME folder.
+* Then invoke this lines on the REPL:
+
+ (require '[cljs.closure :as cljsc])
+ (defn compile-fn []
+ (cljsc/build "samples/hello/src"
+ {:output-dir "samples/hello/out"
+ :output-to "samples/hello/hello.js"}))
+ (use 'cljs-devmode.core)
+ (start-devmode "samples/hello" compile-fn)
+
+* This brings up a Jetty server on port 9090, so open
+ http://localhost:9090/hello-dev.html to see the hello sample of
+ ClojureScript. On every request cljs-devmode checks, if any .cljs
+ file inside samples/hello has changed. In the case of a change the
+ above defined compile-fn is invoked.
+* So change the greeting message inside
+ samples/hello/src/hello/core.cljs.
+* When you refresh the http://localhost:9090/hello-dev.html you will
+ see the new greeting message.
+
+### cljs-devmode supports to automate the above steps
+
+Take a look at the
+[cljs-devmode-example](https://github.com/maxweber/cljs-devmode-example). In
+the project folder of cljs-devmode-example you can invoke:
+
+ lein cljs-devmode $CLOJURESCRIPT_HOME
+
+This generates a cljs-devmode.sh file in the project folder. The
+generated shell script starts the Clojure REPL in the
+$CLOJURESCRIPT_HOME folder. Furthermore 'lein cljs-devmode' has also
+generated a Clojure script, which is a string inside the shell script
+and is passed to the Clojure REPL process for evaluation. This Clojure
+script does the same as the script in the previous section. But the
+paths are adapted to the current project folder (here the
+cljs-devmode-example project). So if you execute the cljs-devmode.sh
+script:
+
+ chmod 755 cljs-devmode.sh
+ ./cljs-devmode
+
+Then the ClojureScript compiler and the cljs-devmode are started
+automatically.
+
+The cljs-devmode-example also demonstrates how to use the
+wrap-cljs-forward Ring middleware. This middleware takes care that all
+request to /cljs/* (for example) are forwarded to localhost:9090,
+where the cljs-devmode is running. So now you can develop your normal
+Clojure web application and let ClojureScript do the JavaScript
+part. Whenever you modify a .cljs file the whole ClojureScript project
+is automatically recompiled and you can test the changes immediately
+in your web browser.
+
+'lein cljs-devmode' has some defaults / conventions for the folder
+structure of the project. But all defaults can be changed through a
+:cljs-devmode entry in the project.clj. These would be the default
+settings:
+
+ (defproject cljs-devmode-example
+ ...
+ :cljs-devmode {:dir "PROJECT_HOME/cljs"
+ :src-dir "PROJECT_HOME/cljs/src"
+ :output-dir "PROJECT_HOME/cljs/out")
+ :output-to "PROJECT_HOME/cljs/cljs-devmode-example.js")}
+
+You do not have to specifiy the :cljs-devmode entry inside your
+project.clj as long as the folder structure of your project follows
+the conventions. Take a look at the folder structure of the
+cljs-devmode-example. The whole ClojureScript project is in the cljs/
+subfolder. The ClojureScript project itself adheres to the folder
+structure of the ClojureScript samples. The :cljs-devmode entry can
+also be leveraged to pass additional parameters to the ClojureScript
+compiler. As you can see in the above example the :output-dir and
+:output-to entries are settings for the ClojureScript compiler, every
+addtional map entry will also be passed to the ClojureScript compiler.
+
+
+## TODOs
+
+This is a really primitive prototype for a ClojureScript development
+mode. I only wrote it to get started with ClojureScript in one of my
+Clojure web applications.
+There are many things which can be improved. For example the
+generation of the .sh shell script is a little bit nasty ;-) You can
+also let the JVM process of leiningen start the ClojureScript
+compiler and the devmode via:
+
+ lein cljs-devmode $CLOJURESCRIPT_HOME start
+
+But then you will not see the output (stdout and stderr) of the
+ClojureScript compiler and therefore you cannot read the helpful error
+messages of the ClojureScript compiler. Nevertheless there is no
+automated way yet, to do a switch to "production mode" (e.g. invoke
+the Google Closure Compiler in advanced mode and move the js file to
+the public/cljs folder).
+
+## License
+
+Copyright (C) 2011 Maximilian Weber
+
+Distributed under the Eclipse Public License, the same as Clojure.
View
@@ -1,5 +1,6 @@
(defproject cljs-devmode "0.1.0-SNAPSHOT"
:description ""
- :dependencies [[org.clojure/clojure "1.2.1"]]
+ :dependencies [[org.clojure/clojure "1.2.1"]
+ [clj-http "0.1.3"]]
:dev-dependencies [[compojure "0.6.5"]
[ring/ring-jetty-adapter "0.3.11"]])
@@ -57,6 +57,9 @@
(def server nil)
+(defn devmode [dir compile-fn]
+ (run-jetty (app dir compile-fn) {:port 9090}))
+
(defn start-devmode [dir compile-fn]
(alter-var-root (var server)
(fn [v]
@@ -0,0 +1,14 @@
+(ns cljs-devmode.ring-middleware
+ (:require [clj-http.client :as http]))
+
+(defn wrap-cljs-forward
+ ([handler prefix server-url]
+ (fn [request]
+ (let [{:keys [uri request-method]} request]
+ (if (and (= request-method :get) uri (.startsWith uri prefix))
+ (let [url (str server-url (.replaceFirst uri prefix ""))
+ headers (:headers request)]
+ (http/request {:method :get :url url
+ :headers headers}))
+ (handler request)))))
+ ([handler prefix] (wrap-cljs-forward handler prefix "http://localhost:9090")))
@@ -1,30 +1,77 @@
(ns leiningen.cljs-devmode
- (:use clojure.java.io
- leiningen.deps
- leiningen.jar))
-
-(defn dest-dir [clojurescript-path]
- (file (file clojurescript-path) "lib/"))
-
-(defn- copy-deps [dir]
- (let [libs (file "lib/dev")]
- (doall (map #(let [name (.getName %)
- dest-file (file dir name)]
- (copy % dest-file))
- (remove #(= "clojure-1.2.1.jar" (.getName %)) (.listFiles libs))))))
-
-(defn- build-and-copy-jar [project dest-dir]
- (let [jar-name (get-jar-filename project)
- jar-file (file jar-name)]
- (jar project)
- (copy jar-file (file dest-dir (.getName jar-file)))))
+ (:use clojure.java.io)
+ (:import [java.lang Process Runtime]
+ java.io.StringReader))
+
+(def cljs-command
+ ["java" "-server" "-Xmx2G" "-Xms2G" "-Xmn256m"
+ "-cp" "lib/*:src/clj:src/cljs"
+ "clojure.main" "-e"])
+
+(defn- build-cljs-invoke [script]
+ (conj cljs-command (apply str script)))
+
+;TODO: How can the stdout of the other JVM be redirected to the stdout
+;of this JVM (clojure.java.shell doesn't support to get the
+;InputStream from the inner java.lang.Process directly yet, so we have
+;to use Runtime.exec here)
+(defn- start-clojurescript [clojurescript-home script]
+ (let [cljs-invoke (build-cljs-invoke script)
+ runtime (Runtime/getRuntime)
+ process (.exec runtime ^"[Ljava.lang.String;" (into-array cljs-invoke)
+ ^"[Ljava.lang.String;" (into-array String [])
+ (as-file clojurescript-home))]
+ (with-open [err (.getErrorStream process)]
+ (slurp err))))
+
+(defn generate-script [clojurescript-home script]
+ (str "#!/bin/sh\n"
+ "cd " clojurescript-home "\n"
+ (apply str (interpose " " cljs-command))
+ " '"
+ (apply str script)
+ "'"))
+
+(def cljsc-bin-path "bin/cljsc")
+
+(defn is-clojurescript-home? [clojurescript-home]
+ (let [cljsc-bin (file (file clojurescript-home) cljsc-bin-path)]
+ (.exists cljsc-bin)))
+
+(defn check-clojurescript-home-param [clojurescript-home]
+ (if-not clojurescript-home
+ "Error: Please provide the ClojureScript home path as first argument."
+ (when-not (is-clojurescript-home? clojurescript-home)
+ (str "Error: The given ClojureScript home path '"
+ clojurescript-home
+ "' is not a ClojureScript installation "
+ "(" cljsc-bin-path " is missing.)"))))
(defn cljs-devmode
- [project & [clojurescript-path]]
- (deps project)
- (let [dest (dest-dir clojurescript-path)]
- (copy-deps dest)
- (build-and-copy-jar project dest)))
-
-(comment (cljsc/build "samples/hello/src" {:output-dir "samples/hello/out"
- :output-to "samples/hello/hello.js"}))
+ [project & [clojurescript-home mode]]
+ (if-let [error (check-clojurescript-home-param clojurescript-home)]
+ (println error)
+ (if (and mode (not (= mode "start")))
+ (println "Error: The only supported mode at the moment is 'start', which starts the ClojureScript compiler process in another JVM from this JVM via Runtime/exec \"java.exe ...\" (you will not see the stdout or stderr of the ClojureScript compiler process).")
+ (let [root-dir (:root project)
+ dir (str root-dir "/cljs")
+ project-name (:name project)
+ defaults {:dir dir
+ :src-dir (str dir "/src")
+ :output-dir (str dir "/out")
+ :output-to (str dir "/" project-name ".js")}
+ cljs-devmode-opts (:cljs-devmode project {})
+ options (merge defaults cljs-devmode-opts)
+ cljs-build-opts (dissoc defaults :dir :src-dir)
+ dir (:dir options)
+ src-dir (:src-dir options)
+ script `((require 'cljs.closure)
+ (defn user/compile-fn [] (cljs.closure/build ~src-dir
+ ~cljs-build-opts))
+ (require 'cljs-devmode.core)
+ (cljs-devmode.core/start-devmode ~dir user/compile-fn))]
+ (if (= mode "start")
+ (do
+ (println "cljs-devmode started. Options: " options)
+ (println (start-clojurescript clojurescript-home script)))
+ (spit "cljs-devmode.sh" (generate-script clojurescript-home script)))))))
@@ -0,0 +1,36 @@
+(ns leiningen.cljs-devmode-bootstrap
+ (:use clojure.java.io
+ leiningen.deps
+ leiningen.jar
+ [leiningen.cljs-devmode :only [check-clojurescript-home-param]]))
+
+(defn- dest-dir [clojurescript-path]
+ (file (file clojurescript-path) "lib/"))
+
+(defn- copy-deps [dir]
+ (let [libs (file "lib/dev")]
+ (doall (map #(let [name (.getName %)
+ dest-file (file dir name)]
+ (copy % dest-file))
+ (remove #(= "clojure-1.2.1.jar" (.getName %)) (.listFiles libs))))))
+
+(defn- build-and-copy-jar [project dest-dir]
+ (let [jar-name (get-jar-filename project)
+ jar-file (file jar-name)]
+ (jar project)
+ (copy jar-file (file dest-dir (.getName jar-file)))))
+
+(defn cljs-devmode-bootstrap
+ [project & [clojurescript-path]]
+ (if-let [error (check-clojurescript-home-param clojurescript-path)]
+ (println error)
+ (if-not (= (:name project) "cljs-devmode")
+ (println "Error: cljs-devmode-bootstrap must be invoked in the root folder of the cljs-devmode project (get the sources from GitHub: https://github.com/maxweber/cljs-devmode)")
+ (do
+ (deps project)
+ (let [dest (dest-dir clojurescript-path)]
+ (copy-deps dest)
+ (build-and-copy-jar project dest))))))
+
+(comment (cljsc/build "samples/hello/src" {:output-dir "samples/hello/out"
+ :output-to "samples/hello/hello.js"}))

0 comments on commit 5671b1a

Please sign in to comment.