2

I'm trying to work out if I can use Om on Node.js to generate HTML. I've been able to get ClojureScript running on Node.js and apparently React can be run on Node.js. I create a Leiningen project as follows as per the basic guide

lein new figwheel om-tut -- --om

modified the project.clj file to start as follows

(defproject om-tut "0.1.0-SNAPSHOT"
  :description "FIXME: write this!"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}

  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-2850"]
                 [figwheel "0.2.5-SNAPSHOT"]
                 [org.clojure/core.async "0.1.346.0-17112a-alpha"]
                 [sablono "0.3.4"]
                 [org.omcljs/om "0.8.8"]]

  :plugins [[lein-cljsbuild "1.0.4"]
            [lein-figwheel "0.2.5-SNAPSHOT"]
            [lein-npm "0.4.0"]]

  :source-paths ["src"]

  :clean-targets ^{:protect false} ["resources/public/js/compiled" "out/server" "out/server/om_tut.js"]

  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src" "dev_src"]
              :compiler {:output-to "resources/public/js/compiled/om_tut.js"
                         :output-dir "resources/public/js/compiled/out"
                         :optimizations :none
                         :main om-tut.dev
                         :asset-path "js/compiled/out"
                         :source-map true
                         :source-map-timestamp true
                         :cache-analysis true }}
             {:id "min"
              :source-paths ["src"]
              :compiler {:output-to "resources/public/js/compiled/om_tut.js"
                         :main om-tut.core                         
                         :optimizations :advanced
                         :pretty-print false}}
             {:id "server"
              :source-paths ["src"]
              :compiler {
                :main pow.core
                :output-to "out/server/om_tut.js"
                :output-dir "out/server"
                :optimizations :simple
                :target :nodejs
                :cache-analysis true
                :source-map "out/server/om_tut.js.map"}}]}

and changed core.cljs to

(ns ^:figwheel-always om-tut.core
    (:require[om.core :as om :include-macros true]
              [om.dom :as dom :include-macros true]))

(enable-console-print!)

(println "Edits to this text should show up in your developer console.")

;; define your app data so that it doesn't get over-written on reload

(defonce app-state (atom {:text "Hello world!"}))

(defn render [data owner]
  (reify om/IRender
    (render [_]
      (dom/h1 nil (:text data)))))

(om/root
  render
  app-state
  {:target (. js/document (getElementById "app"))})

(defn -main []
  om.dom/render-to-str (render app-state nil))

(set! *main-cli-fn* -main)

The project compiles successfully using the following command

lein cljsbuild once server

but when I try to run the generate output like so from a Windows cmd shell

node out\server\om_tut.js

I get

    ...\om-tut\out\server\om_tut.js:40237
om.dom.input = om.dom.wrap_form_element.call(null, React.DOM.input, "input");
                                                   ^
ReferenceError: React is not defined
    at Object.<anonymous> (...\om-tut\out\server\
om_tut.js:40237:52)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

I think the React object that is undefined is supposed to come from the ...\om-tut\out\react.inc.js file which exists and should have been loaded at line 37532 of the generated om_tut.js file

cljs.core.load_file("react.inc.js");

Any ideas about what might be going wrong?

Hugh Powell
  • 317
  • 2
  • 11
  • node.js is on server and it can serve some html/js files which can be generated by clojurescript/om.. So what does react can be `run on` node.js means ? – Ashish Negi Apr 21 '15 at 05:26
  • Node.js runs JavaScript (not just serves it), ClojureScript compiles down into JavaScript and Om is a library for ClojureScript. I want to use the awesomeness that is Om to generate the HTML that I'll serve up to the web for those browsers that don't support JavaScript. – Hugh Powell Apr 21 '15 at 21:09
  • have you tried first if emitted JS runs in the browser ? may be it requires first some call to download `react` itself that would happen with the browser.. – Ashish Negi Apr 22 '15 at 05:18
  • Did you get any errors that could indicate that there was a problem loading ReactJS code? Maybe it uses JS API that is only available in the browser JS? – Piotrek Bzdyl May 02 '15 at 10:15

3 Answers3

1

You'll want to add a preamble to project.clj, e.g. :preamble ["include.js"]. Preambles implicitly reference the /resources directory relative to project.clj. Source: https://github.com/Sparrho/supper/blob/master/project.clj

Within include.js you'll provide a reference to React:

var React = require("react");

Source: https://github.com/Sparrho/supper/blob/master/resources/include.js

For reference, here's the build block from project.clj:

{:id "server"
:source-paths ["src"]
:compiler {:main myapp.core-server
           :target :nodejs
           :output-to "deploy/index.js"
           :output-dir "out-server"
           :preamble ["include.js"] ;; Implicitly nested in /resources                       
           :optimizations :simple
           :language-in :ecmascript5
           :language-out :ecmascript5}}

I've also read that :preamble can reference react.min.js directly as it's been bundled in a WebJar on which Om takes a dependency. Source: https://groups.google.com/forum/#!topic/clojurescript/TL8Cv-jPkzM

tyronep
  • 11
  • 3
1

You could try workaround :preamble ["include.js"] with following content:

module.exports = '';

But it's appropriate only in case when you have only some of cljsjs/ packages (in this case it's cljsjs/react). For example including of cljsjs/jquery 2.1.4 won't work.


Problem appears because of React definition:

!function(e){
    if ("object" == typeof exports && "undefined" != typeof module) {
        module.exports = e();
    }
    else if ("function" == typeof define && define.amd) {
        define([],e);
    }
    else{
        var t;
        t= "undefined" != typeof window ? window
            : "undefined" != typeof global ? global
            : "undefined" != typeof self ? self
            : this,
            t.React = e()
    }
}(function(){/* React definition */})

Environment doesn't have window object in NodeJS but it has module and module.exports. We lie to environment by resetting module.exports to string.

React = require('react') doesn't work out of NpmJS environment with installed react module.


In my opinion the correct solution would be not to include code of React to module, but use this module under NpmJS environment which has react in dependencies.

In this case tyronep's answer is good. We should:

  1. use following :preamble:

    global.React = require('react')
    
  2. don't include cljsjs/react to dependencies

  3. require react in NpmJS dependencies.

Unfortunately if we want to use om then cljsjs/react must be included (that is redundant inclusion)

Community
  • 1
  • 1
chivorotkiv
  • 745
  • 11
  • 27
0

Your current problem is about loading the React module which I'm sure you will solve tweaking your configuration, but you can't call om.core/root on Node. om.core/root mounts your React components in a DOM, which Node lacks. What you want is to define your components, pass them your static data and then call React.renderToString(https://facebook.github.io/react/docs/top-level-api.html#react.rendertostring). Om wraps it with render-to-str.

(def static-data {:text "Hello world!"})

(defn title-component [data owner]
  (reify om/IRender
    (render [_]
      (dom/h1 nil (:text data)))))

(def component-html-as-string
  (dom/render-to-str (title-component static-data)))

Most of Om's value comes from their state handling capabilities, which you won't need for server side rendering (that's why I omitted atom). If what you want is React templating in ClojureScript I suggest you look at Sablono which has a function for static html and a nicer syntax for generating templates than om.dom.

Also, to develop on Node it is better to start from mies-node

sbensu
  • 1,491
  • 10
  • 9