├── .tool-versions ├── assets ├── icon.png ├── favicon.png ├── splash.png └── adaptive-icon.png ├── babel.config.js ├── deps.edn ├── src ├── app │ ├── not_found.cljs │ ├── tabs │ │ ├── index.cljs │ │ ├── explore.cljs │ │ └── layout.cljs │ └── layout.cljs └── shadow │ └── build │ └── targets │ └── expo_router.clj ├── dev └── preload.cljs ├── .gitignore ├── shadow-cljs.edn ├── patches └── expo-router+4.0.17.patch ├── app.json ├── package.json ├── README.md └── LICENSE.md /.tool-versions: -------------------------------------------------------------------------------- 1 | clojure 1.11.1.1105 2 | java openjdk-18.0.2.1 3 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/react-native-cljs-expo-router/master/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/react-native-cljs-expo-router/master/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/react-native-cljs-expo-router/master/assets/splash.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/react-native-cljs-expo-router/master/assets/adaptive-icon.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.12.0"} 2 | org.clojure/clojurescript {:mvn/version "1.11.60"} 3 | com.pitch/uix.core {:mvn/version "1.3.0"} 4 | thheller/shadow-cljs {:mvn/version "2.28.19"}} 5 | :paths ["src" "dev"]} 6 | -------------------------------------------------------------------------------- /src/app/not_found.cljs: -------------------------------------------------------------------------------- 1 | (ns app.not-found 2 | (:require [expo-router :as r] 3 | [expo-status-bar :refer [StatusBar]] 4 | [react-native :as rn] 5 | [uix.core :as uix :refer [$ defui]])) 6 | 7 | (defui ^:export root [] 8 | (js/console.log "NOT FOUND") 9 | ($ r/Stack.Screen {:options #js {:title "Ooops!"}})) 10 | -------------------------------------------------------------------------------- /dev/preload.cljs: -------------------------------------------------------------------------------- 1 | (ns preload 2 | (:require [react-native :as rn])) 3 | 4 | ;; Forwards cljs build errors to React Native's error view 5 | (defn ^:export build-notify [{:keys [type report]}] 6 | (when (= :build-failure type) 7 | (js/console.error (js/Error. report)))) 8 | 9 | ;; Disable fast refresh, since CLJS does this differently 10 | (set! (.-onFastRefresh rn/DevSettings) (fn [& _args])) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *.jar 4 | *.class 5 | /lib/ 6 | /classes/ 7 | /target/ 8 | /checkouts/ 9 | .lein-deps-sum 10 | .lein-repl-history 11 | .lein-plugins/ 12 | .lein-failures 13 | .nrepl-port 14 | .cpcache/ 15 | .lsp/ 16 | .shadow-cljs/ 17 | node_modules/ 18 | .clj-kondo/.cache 19 | /public/js 20 | /public/main.css 21 | .idea 22 | *.iml 23 | .expo/ 24 | .shadow-cljs/ 25 | .cp_cache/ 26 | .clj-kondo/ 27 | .idea/ 28 | .expo/ 29 | out/ 30 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps true 2 | :builds {:app 3 | {:target :expo-router 4 | :root-ns app.layout 5 | :output-dir "out" 6 | :compiler-options {:source-map-path "out/" 7 | :source-map-include-sources-content true 8 | :warnings-as-errors true} 9 | :devtools {:preloads [uix.preload preload] 10 | :build-notify preload/build-notify}}}} 11 | -------------------------------------------------------------------------------- /src/app/tabs/index.cljs: -------------------------------------------------------------------------------- 1 | (ns app.tabs.index 2 | (:require [expo-router :as r] 3 | [expo-status-bar :refer [StatusBar]] 4 | [react-native :as rn] 5 | [uix.core :as uix :refer [$ defui]])) 6 | 7 | (defui ^:export root [] 8 | (js/console.log "TAB INDEX") 9 | ($ rn/View {:style {:flex 1 10 | :justify-content :center 11 | :align-items :center}} 12 | ($ rn/Text {:style {:font-size 24 13 | :font-weight 600}} 14 | "This is Home tab"))) 15 | -------------------------------------------------------------------------------- /src/app/tabs/explore.cljs: -------------------------------------------------------------------------------- 1 | (ns app.tabs.explore 2 | (:require [expo-router :as r] 3 | [expo-status-bar :refer [StatusBar]] 4 | [react-native :as rn] 5 | [uix.core :as uix :refer [$ defui]])) 6 | 7 | (defui ^:export root [] 8 | (js/console.log "TAB EXPLORE") 9 | ($ rn/View {:style {:flex 1 10 | :justify-content :center 11 | :align-items :center}} 12 | ($ rn/Text {:style {:font-size 24 13 | :font-weight 600}} 14 | "This is Explore tab"))) 15 | -------------------------------------------------------------------------------- /src/app/layout.cljs: -------------------------------------------------------------------------------- 1 | (ns app.layout 2 | (:require [expo-router :as r] 3 | [expo-status-bar :refer [StatusBar]] 4 | [react-native :as rn] 5 | [uix.core :as uix :refer [$ defui]] 6 | [app.not-found] 7 | [app.tabs.layout])) 8 | 9 | (defui ^:export root [] 10 | (js/console.log "MAIN LAYOUT") 11 | ($ :<> 12 | ($ StatusBar {:style "auto"}) 13 | ($ r/Stack 14 | ($ r/Stack.Screen {:name "(tabs)" :options #js {:headerShown false}}) 15 | ($ r/Stack.Screen {:name "+not-found"})))) 16 | -------------------------------------------------------------------------------- /src/app/tabs/layout.cljs: -------------------------------------------------------------------------------- 1 | (ns app.tabs.layout 2 | (:require [expo-router :as r] 3 | [expo-status-bar :refer [StatusBar]] 4 | [react-native :as rn] 5 | [uix.core :as uix :refer [$ defui]] 6 | [app.tabs.index] 7 | [app.tabs.explore])) 8 | 9 | (defui ^:export root [] 10 | (js/console.log "TABS LAYOUT") 11 | ($ r/Tabs 12 | {:screenOptions #js {:headerShown false}} 13 | ($ r/Tabs.Screen {:name "index" 14 | :options #js {:title "Home"}}) 15 | ($ r/Tabs.Screen {:name "explore" 16 | :options #js {:title "Explore"}}))) 17 | -------------------------------------------------------------------------------- /patches/expo-router+4.0.17.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/expo-router/build/qualified-entry.js b/node_modules/expo-router/build/qualified-entry.js 2 | index 5e17f1b..d034ec1 100644 3 | --- a/node_modules/expo-router/build/qualified-entry.js 4 | +++ b/node_modules/expo-router/build/qualified-entry.js 5 | @@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.App = void 0; 7 | // This has to be the string "expo-router/_ctx" as we resolve the exact string to 8 | // a different file in a custom resolver for bundle splitting in Node.js. 9 | -const _ctx_1 = require("expo-router/_ctx"); 10 | +const _ctx_1 = require("../../../out/index"); 11 | const react_1 = __importDefault(require("react")); 12 | const ExpoRoot_1 = require("./ExpoRoot"); 13 | const head_1 = require("./head"); 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "TestoApp", 4 | "slug": "testo-app", 5 | "scheme": "your-app-scheme", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "icon": "./assets/icon.png", 9 | "userInterfaceStyle": "light", 10 | "splash": { 11 | "image": "./assets/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "assetBundlePatterns": ["**/*"], 16 | "ios": { 17 | "supportsTablet": true 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/adaptive-icon.png", 22 | "backgroundColor": "#ffffff" 23 | } 24 | }, 25 | "web": { 26 | "favicon": "./assets/favicon.png" 27 | }, 28 | "newArchEnabled": true, 29 | "plugins": ["expo-router"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "concurrently --raw -k \"npm:expo:start\" \"npm:cljs:dev\"", 4 | "expo:start": "expo start --clear", 5 | "cljs:dev": "clojure -M -m shadow.cljs.devtools.cli watch app", 6 | "cljs:release": "clojure -M -m shadow.cljs.devtools.cli release app", 7 | "postinstall": "patch-package" 8 | }, 9 | "main": "expo-router/entry", 10 | "dependencies": { 11 | "expo": "~52.0.32", 12 | "expo-constants": "~17.0.5", 13 | "expo-linking": "~7.0.5", 14 | "expo-router": "~4.0.17", 15 | "expo-status-bar": "~2.0.1", 16 | "react-native": "0.76.7", 17 | "react-native-safe-area-context": "4.12.0", 18 | "react-native-screens": "~4.4.0", 19 | "react-native-web": "~0.19.13" 20 | }, 21 | "devDependencies": { 22 | "concurrently": "^8.2.1", 23 | "patch-package": "^8.0.0", 24 | "react": "^18.3.1", 25 | "react-refresh": "^0.14.0" 26 | }, 27 | "name": "TestoApp" 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An example of using [expo-router](https://docs.expo.dev/router/introduction/) in ClojureScript RN app. 2 | 3 | ## Upgrading existing project 4 | 5 | 1. Install expo-router in your project, [follow instructions here](https://docs.expo.dev/router/installation/#manual-installation) 6 | 2. Patch expo-router to point to JS entry point generated from your CLJS code 7 | - A patch for expo-router@4.0.17 is included in this repo [patches/expo-router+4.0.17.patch](patches/expo-router+4.0.17.patch) 8 | - Follow [this guide](https://dev.to/zhnedyalkow/the-easiest-way-to-patch-your-npm-package-4ece) to patch expo-router package 9 | 3. Add `:root-ns app.layout` setting in your `shadow-cljs.edn` build config 10 | - `app.layout` is root layout of your app 11 | 4. Set `:target :expo-router` in `shadow-cljs.edn` build config and make sure to put `shadow.build.targets.expo-router` namespace on class path, this ns adds `:expo-router` build target. 12 | 5. Make sure that your `layout`, `tabs` and pages namespaces export UI components, via `^:export` meta. Those components will be automatically rendered by expo router. 13 | 14 | > Unlike in Expo, none of cljs namespaces are automatically required, except project's entry point, which means you still have to require them somewhere. In this example `layout` namespaces at every level of routing hierarchy require neighboring namespaces and child layout namespaces. -------------------------------------------------------------------------------- /src/shadow/build/targets/expo_router.clj: -------------------------------------------------------------------------------- 1 | (ns shadow.build.targets.expo-router 2 | (:refer-clojure :exclude (flush)) 3 | (:require [clojure.set :as set] 4 | [clojure.string :as str] 5 | [cljs.compiler :as cljs-comp] 6 | [cljs.analyzer :as cljs-ana] 7 | [clojure.spec.alpha :as s] 8 | [shadow.cljs.repl :as repl] 9 | [shadow.build.node :as node] 10 | [shadow.build :as build] 11 | [shadow.build.targets.shared :as shared] 12 | [shadow.build.config :as config] 13 | [shadow.build.api :as build-api] 14 | [shadow.build.modules :as modules] 15 | [shadow.build.output :as output] 16 | [clojure.java.io :as io] 17 | [shadow.build.data :as data] 18 | [shadow.cljs.util :as util] 19 | [shadow.cljs.devtools.api :as api] 20 | [shadow.build.log :as build-log] 21 | [shadow.build.targets.browser :as browser]) 22 | (:import [com.google.javascript.jscomp.deps SourceCodeEscapers] 23 | [com.google.common.escape Escaper])) 24 | 25 | (s/def ::root-ns symbol?) 26 | 27 | (s/def ::target 28 | (s/keys 29 | :req-un 30 | [::root-ns 31 | ::shared/output-dir])) 32 | 33 | 34 | (defmethod config/target-spec :react-native [_] 35 | (s/spec ::target)) 36 | 37 | (defmethod config/target-spec `process [_] 38 | (s/spec ::target)) 39 | 40 | (defmethod build-log/event->str ::server-addr 41 | [{:keys [addr]}] 42 | (format "RN app will be using IP: %s" addr)) 43 | 44 | (defn set-server-host [state config] 45 | (let [addr (api/get-configured-server-addr) 46 | addrs (api/get-server-addrs)] 47 | 48 | (cond 49 | addr 50 | (assoc-in state 51 | [:compiler-options :closure-defines 'shadow.cljs.devtools.client.env/server-host] 52 | (str addr)) 53 | 54 | (= 1 (count addrs)) 55 | (assoc-in state 56 | [:compiler-options :closure-defines 'shadow.cljs.devtools.client.env/server-host] 57 | (str (first addrs))) 58 | 59 | :else 60 | (-> state 61 | ;; need to set this, otherwise defaults to localhost which is never valid 62 | (assoc-in [:compiler-options :closure-defines 'shadow.cljs.devtools.client.env/server-host] "") 63 | (assoc-in 64 | [:compiler-options :closure-defines 'shadow.cljs.devtools.client.env/server-hosts] 65 | (->> (api/get-server-addrs) 66 | (str/join ","))))))) 67 | 68 | (defn normalize-chunk-def [mode x] 69 | (let [append-key (if (= :dev mode) :append :append-js)] 70 | (cond 71 | (qualified-symbol? x) 72 | {:depends-on #{:index} 73 | :entries [(output/ns-only x)] 74 | append-key (str "\nmodule.exports = " (cljs-comp/munge x) ";\n")} 75 | 76 | (and (map? x) 77 | (qualified-symbol? (:export x))) 78 | (-> x 79 | (update :depends-on util/set-conj :index) 80 | (assoc :entries [(output/ns-only x)]) 81 | (update append-key str "\nmodule.exports = " (cljs-comp/munge (:export x)) ";\n")) 82 | 83 | :else 84 | (throw (ex-info "invalid :chunks config" {:x x}))))) 85 | 86 | (defn add-chunk-modules [modules mode chunks] 87 | (reduce-kv 88 | (fn [modules chunk-id chunk-def] 89 | (let [chunk-def (normalize-chunk-def mode chunk-def)] 90 | (assoc modules chunk-id chunk-def))) 91 | modules 92 | chunks)) 93 | 94 | (defn prepend-str [a b] 95 | (str b a)) 96 | 97 | (defn add-module-loaded-calls [modules] 98 | (reduce-kv 99 | (fn [m module-id module] 100 | (update-in m [module-id :append-js] 101 | prepend-str (str "\nshadow.cljs.devtools.client.env.module_loaded('" (name module-id) "');\n"))) 102 | modules 103 | modules)) 104 | 105 | (defn configure 106 | [{:keys [worker-info] :as state} mode {:keys [chunks root-ns output-dir] :as config}] 107 | (let [output-dir 108 | (io/file output-dir) 109 | 110 | output-file 111 | (io/file output-dir "index.js") 112 | 113 | dev? 114 | (= :dev mode) 115 | 116 | enabled? 117 | (not (false? (get-in config [:devtools :enabled]))) 118 | 119 | build-worker? 120 | (and enabled? (= :dev mode) worker-info) 121 | 122 | modules 123 | (-> {:index {:entries [root-ns]}} 124 | (cond-> 125 | (seq chunks) 126 | (-> (update :index assoc :prepend "var $APP = global.$APP = {};\n") 127 | (add-chunk-modules mode chunks)) 128 | 129 | build-worker? 130 | (add-module-loaded-calls)))] 131 | 132 | (io/make-parents output-file) 133 | 134 | (-> state 135 | (build-api/with-build-options {:output-dir output-dir}) 136 | 137 | (assoc ::output-file output-file 138 | ::root-ns root-ns) 139 | 140 | (build-api/configure-modules modules) 141 | 142 | (cond-> 143 | (seq chunks) 144 | (-> (assoc-in [:compiler-options :rename-prefix-namespace] "$APP"))) 145 | 146 | (update :js-options merge {:js-provider :require}) 147 | (update-in [:compiler-options :externs] util/vec-conj "shadow/cljs/externs/npm.js") 148 | (assoc-in [:compiler-options :closure-defines 'cljs.core/*target*] "react-native") 149 | 150 | ;; need to output sources directly to the :output-dir, not nested in cljs-runtime 151 | (update :build-options dissoc :cljs-runtime-path) 152 | 153 | (cond-> 154 | (:worker-info state) 155 | (-> (shared/merge-repl-defines config) 156 | (set-server-host config) 157 | (update-in [::modules/config :index :entries] shared/prepend 158 | '[shadow.cljs.devtools.client.react-native])) 159 | 160 | (nil? (get-in config [:compiler-options :output-feature-set])) 161 | (assoc-in [:compiler-options :output-feature-set] (if dev? :es5 :es2020)) 162 | 163 | dev? 164 | (shared/inject-preloads :index config))))) 165 | 166 | (def ^Escaper js-escaper 167 | (SourceCodeEscapers/javascriptEscaper)) 168 | 169 | (defn generate-eval-js [{:keys [build-sources] :as state}] 170 | (let [gen-source-map? (not (false? (get-in state [:compiler-options :source-map])))] 171 | (reduce 172 | (fn [state src-id] 173 | (cond 174 | (= src-id output/goog-base-id) 175 | state 176 | 177 | ;; already generated 178 | (get-in state [:output src-id :eval-js]) 179 | state 180 | 181 | :else 182 | (let [{:keys [output-name] :as rc} (data/get-source-by-id state src-id) 183 | {:keys [js] :as output} (data/get-output! state rc) 184 | source-map? (and gen-source-map? (output/has-source-map? output)) 185 | 186 | code 187 | (cond-> js 188 | source-map? 189 | ;; FIXME: the url here isn't really used, wonder if there is a way to do something useful here 190 | (str "\n//# sourceURL=http://localhost:8081/app/" output-name "\n" 191 | ;; "\n//# sourceMappingURL=http://localhost:8081/app/cljs-runtime/" output-name ".map\n" 192 | ;; FIXME: inline map saves having to know the actual URL 193 | (output/generate-source-map-inline state rc output "")))] 194 | 195 | 196 | ;; pre-cache for later so it doesn't get regenerated on hot-compiles 197 | (assoc-in state [:output src-id :eval-js] 198 | (str "SHADOW_ENV.evalLoad(\"" output-name "\", \"" (.escape js-escaper ^String code) "\");"))))) 199 | state 200 | build-sources))) 201 | 202 | (defn eval-load-sources [state sources] 203 | (->> sources 204 | ;; generated earlier to avoid regenerating all the time 205 | (map #(get-in state [:output % :eval-js])) 206 | (str/join "\n"))) 207 | 208 | (defn fake-expo-fs [state ns-root] 209 | (let [re (re-pattern (str "^" ns-root)) 210 | defs (->> state :compiler-env :cljs.analyzer/namespaces vals 211 | (into #{} 212 | (comp 213 | (mapcat (comp vals :defs)) 214 | (filter (comp :export :meta)) 215 | (filter (comp #(str/starts-with? (str %) ns-root) :name)) 216 | (map (juxt :name :file)))))] 217 | (->> defs 218 | (map (fn [[sym path]] 219 | [(-> path 220 | (str/replace re ".") 221 | (str/replace #"layout\.clj[cs]$" "_layout.js") 222 | (str/replace #"not_found\.clj[cs]$" "+not-found.js") 223 | (str/replace #"tabs\/" "(tabs)/") 224 | (str/replace #"\.clj[cs]$" ".js")) 225 | (cljs-comp/munge sym)]))))) 226 | 227 | (defn ->js-object [entries] 228 | (str "{" 229 | (->> entries 230 | (map (fn [[k v]] (str "\"" k "\": " v))) 231 | (str/join ", ")) 232 | "}")) 233 | 234 | (defn bootstrap-expo-context [fs-obj] 235 | (str "\n" 236 | "var _expo_components = " fs-obj ";" 237 | "var __uix_react = require('react');" 238 | "export var ctx = function(path) { 239 | if (_expo_components[path]) { 240 | return { default: (props) => __uix_react.createElement(_expo_components[path], props) }; 241 | } 242 | }; 243 | ctx.keys = () => Object.keys(_expo_components);")) 244 | 245 | (defn inject-expo-context [state mod] 246 | (when-let [ns-root (some-> state :shadow.build/config :root-ns str (str/split #"\.") first)] 247 | (let [ns-root-munged (-> ns-root cljs-comp/munge str) 248 | js-str (bootstrap-expo-context (->js-object (fake-expo-fs state ns-root-munged)))] 249 | (update mod :append str js-str)))) 250 | 251 | (defn flush-unoptimized! 252 | [state mod] 253 | 254 | (let [{:keys [goog-base output-name prepend append sources web-worker] :as mod} 255 | (inject-expo-context state mod) 256 | 257 | target 258 | (data/output-file state output-name) 259 | 260 | source-loads 261 | (eval-load-sources state sources) 262 | 263 | js-requires 264 | (into #{} 265 | (for [src-id sources 266 | :let [{:keys [type] :as src} (data/get-source-by-id state src-id)] 267 | :when (= :cljs type) 268 | :let [js-requires (get-in state [:compiler-env ::cljs-ana/namespaces (:ns src) :shadow/js-requires])] 269 | js-require js-requires] 270 | js-require)) 271 | 272 | out 273 | (str prepend 274 | (->> (for [src-id sources 275 | :let [{:keys [js-require] :as src} (data/get-source-by-id state src-id)] 276 | :when (:shadow.build.js-support/require-shim src)] 277 | ;; emit actual require(...) calls so metro can process those and make them available 278 | (str "$CLJS.shadow$js[\"" js-require "\"] = function() { return require(\"" js-require "\"); };")) 279 | (str/join "\n")) 280 | "\n\n" 281 | 282 | (->> js-requires 283 | (map (fn [require] 284 | (str "$CLJS.shadow$js[\"" require "\"] = function() { return require(\"" require "\"); };"))) 285 | (str/join "\n")) 286 | 287 | "\n\n" 288 | source-loads 289 | append) 290 | 291 | out 292 | (if (or goog-base web-worker) 293 | (str "var $CLJS = global;\n" 294 | "var shadow$start = new Date().getTime();\n" 295 | "var shadow$provide = global.shadow$provide = {};\n" 296 | 297 | ;; needed since otherwise goog/base.js code will goog.define incorrectly 298 | "var goog = global.goog = {};\n" 299 | "global.CLOSURE_DEFINES = " (output/closure-defines-json state) ";\n" 300 | 301 | (let [goog-base (get-in state [:output output/goog-base-id :js])] 302 | (str goog-base "\n")) 303 | 304 | (let [{:keys [polyfill-js]} state] 305 | (when (and (or goog-base web-worker) (seq polyfill-js)) 306 | (str "\n" polyfill-js 307 | "\nglobal.$jscomp = $jscomp;"))) 308 | 309 | (slurp (io/resource "shadow/boot/react-native.js")) 310 | "\n\n" 311 | ;; create the $CLJS var so devtools can always use it 312 | ;; always exists for :module-format :js 313 | "goog.global[\"$CLJS\"] = goog.global;\n" 314 | "\n\n" 315 | out) 316 | ;; else 317 | out)] 318 | 319 | (io/make-parents target) 320 | (spit target out))) 321 | 322 | (defmethod build-log/event->str ::flush-dev 323 | [{:keys [module-id] :as event}] 324 | (format "Flush module: %s" (name module-id))) 325 | 326 | (defn flush [state mode config] 327 | (case mode 328 | :dev 329 | (let [state (generate-eval-js state)] 330 | (do (output/flush-sources state) 331 | (doseq [mod (:build-modules state)] 332 | (util/with-logged-time 333 | [state {:type ::flush-dev :module-id (:module-id mod)}] 334 | (flush-unoptimized! state mod))))) 335 | :release 336 | (-> state 337 | (update :shadow.build.closure/modules (partial mapv #(inject-expo-context state %))) 338 | output/flush-optimized)) 339 | 340 | state) 341 | 342 | (defn process 343 | [{::build/keys [mode stage config] :as state}] 344 | (case stage 345 | :configure 346 | (configure state mode config) 347 | 348 | :compile-prepare 349 | (node/replace-goog-global state) 350 | 351 | :flush 352 | (flush state mode config) 353 | 354 | state)) 355 | 356 | 357 | (comment 358 | (shadow.cljs.devtools.api/compile :expo-ios)) 359 | 360 | (comment 361 | (shadow.cljs.devtools.api/watch :expo-ios {:verbose true})) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | --------------------------------------------------------------------------------