2

I want to compile my .cljs file for both browser and node.js environments, to get server side rendering. As I understand, there's no way to define cljs env in compile time with reader macro conditions like:

#?(:clj ...)
#?(:cljs ...)

so, I can't easily tell compiler to process something like #?(:cljs-node ...) in node.js env.

Second option I see here is to develop a macro file which will define env at compile time. But how to define that current build is targeting node.js? May be, I could pass some params somehow to compiler or get :target compiler param?

Here are my boot files:

application.cljs.edn:

{:require  [filemporium.client.core]
 :init-fns [filemporium.client.core/init]} 

application.node.cljs.edn:

{:require [filemporium.ssr.core]
 :init-fns [filemporium.ssr.core/-main]
 :compiler-options
 {:preamble ["include.js"]
  :target :nodejs
  :optimizations :simple}}
zarkone
  • 1,335
  • 10
  • 16

2 Answers2

2

I am not aware of a public API to achive this. However, you might use cljs.env/*compiler* dynamic var in your macro to check the target platform (i.e. NodeJS vs browser) configured with :target in your :compiler-options and either emit or suppress the code wrapped in the macro:

(defn- nodejs-target?
  []
  (= :nodejs (get-in @cljs.env/*compiler* [:options :target])))

(defmacro code-for-nodejs
  [& body]
  (when (nodejs-target?)
    `(do ~@body)))

(defmacro code-for-browser
  [& body]
  (when-not (nodejs-target?)
    `(do ~@body)))

(code-for-nodejs
  (def my-variable "Compiled for nodejs")
  (println "Hello from nodejs"))

(code-for-browser
  (def my-variable "Compiled for browser")
  (println "Hello from browser"))
Piotrek Bzdyl
  • 12,965
  • 1
  • 31
  • 49
  • Thank you, exactly what I was looking for! But one problem I have with this is `:require` in `ns`. How to require namespaces depending on env? – zarkone Nov 27 '17 at 10:00
  • Currently I see the only way is to use `require` not from `ns` macro, but directly – zarkone Nov 27 '17 at 10:02
  • Hmm, that might be an issue depending on how CLJS compiler handles it. You might try to move `:require` out of the `ns` form and move it to a standalone `(require ...)` wrapped in one of the macros and see if it works. – Piotrek Bzdyl Nov 27 '17 at 10:02
  • These internals have changed, on my machine the current test is `(= "nodejs" (get-in @cljs.env/*compiler* [:options :closure-defines 'cljs.core/*target*]))` for `org.clojure/clojurescript {:mvn/version "1.11.60"}` – Dustin Getz Dec 30 '22 at 16:29
1

Here is up to date code working on org.clojure/clojurescript {:mvn/version "1.11.60"}

[Updated 2023/4/13: improve classpath hygiene]

(ns contrib.cljs-target
  "Do not guard this require to cljs only, it contains clj macros. It is safe to 
require from clj and cljs due to careful consideration below."
  #?(:cljs (:require-macros [contrib.cljs-target]))
  ;(:require cljs.env) -- clojurescript must be on classpath during server macroexpansion (AOT or runtime depending on config)
  #?(:cljs (:require [goog.object :as object])))

; preferred runtime check for target through public API https://cljs.github.io/api/cljs.core/STARtargetSTAR
#?(:cljs (defn nodejs? [] (= cljs.core/*target* "nodejs")))
#?(:cljs (defn browser? [] (= cljs.core/*target* "default")))

;(defmacro do-cljs [& body] (when (some? (:js-globals &env)) `(do ~@body)))

; undocumented hack, only works in macros. https://stackoverflow.com/a/47499855
#?(:clj (defn- cljs-target []
          ; don't force app to :require clojurescript at runtime on the server
          ; (It's okay if you do, it just means clojurescript must be on server classpath)
          (let [compiler @(requiring-resolve 'cljs.env/*compiler*)]
            (get-in @compiler [:options :closure-defines 'cljs.core/*target*]))))

(defmacro do-nodejs  [& body] (if     (= "nodejs" (cljs-target)) `(do ~@body)))
(defmacro do-browser [& body] (if-not (= "nodejs" (cljs-target)) `(do ~@body)))
Dustin Getz
  • 21,282
  • 15
  • 82
  • 131