Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding ClojureScript to an existing codebase

I have a reasonably large React + Relay codebase that is being built using Webpack. Is it possible to gradually introduce ClojureScript + Reagent into this somehow?

I was thinking of starting with some of the smaller functional components in our code base and swapping them out. This would mean that the Reagent component would somehow need to receive props from the parent.

Any thoughts or tooling around doing this? A quick Google only seems to turn up articles around including JavaScript libraries in your ClojureScript app, not the other way around.

like image 923
Samuel Avatar asked Jan 25 '17 05:01

Samuel


1 Answers

You could create a reagent component, export it, and then import it in your JS code.

Since you already have webpack/react in your js project, you need to exclude those from your cljs project:

:dependencies [[org.clojure/clojure "1.8.0"]
               [org.clojure/clojurescript "1.9.229"]
               [org.clojure/core.async "0.2.391"
                :exclusions [org.clojure/tools.reader]]
               [reagent "0.6.0" :exclusions [cljsjs/react cljsjs/react-dom cljsjs/react-dom-server]]]

Now you have to trick Reagent into thinking that those react files you've just excluded are still here. Create these three files:

src/cljsjs/react.cljs

(ns cljsjs.react)

src/cljsjs/react/dom.cljs

(ns cljsjs.react.dom)

src/cljsjs/react/dom/server.cljs

(ns cljsjs.react.dom.server)

Yes, just one line with namespace declaration in each file.

Now you can write your component:

reagent_component/core.cljs:

(ns reagent-component.core
    (:require [reagent.core :as r]))

(def counter (r/atom 5))

(def ^:export test66
    (r/create-class
        {:reagent-render
         (fn [props]
             (let [{:keys [name]} props]
                 [:div 
                  {:on-click (fn [] (swap! counter inc))}
                  name ", I am counting " (clojure.string/join ", " (map inc (range @counter)))])
             )}))

Your cljsbuild section in project.clj might look like this:

:cljsbuild {:builds
            [{:id           "min"
              :source-paths ["src"]
              :compiler     {:output-to     "resources/public/js/compiled/reagent_component_min.js"
                             :output-dir    "resources/public/js/compiled/min"
                             :main          reagent-component.core
                             :optimizations :advanced
                             :asset-path    "js/compiled/out"
                             }}]}

For the purpouse of brevity I've given you only the min section of the cljsbuild.

lein cljsbuild once min will produce resources/public/js/compiled/reagent_component_min.js file, which must be copied into your Webpack project.

New entry is added to your webpack.config before your main entry point:

 entry: [`${APP_DIR}/reagent_component_min.js`, `${APP_DIR}/main.js`],

This file (reagent_component_min.js) should be excluded from babelification:

module: {
  loaders: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components|reagent_component_min.js)/,
      loader: 'babel-loader',
      query: {
        presets: ['latest']
      }
    }
  ]
}

and in your javascript you use it like this:

import React from 'react';
import ReactDOM from 'react-dom';

const comp = reagent_component.core.test66;

const element = <div>Hello, world
    {React.createElement(comp, {name: 'akond'})}
</div>;

ReactDOM.render(
  element,
  document.getElementById('app')
);

Yes, babel jsx plugin does not recognize <comp name="..."/>. That's why React.createElement is called. I didn't figure out how to make it look better, but it works.

like image 198
akond Avatar answered Nov 15 '22 09:11

akond