Let's say I have a cljs
file containing the following:
(ns foo)
(defn add [x y]
(+ x y))
and wish to make this available as a JavaScript library to non-ClojureScript devs (primarily focused on node.js). I can do this:
clj -m cljs.main -c foo
But the problem is that the output is geared towards google closure's module system (e.g. goog.require
). I can set the target to none
with the -t
flag (as opposed to browser or node), and that... doesn't fix this. Setting it to node
also doesn't fix the issue: no index.js
(it's called main like in Java), no module.exports = blah blah
. Seems like it's geared towards standalone, full node apps, rather than libraries.
I understand that ClojureScript uses google closure for it's own sub-modules, and I'm not necessarily looking to get rid of all of that (I'm not sure you could). And I get that es2015 native JavaScript modules are out because of their static nature.
I could massage the output by hand or by script to play nice with the npm ecosystem, but I'm surprised that there's no compiler option that can actually output a npm-friendly module. Or is there? Am I just reading --help
wrong?
This assumes that you already have a working installation of ClojureScript and Node.js
Given:
math101
|-- package.json
|-- src
| `-- com
| `-- example
| `-- math.cljs
package.json
{
"name": "math101",
"version": "1.0.0",
"main": "dist/index.js",
"license": "MIT",
"devDependencies": {
"shadow-cljs": "^2.8.52",
"source-map-support": "^0.5.13"
}
}
Notes:
dist/index.js
- This will contain our ClojureScript code converted to JavaScriptshadow-cljs
- The build (and dependency management) tool that we needsource-map-support
- Required to run ClojureScript on Node.js
📣Please make sure you have installed these two NPM dependencies before you proceed further.
math.cljs
(ns com.example.math)
(defn add [x y]
(+ x y))
At the root of math101
run yarn shadow-cljs init
.
This will create a file called shadow-cljs.edn
with some default settings:
;; shadow-cljs configuration
{:source-paths
["src/dev"
"src/main"
"src/test"]
:dependencies
[]
:builds
{}}
Let's make some changes.
First you don't need that many source paths for this:
{:source-paths
["src"]
:dependencies
[]
:builds
{}}
Then let's add a build configuration:
;; shadow-cljs configuration
{:source-paths
["src"]
:dependencies
[]
:builds
{:math101 {:target :node-library
:output-to "dist/index.js"
:exports-var com.example.math/add}}}
Notes:
:math101
- This the build id that we will use later:target :node-library
- This tells shadow-cljs
that you intend to author a library:output-to "dist/index.js"
- The code you intend to publish:exports-var com.example.math/add
- "Fully qualified name" of the function you intend to publish. This will produce a default export.Additional notes:
The :target :node-library emits code that can be used (via require) as a standard node library, and is useful for publishing your code for re-use as a compiled Javascript artifact.
Source
There is a :npm-module
target available but so far :node-library
has checked all the boxes for me.
Run yarn shadow-cljs compile math101
.
The first time you run this, shadow-cljs
will download a bunch of stuff. Eventually it will finish and when it does...
$ node
> var add = require('./dist')
> add(40, 2)
42
✨✨✨
Need to export more than just one function? No problemo.
Let's add subtract
:
(ns com.example.math)
(defn add [x y]
(+ x y))
(defn subtract [x y]
(- x y))
And now let's update our build config:
;; shadow-cljs configuration
{:source-paths
["src"]
:dependencies
[]
:builds
{:math101 {:target :node-library
:output-to "dist/index.js"
:exports {:add com.example.math/add
:subtract com.example.math/subtract}}}}
Run yarn shadow-cljs compile math101
again and when it finishes:
$ node
> var math101 = require('./dist')
> math101.add(40, 2)
42
> math101.subtract(44, 2)
42
✨✨✨✨✨✨
ADDENDUM
This is only intended to get you started. shadow-cljs compile
generates non-production code (i.e. it's not minified, dead code is not removed AFAIK and Google Closure hasn't run any optimisation yet).
The command for generating production-ready code is shadow-cljs release
but you may want to tweak your build config before.
I would highly recommend that you invest time reading up the shadow-cljs documentation. It is an amazing tool.
shadow-cljs supports output in a CommonJS format via :target :npm-module which does support exactly what you are asking for. Node and other JS tools (eg. webpack
) can consume separate namespaces independently. The default CLJS tools do not support this mode.
ClojureScript however is very much written with the assumption that your whole program will be optimized by the Closure Compiler. This makes it less than ideal for writing libraries to be included in other builds. Each "library" built this way will contain its own version of cljs.core
and therefore will be pretty large to begin and including 2 libraries built this way is a recipe for disaster since they won't be compatible with each other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With