ClojureScript `require` outside `ns`
02 Oct 2016The next version of the ClojureScript compiler adds support for using require
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.
Background
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 require, use
and import were never supported outside of the ClojureScript REPL, and even those
were implemented as a hack on top of ns.
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
forms (use, use-macros, etc.) in ClojureScript:
- A file can have one of:
- a namespace declaration, or
- (possibly several)
requireforms, or - 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
the
cljs.usernamespace 1.
Example
Because this new feature is currently unreleased, the only way you can try it out
today is by building the compiler
uberjar (with 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.jarIn 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)With the 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.
Parting thoughts
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
that made 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!
ns form
is not present, a generated namespace will be provided in the form of
cljs.user.file_nameXXXX, where
XXXX are 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.*