Lately the Clojuresphere has been abuzz with efforts to make it easy to integrate NPM dependencies in ClojureScript projects. The reason is hardly a mystery. The NPM package repository, warts and all, contains a large amount of high-quality libraries. Access to these libraries instantly gives you leverage.
Modern websites usually include their code in the form of a bundle,
essentially a concatenation of individual libraries and modules along
with some glue code. For ClojureScript, the Google Closure compiler is
responsible for generating this bundle. Requiring a cljsjs namespace
cljsjs.react) causes the React.js to be included in
the output bundle. Because the library is included as a foreign lib,
Closure will not attempt to minify or shrink its contents.
Essentially a convenient bridge between the Maven and NPM world, CLJSJS allows you to specify all your dependencies in a single file (project.clj or build.boot). However, the approach also has drawbacks:
Of course CLJSJS packages aren’t magic, so how do they work?
Essentially what they do is to build a single bundle containing the
library, including all its internal dependencies. The bundle’s outgoing
interface is to expose a global var - for react,
window.React, for react-datetime
window.ReactDatetime. On the inbound side, the library can
also be a consumer of external dependencies. For example, react-datetime
assumes the existence of a preloaded
Many if not all CLJSJS libraries consist of a bundle built using the
perhaps the most popular of its sort (the others in the motely crew are
browserify, rollup, Google Closure, and the react-native packager). The
packages relies on a webpack.config.js
that defines both inbound (“external”) and outbound (“output”)
dependencies. The result is a
bundle.js that defines a
single global object.
This raises the question - why not rely on webpack to orchestrate NPM dependencies altogether? This is the strategy I will propose in the remainder of this post.
The double bundle example projects demonstrates how to use webpack directly to use NPM dependencies in your Clojurescript project. The net effect is that the project’s index.html references two separate bundles, one built by webpack and one built by the Clojurescript compiler and Google Closure compiler combo.
Webpack supports exporting a single var, but we actually need to
export multiple libraries as multiple vars. For this reason, the webpack
conig uses a special entry point,
library.js. For each
dependency that needs to be visible from Clojurescript, the file
variable. This is not elegant, but the effect is similar to how CLJSJS
works: all dependencies are available as global names,
With this setup, adding a new NPM dependency is as easy running
yarn add react-datetime, adding
window.ReactDatetime = require("react-datetime") to
library.js and rebuilding the bundle using
yarn build. Having done this, you can access the library
(goog.object/get js/window "ReactDatetime").
What’s more, any library available on NPM should work. Inter-library dependencies will be handled automatically by webpack. Because webpack is popular, chances are getting things to work will require little additional work - and problems will be easy to google.
With the double-bundle approach, a few things need to be kept in mind:
cljsjs/react dependency, we also lose
cljsjs.react namespace. However, libraries like Reagent
require this namespace. The solution is to mock out this namespace with
an empty cljs file. This is admittedly somewhat inelegant.