I recently wrote a post on why you will have trouble compiling your node.cljs project with the
:advanced optimization mode. Basically the problem is that the Google Closure compiler is not aware of the node standard library, and therefore it won't preserve the standard library symbol names. Eg the
readdirSync symbol in a call to
(.readdirSync fs) will get mangled by the compiler and the app will fail with a runtime error.
The solution is to use the Closure compiler externs facility to provide the compiler with a list of symbols that are used in library calls and that should be preserved rather than mangled. But the implication is that you as the developer have to create that list of symbols separately for each and every one of your node.cljs projects, which sounds like a lot of grunt work that shouldn't be necessary.
I finished the earlier post by mentioning an npm package called closurecompiler-externs by Daniel Wirtz that provides, as the name implies, Closure compiler externs for the node standard library, but at the time I hadn't had time to try it out yet. This post is a quick rundown on how to use closurecompiler-externs in your node.cljs project.
To play along...
As last time:
- create a new project with
lein new nodecljs nodels
- copy the contents of the
nodels.cljexample file over the auto-generated
- and finally change the
You can compile the app with:
lein cljsbuild once
And you can run the resulting JS with:
node nodels.js .
Download the closurecompiler-externs module
The easiest way to pull in the closurecompiler-externs module is to install it into the current project with npm:
npm install closurecompiler-externs
This command will download the modules and all its dependencies and install them into
See this post on more information about using npm with node.cljs projects.
Reference the appropriate externs files in
Next we need to tell the Closure compiler about the externs files that we want to use. The
:compiler map in
project.clj file accepts an
:externs key with a vector value. The value defines a list of externs files that should be passed to the Closure compiler.
The closurecompiler-externs module provides one externs file per node standard library module. Eg there is a
fs.js externs file for the node file system module.
Our example app,
nodels.cljs, uses the
fs modules, so we need to add the appropriate externs files to the
:externs vector in
:externs ["node_modules/closurecompiler-externs/process.js" "node_modules/closurecompiler-externs/fs.js"]
Rebuild the app
We need to now rebuild the app with the new externs files. The
lein-cljsbuild plugin isn't clever enough to rebuild everything when the
:compiler map has changed, so be sure to clean the old artifacts first:
lein cljsbuild clean lein cljsbuild once
Note: the compiler won't give you any warning at all if you've misspelled the name of an extern file, so make sure the
:externs vector is correct.
Assuming everything has gone well, the app should now work:
% node nodels.js . . .gitignore README.md node_modules node_modules/closurecompiler-externs node_modules/closurecompiler-externs/.travis.yml node_modules/closurecompiler-externs/LICENSE node_modules/closurecompiler-externs/NodejsClosureCompilerExterns.png ...
Having the closurecompiler-externs module available definitely makes it easier to use externs in a node.cljs project. But you still have to specify each module separately - wouldn't it be possible to just a single externs file with all the node standard library modules in it? Ideally we would be able to generate the externs file from our CLJS code with some lisp magic. Has anyone looked into that?