UPDATE (June 17th, 2017): A year later, this post and method are very much still relevant. I’ve made a small update to take advantage of the
:foreign-libs option, as suggested in the comments (Thanks Andreas and Alex!), which reduces the plumbing necessary.
Moving to Clojurescript and Reagent has been amazing in many ways but I just couldn’t wrap my head around a comparable import flow.
This solution does not use Cljsjs in anyway.
Why not Cljsjs?
- Very few packages compared to NPM or Bower. Whenever I look for something it’s usually not there.
- Packages are mostly out of date compared to their NPM counterparts. This make sense because with NPM packages the author/maintainer of the library is the same person as the maintainer of the package. That’s not the case with Cljsjs.
- Packaging a library is not trivial. The community’s usual reply to the import issue is “just package it yourself”. Sending someone who wants to fiddle around with a 3rd party react component, to package the dependency themselves is, in my opinion, cumbersome. This also leads back to problem #2.
In this example we’ll start with a simple frontend-only Reagant app and setup a workflow that will allow us to add a react-player component to it. Let’s call this project Zefstyle. You can also skip this altogether and just jump to the finished result here.
Starting with a template:
For sanity - Let’s see that it’s working
While that’s running, open public/index.html in your favorite browser. You should see a page that says “Welcome to Reagent”
Since we’re running figwheel, we can change that live. In
public/index.html change the header to “Zef Style” - You should see it updating within seconds. You can then close fihghweel for now.
Let’s create a minimal
package.json file. We can either do it interactively with
npm init or just manually create a
- We import
react-domalthough they are not a hard dependency of
react-playerand more than that - we already have a React instance in our code, the one that Reagant depends on. Why? There is an order of execution issue when relying on Reagant’s React dependency. This also means that we will later need to exclude Reagant’s React dependency so that we don’t have two React instances.
- I’ve included some shortcuts for webpack scripts. They are not necessary but will come in handy.
All of our dependency tree should now be in the
An alternative approach to this step would be to use the lein-npm plugin. I’m pretty comfortable with npm so I decided to do it myself.
Our webpack setup will be pretty minimal - no fancy loaders. It consists of a definition file
webpack.config.js and an entry script that bootstraps our imported libraries to the
We basically defined our entry point script as
src/js/main.js and our output artifact as
src/js/main.js should look something like:
I usually prefer not to bind too many objects to the global window context, that’s why I push whatever I can into
window.deps. With that said,
ReactDOM must be on the global window context because that’s where components expect them to be.
Now we can either run
For a onetime build, or:
Leiningen project setup
Like I mentioned before, we’re counting on webpack to bring React into the picture. That means that we need to get rid of the
cljsjs.react.dom packages that reagant depends on.
project.clj let’s change our dependencies to be:
And add an external dependency to our webpack generated
:foreign-libs. We need to add the following to all build profiles:
Notice how our foreign lib satisfies all react dependencies and also add a convenience namespace,
webpack.bundle, for importing the external bundle.
At this point it’s a good idea to cleanup and re-run figwheel.
Now that we have everything in place we can add the react-player to our main Reagant view.
src/zefstyle/core.cljs let’s add the
webpack.bundle dependency to our namespace:
and change our
home-page function to be:
Notice the special
:> syntax for using pure React components.
public/index.html should yield something like this:
Voila! We brought the pure JS
react-player component into our Reagent view.
To bring in new components we just need to declare them in
require() them in main.js and rebuild.
- JS dependencies are not processed by the Closure compiler so they do not get advanced optimization, only whatever webpack is setup to do.
- Setting up this process is not trivial. However, it only needs to be done once and from then on it’s a smooth sail. Also, automating the process or part of it sounds like a feasible task as part of a lein plugin. I’ll look into that when some time frees up.
Let me know if this was helpful, requires some fixups or if it’s complete rubbish.