-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathindex.html
370 lines (227 loc) · 22.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<!DOCTYPE html><html><head><title>README.md - Github Flavored Markdown Server</title><link rel="stylesheet" href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github-deb21d3762619a9bb179b6ce5dd37b470b8c60bb.css"><link rel="stylesheet" href="https://a248.e.akamai.net/assets.github.com/stylesheets/bundles/github2-9372e88595d418680667f64527f4624a6f89251e.css"><script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<style type="text/css">
#content{padding:50px 0}
#content{width:920px;margin:0 auto}
</style></head><body><div id="content"><div id="readme" class="announce instapaper_body md"><span class="name"><span class="icon"></span>README.md</span><div class="markdown-body"><p><a href="http://travis-ci.org/michaelsbradleyjr/node-clojurescript"><img src="https://secure.travis-ci.org/michaelsbradleyjr/node-clojurescript.png?branch=master" alt="Build Status" title="" /></a></p>
<h1>node-clojurescript</h1>
<p><a href="https://github.com/michaelsbradleyjr/node-clojurescript">node-clojurescript</a> aims to provide seamless integration between <a href="http://nodejs.org/">NodeJS</a> and <a href="https://github.com/clojure/clojurescript">ClojureScript</a>. This is a young project, started in May 2012, it's under active development and welcomes participation by the NodeJS and <a href="http://clojure.org">Clojure</a> communities.</p>
<p>Packages are available on the <a href="http://npmjs.org/">npm</a> registry: <a href="http://search.npmjs.org/#/clojure-script">clojure-script</a>.</p>
<h2>Description</h2>
<p>The ClojureScript library ships with some basic mechanisms for creating compiled scripts suitable for use with NodeJS. But more is possible, and this library aims to make compiling, loading and running <code>.cljs</code> scripts a breeze, in the same manner that <a href="http://coffeescript.org">CoffeeScript</a> <code>.coffee</code> files can be used transparently in the development of NodeJS-backed applications.</p>
<h2>Quick Start</h2>
<p>Want to get started? There are some <a href="#prerequisites">prerequisites</a>, but if you'd prefer to trouble with those later:</p>
<div class='highlight'><pre><code>$ npm install -g clojure-script
</code></pre></div>
<p>If you get an error message from npm, it means you need to review the <em><a href="#prerequisites">Prerequisites</a></em> section of this README.<br />Let's continue...</p>
<div class='highlight'><pre><code>$ touch hello.cljs && vim hello.cljs
</code></pre></div>
<p>Now paste in something like:</p>
<div class='highlight'><pre><code>(ns hello
(:require [cljs.nodejs :as nodejs]))
(defn greet [n]
(println (str "Hello, " n)))
(nodejs/next-tick
(fn []
(greet "World!")))
</code></pre></div>
<p>Save it, and leave the editor open. In another terminal, navigate to the directory where you created <code>hello.cljs</code> and do:</p>
<div class='highlight'><pre><code>$ ncljsc hello.cljs
</code></pre></div>
<p>When you invoke <code>ncljsc</code>, it fires up the JVM, the ClojureScript compiler and the <a href="https://developers.google.com/closure/compiler">Google Closure Compiler</a>. This means the compilation will seem <em>slow</em>, even <em>really slow</em> (10+ seconds), especially if you're used to the sub-second compile times of CoffeeScript. That's expected, and the issue will be revisited in the <em><a href="#faster-faster">Faster, faster!</a></em> section below.</p>
<p><strong>N.B.</strong> the compiled JavaScript will be evaluated by the NodeJS (V8) runtime, not by the JVM.</p>
<p>You should eventually see printed in your terminal:</p>
<div class='highlight'><pre><code>$ ncljsc hello.cljs
Hello, World!
</code></pre></div>
<p>Now replace the contents of <code>hello.cljs</code> with:</p>
<div class='highlight'><pre><code>(ns hello
(:require [cljs.nodejs :as nodejs]
[clojure.string :as string]))
(def http
(nodejs/require "http"))
(def server (.createServer http
(fn [req, res]
(.writeHead res 200 (clj->js {:Content-Type "text/plain"}))
(.end res "Hello, World\n"))))
(.listen server 4200 "127.0.0.1")
(println "Server running at <a href='http://127.0.0.1:4200'>http://127.0.0.1:4200</a>")
;; Helper
(defn clj->js
"Recursively transforms ClojureScript maps into Javascript objects,
other ClojureScript colls into JavaScript arrays, and ClojureScript
keywords into JavaScript strings. Credit:
<a href='http://mmcgrana.github.com/2011/09/clojurescript-nodejs.html'>http://mmcgrana.github.com/2011/09/clojurescript-nodejs.html</a>"
[x]
(cond
(string? x) x
(keyword? x) (name x)
(map? x) (.-strobj (reduce (fn [m [k v]]
(assoc m (clj->js k) (clj->js v))) {} x))
(coll? x) (apply array (map clj->js x))
:else x))
</code></pre></div>
<p>Save it, then rerun <code>ncljsc hello.cljs</code> and point your browser to <a href="http://127.0.0.1:4200">localhost:4200</a>.</p>
<p>Did it work? Cool! (maybe submit an <a href="https://github.com/michaelsbradleyjr/node-clojurescript/issues">issue</a> if it didn't)</p>
<h3><em>Faster, faster!</em></h3>
<p>The slow compile times mentioned above are owing to startup time of the JVM, plus the time to initially load the two underlying compilers (ClojureScript and Google Closure). This is an annoying problem...</p>
<p><em>Problem solved!</em> Starting with <code>v0.1.4</code>, node-clojurescript offers a way to compile against a long-running, "detached" JVM server:</p>
<div class='highlight'><pre><code>$ ncljsc --server 4242
</code></pre></div>
<p>Invoke the command above and leave the terminal open (or run it in a <a href="http://tmux.sourceforge.net/">tmux</a> or screen session). You don't need to navigate to a particular path before starting it, bu you need to leave it running. After 10+ seconds you should see:</p>
<div class='highlight'><pre><code>$ ncljsc --server 4242
Starting up, please wait...
Initial build completed, JVM and compiler are primed and ready!
Detached JVM server listening at <a href='http://127.0.0.1:4242/'>http://127.0.0.1:4242/</a>
</code></pre></div>
<p>If you see something about a <code>DTraceProviderBindings</code> error, just ignore it as it's <a href="https://github.com/mcavage/node-restify/issues/100">harmless</a>. Depending on your platform, no error may be reported.</p>
<p>Now open another terminal and go back to the directory where you created <code>hello.cljs</code>. Then do:</p>
<div class='highlight'><pre><code>$ ncljsc --client 4242 hello.cljs
</code></pre></div>
<p>You should notice a marked difference in the time required for the script to run. Once the <code>--server</code> JVM is "hot", compile times should take only a few seconds, instead of 10+ seconds. That's because the <code>--client</code> process does not start its own JVM.</p>
<h4>How does it work?</h4>
<p>The <code>--server</code> process accepts "build requests" over HTTP, listening on <code>localhost</code> at the specified port. The <code>--client</code> then makes synchronous or asynchronous requests (depending on how it's invoked). And that's it: from the perspective of the end-user, the only difference is that these "remote" builds happen more quickly than "local" builds. Overall usage of <code>ncljsc</code> is the same whether you run remote or local builds.</p>
<h4>A few notes:</h4>
<ul>
<li>This feature is under active development and won't always work correctly, e.g. errors may not always make it back to the client and the return value of a build may be an empty string. It's best to keep an eye on the terminal output of the server process for signs of trouble.</li>
<li>You may use whatever port number you prefer, as long as the client and server use the same port.</li>
<li>Requests are restricted to the <code>localhost</code> interface. <em>(security)</em></li>
<li>There is a transparent exchange of credentials hard-wired into the client-server logic, so that arbitrary processes can't make build requests. <em>(security)</em></li>
<li>Aforesaid "credentials exchange" requires server and client processes to be run as the same user. <em>(security)</em></li>
<li>Credentials aren't persistent, so if the server process is bounced (you restart it, maybe it crashed), client processes must be restarted if they're long-running and will attempt further build requests. <em>(security?)</em></li>
<li>Don't use the client-server mode in a production environment. <em>(goes without saying?)</em></li>
</ul>
<h3>Automatic re-compiles</h3>
<p>It's 2012 and you shouldn't <em>have</em> to manually re-run your scripts while you're developing them. And you don't!</p>
<p>After some experimentation, <a href="https://github.com/isaacs/node-supervisor">supervisor</a> seems (to the author) to be the simplest and most flexible NodeJS-based tool for automatically re-starting scripts in a development workflow. Make sure to install it globally: <code>npm install -g supervisor</code>.</p>
<p>With <code>supervisor</code> installed and a <code>ncljsc --server</code> process running, revisit the directory where you created <code>hello.cljs</code> and do:</p>
<div class='highlight'><pre><code>$ supervisor -w hello.cljs -n exit -x ncljsc -- --client 4242 hello.cljs
</code></pre></div>
<p>That's a lot of flags for a single command, but see <code>supervisor --help</code> and you'll soon have the hang of it. Note that we're making use of <code>--client 4242</code>, which is proper to <code>ncljsc</code>, not <code>supervisor</code>.</p>
<p>Now edit <code>hello.cljs</code> and watch what happens when you save it. <em>Fantastic!</em> It compiles quickly, and will do so repeatedly whenever you save changes, so long as you keep <code>supervisor</code> running.</p>
<h3>Compiling to disk</h3>
<p>In addition to running <code>.cljs</code> scripts, <code>ncljsc</code> can also be used to write compiled JavaScript to disk. For example:</p>
<p><strong>saves to <code>hello.js</code> in the same directory</strong> (local build)</p>
<div class='highlight'><pre><code>$ ncljsc --compile hello.cljs
</code></pre></div>
<p><strong>saves to <code>myscript/hello.js</code> in the same directory</strong> (remote build)</p>
<div class='highlight'><pre><code>$ ncljsc --compile --output myscript --client 4242 hello.cljs
</code></pre></div>
<p><strong>re-compiles and re-saves when changes are made</strong> (remote build)</p>
<div class='highlight'><pre><code>$ supervisor -w hello.cljs -n exit -x ncljsc -- --client 4242 --compile hello.cljs
</code></pre></div>
<h3>More options</h3>
<p>The <code>ncljsc</code> command provides additional capabilities. Try:</p>
<div class='highlight'><pre><code>$ ncljsc --help
</code></pre></div>
<p>Not all of the features have been implemented yet. Also, you'll notice that <code>ncljsc</code> provides built-in <code>--watch</code> and <code>--watch-deps</code> options. Those do work, but there are some outstanding <a href="https://github.com/joyent/node/issues/3172">bugs</a> related to NodeJS's <code>fs.watch</code> facility. As such, it seemed better to propose <code>supervisor</code> as a file watching tool than explain a bunch of caveats regarding the built-in watch support. But by all means experiment and <a href="https://github.com/michaelsbradleyjr/node-clojurescript/issues">report back</a>.</p>
<h3>What's Next?</h3>
<p>So now what you should do is read up on Clojure and ClojureScript and <em>get to busy!</em> See the <em><a href="#resources">Resources</a></em> section below.</p>
<h2>Prerequisites</h2>
<h3>Java</h3>
<p>This library wraps a NodeJS front-end around the ClojureScript compiler, which is written in the Clojure language, which is hosted on the <a href="http://en.wikipedia.org/wiki/Java_virtual_machine">Java Virtual Machine</a> (JVM). That means you <em>must</em> have Java setup to successfully install <code>clojure-script</code> with npm.</p>
<p>More specifically, you'll need JDK 1.6 (Java SE 6): <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">Windows</a>, <a href="https://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/02-JavaDevTools/JavaDevTools.html">Mac</a>, <a href="https://github.com/flexiondotorg/oab-java6">debian / ubuntu</a></p>
<p>You'll also need to export the proper value for <code>JAVA_HOME</code> into your environment. The <a href="https://github.com/nearinfinity/node-java">installation instructions</a> for the <code>node-java</code> package are quite helpful in this regard, though note that installing <code>clojure-script</code> with npm will automatically install <code>node-java</code> as well (i.e. you don't need to do that separately).</p>
<h3>NodeJS</h3>
<p>If you're new to NodeJS and don't have it setup, that will be your next step. I highly recommend Tim Caswell's <a href="https://github.com/creationix/nvm">Node Version Manager</a> (nvm). It's easy to install and, and makes working with multiple <code>node</code> versions dead simple. For example:</p>
<div class='highlight'><pre><code>$ nvm install v0.6.17
...
$ nvm install v0.6.10
...
$ nvm use v0.6.17
</code></pre></div>
<p>When you install <code>node</code>, the <code>npm</code> tool will get installed along with it. So as long you have Java in place (see above), you should be ready to run:</p>
<div class='highlight'><pre><code>$ npm install -g clojure-script
</code></pre></div>
<p>That's it! Installing <code>clojure-script</code> (the npm package name for this library) will automatically perform a package-localized installation of Clojure, ClojureScript, Google Closure Compiler, etc.</p>
<p>If you get an error during installation, look closely at the error message. Maybe you made a typo while following the steps above? If you can't figure it out, feel free to submit an <a href="https://github.com/michaelsbradleyjr/node-clojurescript/issues">issue</a>.</p>
<h2>NodeJS <code>require</code> support</h2>
<p>With a local (vs. global) <code>node_modules</code> installation of <code>clojure-script</code>, you can load <code>.cljs</code> modules from other scripts without having to compile them beforehand.</p>
<p>With respect to the <em><a href="#quick-start">Quick Start</a></em> examples above: create a script <code>other.js</code> in the same directory as <code>hello.cljs</code>, then paste in the following and save it:</p>
<div class='highlight'><pre><code>require('clojure-script');
require('./hello');
// or require('./hello.cljs');
</code></pre></div>
<p>Then you can do:</p>
<div class='highlight'><pre><code>$ node other.js
</code></pre></div>
<p><strong><span style="color:red;">N.B.</span></strong> This is a handy feature for development purposes. But it would be a terrible idea to publish a package on the npm registry which makes use of this technique. Modules <em>developed</em> in ClojureScript should be <em>published</em> with <em>only</em> compiled JavaScript loaded at runtime (via NodeJS's <code>require</code>).</p>
<h3>Remote builds and <code>require</code></h3>
<p>It's entirely possible to leverage the <code>require</code> support in combination with node-clojurescript's client-server mode described in the <em><a href="#faster-faster">Faster, faster!</a></em> section above.</p>
<p>Suppose you have a "detached" JVM server process running on port <code>8888</code>:</p>
<div class='highlight'><pre><code>$ ncljsc --server 8888
</code></pre></div>
<p>In your <code>.js</code> script you can then indicate:</p>
<div class='highlight'><pre><code>require('clojure-script')(8888);
require('./hello.cljs');
</code></pre></div>
<p>In this context, the client-logic makes a <em>synchronous</em> (not async) build request against the server process. <code>hello.cljs</code> will be transparently compiled and loaded as before, but more quickly.</p>
<p>You can call the function returned by <code>require</code> without arguments, like so:</p>
<div class='highlight'><pre><code>require('clojure-script')();
...
</code></pre></div>
<p>In that case, the port number will default to <code>4242</code> (make sure the server process is using the same port). Note that calling the function without arguments and <em>not</em> calling it are two distinct things. If you don't call it, the <code>clojure-script</code> module will start a new JVM and perform a local build. If you do call it, with or without arguments, a JVM will <em>not</em> be started and the module will make a remote build request.</p>
<h2>Namespaces</h2>
<p>Clojure and ClojureScript support the notion of <a href="http://clojure.org/namespaces">namespaces</a>. Unlike loading <a href="http://nodejs.org/api/modules.html">modules</a> with NodeJS's <code>require</code>, using ClojureScript's namespace <code>:require</code> will result in the namespaced module being <em>inlined</em> as part of the compiled JavaScript (scope is carefully preserved).</p>
<p>Try creating two scripts, <code>foo.cljs</code> and <code>bar.cljs</code>:</p>
<p><strong>foo.cljs</strong></p>
<div class='highlight'><pre><code>(ns foo
(:require [cljs.nodejs :as nodejs]
[bar :as bar]))
(defn ^:export greet [name, title]
(str "Hello, " (bar/title title) " " name))
</code></pre></div>
<p><strong>bar.cljs</strong></p>
<div class='highlight'><pre><code>(ns bar
(:require [cljs.nodejs :as nodejs]))
(defn ^:export title [t]
(str t " Amazing" ))
</code></pre></div>
<p>Now create a third script, <code>greet.js</code>:</p>
<p><strong>greet.js</strong></p>
<div class='highlight'><pre><code>var foo = require('./compiled.js').foo;
console.log(foo.greet('ClojureScript developer!', 'Mr.'));
</code></pre></div>
<p>Time to compile:</p>
<div class='highlight'><pre><code>$ ncljsc -c -p foo.cljs > compiled.js
</code></pre></div>
<p>When that's finished, it's time to run <code>greet.js</code>:</p>
<div class='highlight'><pre><code>$ node greet.js
Hello, Mr. Amazing ClojureScript developer!
</code></pre></div>
<p>Examining the plentiful contents of <code>compiled.js</code>, you'll see (toward the bottom) that both <code>foo.cljs</code> and <code>bar.cljs</code> were compiled into the stand-alone JS file.</p>
<h2>Bundled Leiningen</h2>
<p><a href="https://github.com/technomancy/leiningen">Leiningen</a> is a popular and flexible build tool in wide use among Clojure developers. node-clojurescript bundles the shell script front-end to Leiningen (the <code>lein</code> command) and proxies to it with an executable script named <code>nlein</code>.</p>
<p>If you've installed the <code>clojure-script</code> module globally with <code>npm install -g clojure-script</code>, then you should be able to run:</p>
<div class='highlight'><pre><code>$ nlein
</code></pre></div>
<p><code>nlein</code> is a simple proxy script and does not feature any customizations of Leiningen. If you already have a <code>lein</code> executable on your path, <code>nlein</code> will ask whether it should delegate to it, with the option to remember your decision.</p>
<p>Note that <code>nlein</code>, when it's not delegating to another <code>lein</code>, will store JAR files and other things it downloads in the <code>support/.lein</code> directory, relative to the root of the <code>clojure-script</code> package. This is to keep <code>nlein</code> from even potentially conflicting with an existing installation. Normally, <code>lein</code> stores such things in <code>$HOME/.lein</code>.</p>
<p>All in all, the purpose of <code>nlein</code> is to provide an easy way for NodeJS developers to get up and running with Leiningen. If you're already using Leiningen, you may choose to ignore <code>nlein</code> and go about your business as usual.</p>
<h2>Coming Soon</h2>
<p>There are several goals that need to be accomplished in short order:</p>
<ul>
<li>The tooling developed in CoffeeScript needs to be re-implemented in ClojureScript so that this library will be pseudo self-hosting.</li>
<li>A plugin for the Leiningen build tool needs to be adapted or written, for use in development of complex ClojureScript projects in conjunction with this library and other NodeJS modules.</li>
<li>Missing features of <code>ncljsc</code> need to be implemented, the most important being a <a href="https://github.com/clojure/clojurescript/wiki/The-REPL-and-Evaluation-Environments">REPL</a>.</li>
<li>More and better documentation and examples.</li>
</ul>
<p>Help in accomplishing these and future goals is more than welcome.</p>
<h2>Resources</h2>
<div class='highlight'><pre><code>$ ncljsc --help
</code></pre></div>
<p><a href="http://himera.herokuapp.com/synonym.html">ClojureScript: Translations from JavaScript</a></p>
<p><a href="http://clojuredocs.org">ClojureDocs</a>, <a href="http://clojure.org">Clojure.org</a></p>
<p><a href="http://nodejs.org/api">NodeJS Documentation</a></p>
<p><a href="https://github.com/technomancy/leiningen">Leiningen</a>, <a href="https://github.com/technomancy/leiningen/wiki">wiki</a></p>
<p>Google Groups: <a href="https://groups.google.com/forum/?fromgroups#!forum/clojure">clojure</a>, <a href="https://groups.google.com/forum/?fromgroups#!forum/nodejs">nodejs</a></p>
<p><code>#clojure</code>, <code>#clojurescript</code> and <code>#node.js</code> channels on Freenode IRC.</p>
<p><a href="https://github.com/bbatsov/prelude">prelude</a>, <a href="https://github.com/bbatsov/prelude-modules">prelude-modules</a>, and <a href="http://emacsformacosx.com/">Emacs for OS X</a> (latest pretest)</p>
<h2>Credit</h2>
<p>This software is derived from and incorporates existing works:</p>
<p><a href="https://github.com/jashkenas/coffee-script">CoffeeScript</a>, <a href="https://github.com/clojure/clojurescript">ClojureScript</a>, <a href="https://github.com/cemerick/pomegranate">Pomegranate</a></p>
<p>In particular, many thanks are owed to <a href="https://github.com/jashkenas">Jeremy Ashkenas</a> and the other <a href="https://github.com/jashkenas/coffee-script">CoffeeScript</a> maintainers. Using the CoffeeScript tooling as a template, it was possible to whip together a useable NodeJS front-end in one intense week. It would have otherwise been much more slow-going.</p>
<h2>Copyright and License</h2>
<p>This software is Copyright (c) 2012 by Michael Bradley, Jr.</p>
<p>The use and distribution terms for this software are covered by the <a href="http://opensource.org/licenses/eclipse-1.0.php">Eclipse Public License 1.0</a> which can be found in the file <a href="http://michaelsbradleyjr.github.com/node-clojurescript/licenses/epl-v10.html">epl-v10.html</a> under the <a href="https://github.com/michaelsbradleyjr/node-clojurescript/tree/master/licenses">licenses directory</a> at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.</p>
<hr />
<p><a href="https://developer.mozilla.org/en/JavaScript/Reference/" title="JavaScript Reference"><br /> <img src="http://static.jsconf.us/promotejshs.png" alt="JavaScript Reference" /><br /></a></p></div></div></div></body></html>