Skip to content

Commit

Permalink
Adding crossover support and examples.
Browse files Browse the repository at this point in the history
  • Loading branch information
Evan Mezeske committed Dec 31, 2011
1 parent 4128668 commit 91e8506
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 24 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -3,3 +3,7 @@ pom.xml
lib
classes/
lein-cljsbuild-*.*.*
example-projects/*/resources
example-projects/*/classes
example-projects/*/lib
example-projects/*/src-cljs/example/crossover
153 changes: 149 additions & 4 deletions README.md
Expand Up @@ -7,9 +7,12 @@ that your project can depend on a specific version of lein-cljsbuild, fetch
it via `lein deps`, and you don't have to install any special executables into
your `PATH`.

Also, this plugin has built-in support for seamlessly sharing code between
your Clojure server-side project and your ClojureScript client-side project.

[1]: https://github.com/ibdknox/cljs-watch

## Installation
## Installation

You can install the plugin via lein:

Expand All @@ -19,21 +22,26 @@ Or by adding lein-cljs to your `project.clj` file in the `:dev-dependencies`
section:

```clojure
(defproject my-thingie "1.2.3"
(defproject lein-cljsbuild-example "1.2.3"
:dev-dependencies [[emezeske/lein-cljsbuild "0.0.1"]])
```

Make sure you pull down the jar file:

$ lein deps

## Just Give Me a Damned Example Already!

See the `example-projects` directory for a couple of simple examples
of how to use lein-cljsbuild.

## Configuration

The lein-cljsbuild configuration is specified under the `:cljsbuild` section
of your `project.clj` file:
of your `project.clj` file. A simple project might look like this:

```clojure
(defproject my-thingie "1.2.3"
(defproject lein-cljsbuild-example "1.2.3"
:dev-dependencies [[emezeske/lein-cljsbuild "0.0.1"]]
:cljsbuild {
; The path to the top-level ClojureScript source directory:
Expand All @@ -59,6 +67,143 @@ avoids the time-consuming JVM startup for each build:

$ lein cljsbuild auto

## Sharing Code Between Clojure and ClojureScript

Sharing code with lein-cljsbuild is accomplished via the configuration
of "crossovers". A crossover specifies a directory in your Clojure project,
the content of which should be copied into your ClojureScript project. The
files in the Clojure directory will be monitored and copied over when they are
modified. Of course, remember that since the files will be used by both Clojure
and ClojureScript, they will need to only use the subset of features provided by
both languages.

Assuming that your top-level directory structure looked something like this:

<pre>
├── src-clj
│   └── example
│   ├── core.clj
│   ├── something.clj
│   └── crossover
│      ├── some_stuff.clj
│      └── some_other_stuff.clj
└── src-cljs
   └── example
   ├── core.cljs
   ├── whatever.cljs
   └── util.cljs
</pre>

And your `project.clj` file looked like this:

```clojure
(defproject lein-cljsbuild-example "1.2.3"
:dev-dependencies [[emezeske/lein-cljsbuild "0.0.1"]]
:source-path "src-clj"
:cljsbuild {
:source-dir "src-cljs"
:output-file "war/javascripts/main.js"
:optimizations :whitespace
:pretty-print true
; Each entry in the :crossovers vector describes a directory
; containing Clojure code that is meant to be used with the
; ClojureScript code as well. Files in :from-dir are copied
; into :to-dir whenever they are modified.
:crossovers [{:from-dir "src-clj/example/crossover"
:to-dir "src-cljs/example/crossover"}]})
```

Then lein-cljsbuild would copy files from `src-clj/example/crossover`
to `src-cljs/example/crossover`, and you'd end up with this:

<pre>
├── src-clj
│   └── example
│   ├── a_file.clj
│   ├── core.clj
│   └── crossover
│      ├── some_stuff.clj
│      └── some_other_stuff.clj
└── src-cljs
   └── example
   ├── a_different_file.cljs
   ├── crossover
   │   ├── some_stuff.cljs
   │   └── some_other_stuff.cljs
   ├── whatever.cljs
   └── util.cljs
</pre>

With this setup, you would probably want to add `src-cljs/example/crossover`
to your `.gitignore` file (or equivalent), as its contents are updated automatically
by lein-cljsbuild.

## Sharing Macros Between Clojure and ClojureScript

In ClojureScript, macros are still written in Clojure, and can not be written
in the same file as actual ClojureScript code. Also, to use them in a ClojureScript
namespace, they must be required via `:require-macros` rather than the usual `:require`.

This makes using the crossover feature to share macros between Clojure and ClojureScript
a bit difficult, but lein-cljsbuild has some special constructs to make it possible.

Three things need to be done to use lein-cljsbuild to share macros.

### 1. Keep Macros in Separate Files

These examples assume that your project uses the `src-clj/example/crossover`
directory, and that all of the macros are in a file called
`src-clj/example/crossover/macros.clj`.

### 2. Tell lein-cljsbuild Which Files Contain Macros

Add this magical comment to any crossover files that contain macros:

```clojure
;*CLJSBUILD-MACRO-FILE*;
```

This tells lein-cljsbuild to keep the `.clj` file extension when copying
the files into the ClojureScript directory, instead of changing it to `.cljs`
as usual.

### 3. Use Black Magic to Require Macros Specially

In any crossover Clojure file, lein-cljsbuild will automatically erase the
following string (if it appears):

```clojure
;*CLJSBUILD-REMOVE*;
```

This magic can be used to generate a `ns` statement that will work in both
Clojure and ClojureScript:

```clojure
(ns example.crossover.some_stuff
(:require;*CLJSBUILD-REMOVE*;-macros
[example.crossover.macros :as macros]))
```

Thus, after removing comments, Clojure will see:

```clojure
(ns example.crossover.some_stuff
(:require
[example.crossover.macros :as macros]))
```

However, lein-cljsbuild will remove the `;*CLJSBUILD-REMOVE*;` string entirely,
before copying the file. Thus, ClojureScript will see:

```clojure
(ns example.crossover.some_stuff
(:require-macros
[example.crossover.macros :as macros]))
```

And thus the macros can be shared.

## License

Source Copyright © Evan Mezeske, 2011.
Expand Down
18 changes: 18 additions & 0 deletions example-projects/advanced/README.md
@@ -0,0 +1,18 @@
This is an example web application that uses [Ring][1], [Compojure][2],
and [lein-cljsbuild][3]. It demonstrates the use of lein-cljsbuild to
build ClojureScript into JavaScript. It also shows how to share code
between Clojure and ClojureScript, including macros.

To play around with this example project, you will first need
[Leiningen][4] installed.

Set up and start the server like this:

lein deps
lein cljsbuild once
lein ring server

[1]: https://github.com/mmcgrana/ring
[2]: https://github.com/weavejester/compojure
[3]: https://github.com/emezeske/lein-cljsbuild
[4]: https://github.com/technomancy/leiningen
15 changes: 15 additions & 0 deletions example-projects/advanced/project.clj
@@ -0,0 +1,15 @@
(defproject cljsbuild-example-advanced "0.0.1"
:description "An advanced example of how to use lein-cljsbuild"
:source-path "src-clj"
:dependencies [[org.clojure/clojure "1.3.0"]
[compojure "0.6.5"]
[hiccup "0.3.7"]]
:dev-dependencies [[emezeske/lein-cljsbuild "0.0.1"]
[lein-ring "0.5.0"]]
:cljsbuild {:source-dir "src-cljs"
:crossovers [{:from-dir "src-clj/example/crossover"
:to-dir "src-cljs/example/crossover"}]
:output-file "resources/public/js/main.js"
:optimizations :whitespace
:pretty-print true}
:ring {:handler example.routes/app})
@@ -0,0 +1,6 @@
;*CLJSBUILD-MACRO-FILE*;

(ns example.crossover.macros)

(defmacro reverse-eval [form]
(reverse form))
@@ -0,0 +1,7 @@
(ns example.crossover.shared
(:require;*CLJSBUILD-REMOVE*;-macros
[example.crossover.macros :as macros]))

(defn make-example-text []
(macros/reverse-eval
("code" "shared " "from the " "Hello " str)))
16 changes: 16 additions & 0 deletions example-projects/advanced/src-clj/example/routes.clj
@@ -0,0 +1,16 @@
(ns example.routes
(:use compojure.core
example.views
[hiccup.middleware :only (wrap-base-url)])
(:require [compojure.route :as route]
[compojure.handler :as handler]
[compojure.response :as response]))

(defroutes main-routes
(GET "/" [] (index-page))
(route/resources "/")
(route/not-found "Page not found"))

(def app
(-> (handler/site main-routes)
(wrap-base-url)))
13 changes: 13 additions & 0 deletions example-projects/advanced/src-clj/example/views.clj
@@ -0,0 +1,13 @@
(ns example.views
(:require
[example.crossover.shared :as shared])
(:use
[hiccup core page-helpers]))

(defn index-page []
(html5
[:head
[:title (shared/make-example-text)]
(include-js "/js/main.js")]
[:body
[:h1 (shared/make-example-text)]]))
5 changes: 5 additions & 0 deletions example-projects/advanced/src-cljs/example/hello.cljs
@@ -0,0 +1,5 @@
(ns example.hello
(:require
[example.crossover.shared :as shared]))

(js/alert (shared/make-example-text))
17 changes: 17 additions & 0 deletions example-projects/simple/README.md
@@ -0,0 +1,17 @@
This is an example web application that uses [Ring][1], [Compojure][2],
and [lein-cljsbuild][3]. It demonstrates the use of lein-cljsbuild to
build ClojureScript into JavaScript.

To play around with this example project, you will first need
[Leiningen][4] installed.

Set up and start the server like this:

lein deps
lein cljsbuild once
lein ring server

[1]: https://github.com/mmcgrana/ring
[2]: https://github.com/weavejester/compojure
[3]: https://github.com/emezeske/lein-cljsbuild
[4]: https://github.com/technomancy/leiningen
13 changes: 13 additions & 0 deletions example-projects/simple/project.clj
@@ -0,0 +1,13 @@
(defproject cljsbuild-example-simple "0.0.1"
:description "A simple example of how to use lein-cljsbuild"
:source-path "src-clj"
:dependencies [[org.clojure/clojure "1.3.0"]
[compojure "0.6.5"]
[hiccup "0.3.7"]]
:dev-dependencies [[emezeske/lein-cljsbuild "0.0.1"]
[lein-ring "0.5.0"]]
:cljsbuild {:source-dir "src-cljs"
:output-file "resources/public/js/main.js"
:optimizations :whitespace
:pretty-print true}
:ring {:handler example.routes/app})
16 changes: 16 additions & 0 deletions example-projects/simple/src-clj/example/routes.clj
@@ -0,0 +1,16 @@
(ns example.routes
(:use compojure.core
example.views
[hiccup.middleware :only (wrap-base-url)])
(:require [compojure.route :as route]
[compojure.handler :as handler]
[compojure.response :as response]))

(defroutes main-routes
(GET "/" [] (index-page))
(route/resources "/")
(route/not-found "Page not found"))

(def app
(-> (handler/site main-routes)
(wrap-base-url)))
10 changes: 10 additions & 0 deletions example-projects/simple/src-clj/example/views.clj
@@ -0,0 +1,10 @@
(ns example.views
(:use [hiccup core page-helpers]))

(defn index-page []
(html5
[:head
[:title "Hello World"]
(include-js "/js/main.js")]
[:body
[:h1 "Hello World"]]))
3 changes: 3 additions & 0 deletions example-projects/simple/src-cljs/example/hello.cljs
@@ -0,0 +1,3 @@
(ns example.hello)

(js/alert "Hello from ClojureScript!")

0 comments on commit 91e8506

Please sign in to comment.