ClojureScript `require` outside `ns`02 Oct 2016
The next version of the ClojureScript compiler adds support for using
outside of the
ns form. Owing to ClojureScript's compilation model, however, there
exist subtle differences with respect to the behavior that Clojure provides. Read
on to learn more.
Ever since its inception, ClojureScript has always required files to provide a namespace
declaration at the top. A hard requirement like that is sometimes counterproductive
when we just want to try something out quickly. Besides, it doesn't mimic Clojure's
behavior very well, where it assumes you're in the default
user namespace when
a namespace declaration is not provided. In addition, forms like
import were never supported outside of the ClojureScript REPL, and even those
were implemented as a hack on top of
A number of tickets (1, 2) had been lying around in ClojureScript's issue tracker to address these shortcomings. Now, a recent addition to the compiler (coming in the next release) fixes these issues, paving the way for a number of enhancements coming to ClojureScript in the near future.
Differences from Clojure
In Clojure, you can
require namespaces dynamically into your namespace, load code
conditionally and at arbitrary nesting levels. In ClojureScript, this is not the
case, mostly due to the static nature of namespaces in the Google Closure Library.
Consequently, there are couple of rules to abide by when calling
require and similar
use-macros, etc.) in ClojureScript:
- A file can have one of:
- a namespace declaration, or
- (possibly several)
- none of the above, in the case it doesn't depend on any other namespace.
- when present,
requireforms must appear at the top of a file.
- All the definitions in a file without a namespace declaration are interned in
Because this new feature is currently unreleased, the only way you can try it out
today is by building the compiler
script/uberjar). Below is an example you can use to guide you through
this new addition.
Create a project with the following structure:
project ├─ src │ └─ foo.cljs └─ cljs.jar
foo.cljs, place the contents below. As an aside, note that we're taking advantage
of Clojure namespace aliasing
(JIRA ticket) and
implicit macro loading. This too —
as one might expect — works outside the namespace declaration.
;; src/foo.cljs (require '[clojure.test :refer [deftest is run-tests]]) (deftest failing-test (is false)) (run-tests)
cljs.jar uberjar in place, running the following command will land you
directly in a ClojureScript Node.js REPL (note: requires a Node.js installation).
$ java -cp src:cljs.jar clojure.main -e "(require 'cljs.repl) (require 'cljs.repl.node) (cljs.repl/repl (cljs.repl.node/repl-env))"
Then you can load the file into the REPL and see it execute!
ClojureScript Node.js REPL server listening on 53518 To quit, type: :cljs/quit cljs.user=> (load-file "src/foo.cljs") Testing cljs.user FAIL in (failing-test) (at .cljs_node_repl/cljs/test.js:432:14) expected: false actual: false Ran 1 tests containing 1 assertions. 1 failures, 0 errors. nil cljs.user=>
I've also captured all the above in a gist that you can refer back to later.
This addition unlocked a number of new possibilities for ClojureScript, which are
already bearing fruits. First of all, it already allowed us to
delete a ton of hacky code
require work in REPLs. In a very near future, it will allow ClojureScript
to add a number of new exciting features, one of which is support for
extensible tagged literals.
Thanks for reading!
nsform is not present, a generated namespace will be provided in the form of
XXXXare the first characters of the filenames's SHA1 hash. This implementation detail effectively works around the fact that the Google Closure Library requires files to provide different namespaces.
*Thanks to Mike Fikes for reading a draft of this post.*