Skip to content

Instantly share code, notes, and snippets.

@pbostrom
Last active March 8, 2024 07:53
Show Gist options
  • Save pbostrom/87500c8c3fa43b23cd3ccd764ef767d5 to your computer and use it in GitHub Desktop.
Save pbostrom/87500c8c3fa43b23cd3ccd764ef767d5 to your computer and use it in GitHub Desktop.
Loading third-party npm modules in ClojureScript

Loading third-party npm modules in ClojureScript

This example shows how to load the re-resizable npm module which provides a resizable React component. Requires the lein-npm plugin.

  1. Add the npm module to the :npm :dependencies section of project.clj:
 :npm {:package {:scripts {:build "webpack -p"}}
       :dependencies [[react "16.4.0"]
                      [react-dom "16.4.0"]
                      ["@cljs-oss/module-deps" "1.1.1"]
+                     [re-resizable "4.4.8"]
                      [browserify "15.1.0"]
                      [webpack "4.8.3"]]
       :devDependencies [[express "4.16.3"]
                         [http-proxy-middleware "0.18.0"]
                         [tailwindcss "0.5.2"]
                         [postcss-cli "5.0.0"]
                         [cssnano "3.10.0"]
                         [glhd-tailwindcss-transitions "0.3.0"]
                         [minimist "1.2.0"]
                         [chalk "2.4.1"]
                         [webpack-cli "3.0.3"]]}
  1. Create a JavaScript source file to require all of your npm dependencies (we'll use src/js/npm-deps.js). Later we'll use a bundling tool like browserify or webpack to bundle the modules for use in the browser. In this file, require the npm module and assign it to the browser's window object. We need to give the module a name which we will refer to in the following steps; we'll use Resizable.
+ window.Resizable = require('re-resizable');
  window.React = require('react');
  window.ReactDOM = require('react-dom');
  1. Update the :foreign-libs section of the ClojureScript :compiler section project.clj. We need to create a ClojureScript namespace to use in the context of a ClojureScript require block. We could just re-use the name of the npm module, but I prefer to avoid single-segment namespaces. I'll use the prefix npm and call it npm.resizable. Put the namespace into the :provides section.
:compiler     {:main                 foo.core
               :output-to            "resources/public/js/app.js"
               :output-dir           "resources/public/js/out"
               :process-shim         true
               :foreign-libs [{:file "resources/public/js/npm-deps.js"
                               :provides ["cljsjs.react"
                                          "cljsjs.react.dom"
                                          "react"
                                          "react-dom"
+                                         "npm.resizable"]
...

We also need to update the :global-exports section to map the ClojureScript namespace we created in Step 3 to the JavaScript object we attached to the window object in Step 2.

:compiler     {:main                 foo.core
               :output-to            "resources/public/js/app.js"
               :output-dir           "resources/public/js/out"
               :process-shim         true
               :foreign-libs [{:file "resources/public/js/npm-deps.js"
                               :provides ["cljsjs.react"
                                          "cljsjs.react.dom"
                                          "react"
                                          "react-dom"
                                          "npm.resizable"]
                               :global-exports {react React
                                                react-dom ReactDOM
+                                               npm.resizable Resizable}}]
  1. Now we can require the npm module into our ClojureScript application using this namespace. In this case the re-resizable npm module provides a React component:
(ns foo.auth.views
  (:require [re-frame.core :refer [subscribe dispatch]]
            [reagent.core :as reagent]
            [clojure.string :as str]
            [bouncer.core :as b]
            [bouncer.validators :as v]
            [camel-snake-kebab.core :refer [->kebab-case-keyword]]
            [camel-snake-kebab.extras :refer [transform-keys]]
+           [npm.resizable :as resizable]))
...
+ [:> resizable {:defaultSize {:width 320 :height 200}
+                :class "bg-white" }
  1. Run lein npm install. This will install the npm dependencies. Use a tool like browserify or webpack to bundle your modules for browser usage. The output will be the :foreign-libs file declared above.

browserify example:

npm install browserify
./node_modules/.bin/browserify src/js/npm-deps.js -o resources/public/js/npm-deps.js

Next, compile your ClojureScript application using your normal workflow. Your npm module should now be available in your application.

  1. Declare externs for your module. If you're lucky, you can find an externs file for a recent version of your module at https://github.com/cljsjs/packages. For example here is one for React. Otherwise, you can use this externs generator. Basic steps are:
npm install -g externs-generator

Then run the generator on the foreign libs file resources/public/js/npm-deps.js that was created in Step 5. You will need to specify a main interface to extern from your module. This can be a little tricky to determine, but read through the documentation for your module or look through the js source to see if you can figure it out. In our example it happens to be called Resizable:

generate-extern -f resources/public/js/npm-deps.js -n Resizable -o externs.js

Specify the externs.js file for the :externs option to the ClojureScript compiler. If successful, you should be able to turn on advanced compilation and load the application in the browser without any errors. For more info see https://github.com/cljsjs/packages/wiki/Creating-Externs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment