Get your :advanced mode on with the closurecompiler-externs npm module

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.clj example file over the auto-generated src/nodels/core.cljs
  • and finally change the :optimizations setting from :simple to :advanced in project.clj.

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 ./node_modules.

See this post on more information about using npm with node.cljs projects.

Reference the appropriate externs files in project.clj

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 process and fs modules, so we need to add the appropriate externs files to the :externs vector in project.clj:

: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.

Run it!

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
...

Conclusions

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?


Want to read more about ClojureScript on Node?
Sign up for our mailing list!

Unsubscribe at any time. No spam, ever.
comments powered by Disqus