This post describes, step by step, how to setup a workflow for importing React components from NPM, using Webpack, and incorporate them in your Reagent views.
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.
Motivation
Any Javascript developer using a modern build tool can easily test and incorporate React components from 3rd party developers in their app. It’s usually just a matter of declaring dependencies, building and importing.
Moving to Clojurescript and Reagent has been amazing in many ways but I just couldn’t wrap my head around a comparable import flow.
The workflow I will describe employs NPM as a dependency manager and Webpack as a build tool. The build artifact is added as an external Javascript dependency to an otherwise (pretty) standard Leinnnigen build process.
This solution does not use Cljsjs in anyway.
Why not Cljsjs?
Cljsjs is a great community effort to package common Javascript libraries in a Clojurescript friendly way. However, it has some shortcomings.
- 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.
Zefstyle
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.
NPM Setup
Let’s create a minimal package.json
file. We can either do it interactively with npm init
or just manually create a package.json
file:
package.json remarks:
- We import
react
andreact-dom
although they are not a hard dependency ofreact-player
and 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.
Install dependencies:
All of our dependency tree should now be in the node_modules
directory.
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.
Webpack setup
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 window
object.
webpack.config.js
:
We basically defined our entry point script as src/js/main.js
and our output artifact as public/js/bundle.js
.
Our entry 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, React
and 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:
For a continuous build that watches for changes in our code. Given that the Javascript code should remain pretty static the watch might be redundant.
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
& cljsjs.react.dom
packages that reagant depends on.
In project.clj
let’s change our dependencies to be:
And add an external dependency to our webpack generated bundle.js
through :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.
Grand finale
Now that we have everything in place we can add the react-player to our main Reagant view.
in 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.
opening 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 package.json
, require()
them in main.js and rebuild.
Caveats:
- 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.
Thanks to:
- Thariq Shihipar for suggesting this approach although condemning the use of anything other than Javascript and React.
- /r/clojurescript, Clojurians on Slack and especially @sbmitchell for helping me troubleshoot.