ClojureScript

ClojureScript with Webpack

This page documents how to use Webpack and externs inference to easily integrate popular libraries from the JavaScript ecosystem into your ClojureScript project. You should have Node.js and yarn installed. This guide assumes you have read through the Quick Start. This guide borrows liberally from this excellent guide on Webpack 2.

Setting Up Your Project

First create a project directory and initialize yarn:

mkdir hello-webpack
cd hello-webpack
yarn init -y

Add webpack and its command line tools:

yarn add webpack webpack-cli

Create a webpack.config.js file:

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'index_bundle.js'
  }
}

We’re now ready to setup our JS dependencies:

JavaScript Dependencies

Install react and react-dom:

yarn add react react-dom

Now we need to import and export them so that ClojureScript can use them. Create src/js/index.js with the following content:

import React from 'react';
import ReactDOM from 'react-dom';
window.React = React;
window.ReactDOM = ReactDOM;

Now build your foreign library!

yarn webpack

You should now have a file in dist/index_bundle.js. Let’s do the ClojureScript bits now.

Using Your Foreign Library

Create a deps.edn file:

{:paths ["src/cljs"]
 :deps {org.clojure/clojurescript {:mvn/version "1.10.312"}}}

Now create src/cljs/hello_webpack/core.cljs with the following contents:

(ns hello-webpack.core
  (:require [react]))

(.log js/console react/Component)

Notice that we are requiring React as if it was a normal require and a normal namespace.

In order for this to work we need to set a couple of compiler options. Create a build.edn file with the following:

{:main hello-webpack.core
 :output-to "out/main.js"
 :output-dir "out"
 :infer-externs true
 :npm-deps false
 :foreign-libs [{:file "dist/index_bundle.js"
                 :provides ["react" "react-dom"]
                 :global-exports {react React
                                  react-dom ReactDOM}}]}

We turn on externs inference and we disable reading from node_modules since we’re managing that on our own. We then state what our foreign lib provides and what global JavaScript variables will have the library objects. This is why we exported to window in index.js. Note that our foreign library can provide as many libraries as we like and we can define exactly where they will be found. This also allows us to use foreign libraries as if they were normal ClojureScript namespaces. However, ClojureScript knows these are foreign libraries and will treat any function calls and property accesses as things to be considered for externing.

Let’s see this in action.

clj -m cljs.main -co build.edn -v -c

Let’s verify that it worked:

clj -m cljs.main -s

Open your browser to http://localhost:9000. Open the Developer Console, you should see that React.Component got logged.

Check out/inferred_externs.js. You should see that Object.Component was inferred.

Overriding a Foreign Library

Occasionally you may find that you need to provide a custom build of React but you still want to use some ClojureScript React binding like Reagent. ClojureScript supports this out of the box.

To demonstrate this change your deps.edn to the following:

{:paths ["src/cljs"]
 :deps {org.clojure/clojurescript {:mvn/version "1.10.312"}
        reagent {:mvn/version "0.8.1" :exclusions [cljsjs/react]}}}

Change your source file to following:

(ns hello-webpack.core
  (:require [goog.dom :as gdom]
            [reagent.core :as r]))

(defn simple-component []
  [:div
   [:p "I am a component!"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])

(r/render [simple-component] (gdom/getElement "app"))

Rebuild your project, run the webserver and open http://localhost:9000:

clj -m cljs.main -co build.edn -v -c -s

To verify that externs inference allows advanced compilation to work, let’s make an advanced build:

clj -m cljs.main -co build.edn -O advanced -v -c -s

Original author: David Nolen