Global Exports for Foreign Libraries

30 July 2017
David Nolen

Integration with the npm ecosystem promises to greatly reduce the friction around Closure-unaware JavaScript dependencies. However, this work is very much in progress and in no way precludes improving existing solutions to the problem of "foreign" dependencies.

ClojureScript’s :foreign-libs option has long provided a way to include JavaScript libraries which cannot be expected to pass through Closure advanced compilation. Through community efforts like CLJSJS, ClojureScript developers can reach functionality not readily provided by ClojureScript or Closure Library with relative ease.

However, the design of :foreign-libs has always had a glaring flaw - these libraries are assumed to be globally loaded. Any API exported by the foreign library had to be accessed through the global environment:

(ns foo
  (:require [cljsjs.react]))

(def react js/React)

Thus foreign libraries supported none of the usual :require affordances like :as, :refer, :rename, etc.

While this may seem like a minor point, over the years users have reached further and further into the JavaScript ecosystem for functionality not provided elsewhere. For many projects this meant using tools like Webpack to package up all such dependencies into a single foreign library. While expedient, this approach leads to an unidiomatic style, and furthermore creates an obstacle should users try to migrate these dependencies to direct consumption from node_modules down the road.

In the next release we are introducing a simple new enhancement to the :foreign-libs compiler option - :global-exports. A foreign library entry can now declare which namespace maps to which globally exported name:

:foreign-libs [{:provides ["cljsjs.react"]
                :global-exports '{cljsjs.react React}}
               {:provides ["cljsjs.react.dom"]
                :global-exports '{cljsjs.react.dom ReactDOM}}]

With this simple change cljsjs.react can now be treated as a regular namespace:

(ns foo
  (:require [cljsjs.react :as react :refer [createElement]))

Note that as :global-exports is a map of namespaces to global exports, users leveraging Webpack to make a single foreign library can easily map all the bundled libraries for idiomatic usage.

This also provides a gradual migration path to node_modules if so desired. For example, a user could create a cljsjs.react artifact with a declared :npm-deps on React. Since foreign library usage is now unified with normal namespace usage, you can switch to node_modules dependencies with no actual changes in your source code, just changes to your dependencies in your dependency management tool of choice (Maven, Lein, Boot).

We believe this feature addresses a long outstanding pain point and provides a smooth migration path to node_modules based dependencies. Please give it a try with ClojureScript 1.9.854 or later.