Skip to content
Browse files

change how :react-native dev builds load code

to get source maps sort of working when using debug-in-chrome
  • Loading branch information...
thheller committed Aug 2, 2019
1 parent 5128e24 commit f30668c23eda356649fb0015c2992ee44038cf85
Showing with 218 additions and 39 deletions.
  1. +6 −0 shadow-cljs.edn
  2. +22 −0 src/dev/demo/expo.cljs
  3. +59 −17 src/dev/demo/rn.cljs
  4. +28 −0 src/main/shadow/boot/react-native.js
  5. +103 −22 src/main/shadow/build/targets/react_native.clj
@@ -463,6 +463,12 @@
:devtools {:autoload true
:preload [shadow.expo.keep-awake]}}

{:target :react-native
:init-fn demo.rn/init
:output-dir "out/TestCRNA/app"
:devtools {:autoload true}}

{:target :expo
:init-fn demo.rn/init
@@ -0,0 +1,22 @@
(ns demo.expo
["react" :as react :rename {createElement $}]
["react-native" :as rn :refer (Button Text View)]
[shadow.expo :as expo]

(defn fail []
(throw (ex-info "failed" {})))

(defn render-root []
($ View nil
($ Text nil "Hello World from CLJS! 1")
($ Text nil "Hello World from CLJS! 2")
($ Text nil "Hello World from CLJS! 3")
($ Button #js {:title "Hello World" :onPress fail})))

(defn ^:dev/after-load start []
(expo/render-root (render-root)))

(defn init []
@@ -1,22 +1,64 @@
(ns demo.rn
["react" :as react :rename {createElement $}]
["react-native" :as rn :refer (Button Text View)]
[shadow.expo :as expo]

(defn fail []
(throw (ex-info "failed" {})))

(defn render-root []
($ View nil
($ Text nil "Hello World from CLJS! 1")
($ Text nil "Hello World from CLJS! 2")
($ Text nil "Hello World from CLJS! 3")
($ Button #js {:title "Hello World" :onPress fail})))

(defn ^:dev/after-load start []
(expo/render-root (render-root)))
["react-native" :as rn]
["react" :as react]
["create-react-class" :as crc]))

(def styles
^js (-> {:container
{:flex 1
:backgroundColor "#fff"
:alignItems "center"
:justifyContent "center"}
{:fontWeight "bold"
:fontSize 24
:color "blue"}}

(defn bad-press [e]
(js/console.log "pressed the bad button")
(throw (ex-info "button pressed" {})))

(defn root []
(react/createElement rn/View #js {:style (.-container styles)}
(react/createElement rn/Text #js {:style (.-title styles)} "Hello!")
(react/createElement rn/Button #js {:onPress (fn [e] (bad-press e))
:title "error"})))

(defonce root-component-ref (atom nil))
(defonce root-ref (atom nil))

(defn render-root [root]
(let [first-call? (nil? @root-ref)]
(reset! root-ref root)

(if-not first-call?
(when-let [root @root-component-ref]
(.forceUpdate ^js root))
(let [Root
#js {:componentDidMount
(fn []
(this-as this
(reset! root-component-ref this)))
(fn []
(reset! root-component-ref nil))
(fn []
(let [body @root-ref]
(if (fn? body)

(rn/AppRegistry.registerComponent "TestCRNA" (fn [] Root))))))

(defn start
{:dev/after-load true}
(render-root (root)))

(defn init []
@@ -0,0 +1,28 @@
var SHADOW_ENV = $CLJS.SHADOW_ENV = (function() {
var env = {};

var loadedFiles = {};

env.setLoaded = function(name) {
loadedFiles[name] = true;

env.load = function(opts, paths) {
paths.forEach(function(name) {

env.isLoaded = function(name) {
// this is only used by live-reload checking if it should reload a file
// since all files will always be loaded we don't really need to track this?
return true;
// return loadedFiles[name] || false;

env.evalLoad = function(name, code) {

return env;
@@ -6,7 +6,7 @@
[clojure.spec.alpha :as s]
[shadow.cljs.repl :as repl]
[ :as node]
[ :as comp]
[ :as build]
[ :as shared]
[ :as config]
[ :as build-api]
@@ -16,7 +16,10 @@
[ :as data]
[shadow.cljs.util :as util]
[shadow.cljs.devtools.api :as api]
[ :as build-log]))
[ :as build-log]
[ :as browser])
(:import [ SourceCodeEscapers]
[ Escaper]))

(s/def ::init-fn qualified-symbol?)

@@ -38,7 +41,7 @@
(format "Using IP: %s" addr))

(defn set-server-host [state {:keys [local-ip] :as config}]
(let [server-addr (or local-ip (api/get-server-addr))]
(let [server-addr (or local-ip (api/get-server-addr))]

(util/log state {:type ::server-addr :addr server-addr})

@@ -60,18 +63,14 @@

(-> state
(build-api/with-build-options {:output-dir output-dir})
(assoc-in [:build-options :module-format] :js))
(cond-> dev? (assoc-in [:js-options :require-fn] "$CLJS.shadow$jsRequire"))

(assoc ::output-file output-file
::init-fn init-fn)

{:index (-> {:entries [(output/ns-only init-fn)]}
(not dev?)
(assoc :append-js (output/fn-call init-fn))))})
{:index {:entries [(output/ns-only init-fn)]
:append-js (output/fn-call init-fn)}})

(update :js-options merge {:js-provider :require})
(assoc-in [:compiler-options :closure-defines 'cljs.core/*target*] "react-native")
@@ -87,22 +86,104 @@
(shared/inject-preloads :index config)))))

(defn flush-dev-index [{::keys [output-file] :as state} {:keys [init-fn] :as config}]
(spit output-file
(str "var CLJS = require(\"./cljs_env.js\");\n"
(->> (:build-sources state)
(remove #{output/goog-base-id})
(map #(data/get-source-by-id state %))
(map :output-name)
(map #(str "require(\"./" % "\");" ))
(str/join "\n"))
"\nCLJS." (cljs-comp/munge init-fn) "();")))
(def ^Escaper js-escaper

(defn eval-load-sources [state sources]
(->> sources
(remove #{output/goog-base-id})
(map #(data/get-source-by-id state %))
(map (fn [{:keys [output-name] :as rc}]
(let [{:keys [js] :as output} (data/get-output! state rc)

source-map? (output/has-source-map? output)

(cond-> js
;; FIXME: the url here isn't really used, wonder if there is a way to do something useful here
(str "\n//# sourceURL=http://localhost:8081/app/cljs-runtime/" output-name "\n"
;; "\n//# sourceMappingURL=http://localhost:8081/app/cljs-runtime/" output-name ".map\n"
;; FIXME: inline map is more expensive to generate but saves having to know the actual URL
(output/generate-source-map-inline state rc output nil)

(str "SHADOW_ENV.evalLoad(\"" output-name "\", \"" (.escape js-escaper ^String code) "\");")
(str/join "\n")))

(defn flush-unoptimized!
[{:keys [unoptimizable build-options] :as state}
{:keys [goog-base output-name prepend append sources web-worker] :as mod}]

(let [target
(data/output-file state output-name)

(eval-load-sources state sources)

(str prepend
(->> (for [src-id sources
:let [{:keys [js-require] :as src} (data/get-source-by-id state src-id)]
:when ( src)]
;; emit actual require(...) calls so metro can process those and make them available
(str "$CLJS.shadow$js[\"" js-require "\"] = require(\"" js-require "\");"))
(str/join "\n"))

(if (or goog-base web-worker)
(str unoptimizable
;; always include this in dev builds
;; a build may not include any shadow-js initially
;; but load some from the REPL later
"var shadow$provide = {};\n"
"var $CLJS = global;\n"

"$CLJS.shadow$js = {};\n"
"$CLJS.shadow$jsRequire = function(name) { return $CLJS.shadow$js[name]; };"

(let [{:keys [polyfill-js]} state]
(when (and (or goog-base web-worker) (seq polyfill-js))
(str "\n" polyfill-js)))

"global.CLOSURE_DEFINES = " (output/closure-defines-json state) ";\n"

(let [goog-rc (get-in state [:sources output/goog-base-id])
goog-base (get-in state [:output output/goog-base-id :js])]

(str goog-base "\n"))

" = goog;\n"

(slurp (io/resource "shadow/boot/react-native.js"))
;; create the $CLJS var so devtools can always use it
;; always exists for :module-format :js
"[\"$CLJS\"] =;\n"
;; else

(io/make-parents target)
(spit target out)))

(defmethod build-log/event->str ::flush-dev
[{:keys [module-id] :as event}]
(format "Flush module: %s" (name module-id)))

(defn flush [state mode config]
(case mode
(do (flush-dev-index state config)
(output/flush-dev-js-modules state mode config))
(do (output/flush-sources state)
(doseq [mod (:build-modules state)]
[state {:type ::flush-dev :module-id (:module-id mod)}]
(flush-unoptimized! state mod))))
(output/flush-optimized state))

0 comments on commit f30668c

Please sign in to comment.
You can’t perform that action at this time.