Fork me on GitHub
#shadow-cljs
<
2024-02-06
>
Ertugrul Cetin09:02:14

I'm currently encountering an issue with my Shadow CLJS project where I get an Uncaught ReferenceError: shadow$bridge is not defined error. I believe this problem is related to my configuration for external JavaScript dependencies and tree shaking. I am attempting to apply tree shaking to reduce the size of my npm dependencies in the build output. Trying for release mode. npx shadow-cljs watch app Below is the relevant part of my Shadow CLJS configuration:

:builds {:app {:target :browser
               :output-dir "resources/public/js/compiled"
               :asset-path "/js/compiled"
               :modules {:app {:init-fn my-app.ui.core/init}}
               :js-options {:js-provider :external
                            :external-index "target/index.js"
                            :external-index-format :esm}
               :dev {:compiler-options {:closure-defines {re-frame.trace.trace-enabled? true
                                                          day8.re-frame.tracing.trace-enabled? true}
                                        :warnings {:redef false}}}
               :release {:build-options {:ns-aliases {day8.re-frame.tracing day8.re-frame.tracing-stubs}}}}}
I'm trying to use the :external-index-format :esm to generate ESM code, but I'm facing an issue where the shadow$bridge function seems to be missing or not properly exported, as indicated by the error message. The error I receive is:
Uncaught ReferenceError: shadow$bridge is not defined
;var tE = shadow$bridge("react");
I would greatly appreciate any insights or suggestions on what might be causing this issue and how to resolve it.

thheller09:02:54

that is the error you get when you are not loading the external code first

thheller09:02:08

i.e. the output produced by compiling target/index.js

thheller09:02:01

(or if you are loading that it might be doing something async?)

Ertugrul Cetin10:02:20

I've moved target/index.js into my resources folder and updated index.html like this;

And I got;
Uncaught SyntaxError: Cannot use import statement outside a module
app.js:540 Uncaught ReferenceError: shadow$bridge is not defined
    at app.js:540:307
    at app.js:1078:4
this is my first time using this tree-shaking feature of shadow-cljs so not sure what I'm doing 🙂

thheller10:02:47

ok I think you misunderstand what this is about

thheller10:02:18

:js-provider :external means that the :external-index "target/index.js file MUST be processed by some other third party tool, such as webpack

thheller10:02:50

:external-index-format :esm only enables webpack to do tree shaking of that file, shadow-cljs does not perform any processing of it whatsoever, besides generating it

Ertugrul Cetin10:02:04

Hmm I got it now, thanks! So in order to reduce the bundle size (using tree shaking), I should use external tool?

thheller10:02:47

I don't know. that entirely depends on which npm libraries you use. shadow-cljs still performs "tree shaking" for CLJS code, just not npm

thheller10:02:38

so, yes if you use a lot of npm libraries, which still need to support this themselves, then yes webpack is an option to shrink the bundle size

Ertugrul Cetin10:02:31

I get it now, thank you so much for your help and time!

pez20:02:52

Can I invoke different :deps alisases for a release build than for a dev one? So when I do shadow-cljs watch :app I want it to be {:deps {:aliases [:dev]} …} and for a shadow-cljs release :app I want {:deps {:aliases [:prod]} …} .

thheller20:02:58

I feel like I answered this before. no, not with shadow-cljs. but you can of course just run clj -A:prod -M -m shadow.cljs.devtools.cli release app which is identical to shadow-cljs release app, just with the switched alias

thheller20:02:07

dunno why you want to do it though. I generally recommend not doing this. IMHO there are no valid reasons to do it

pez23:02:53

Yes, I think we’ve been here before. I tried to search for it, even, but didn’t find it. I think last time you showed me why I didn’t need it for what I wanted to achieve, so it may be that I am holding things wrong again. The reason this time is that I want different app configs for prod and dev, and am currently going with this in deps.edn:

:aliases {:dev {:extra-paths ["config/dev"]}
           :prod {:extra-paths ["config/prod"]}}

thheller07:02:07

define "app config", what exactly is in those paths?

pez07:02:49

My idea was to have separate config.edn files there and then slurp them in a macro, like so:

(ns app.config
  #?(:clj (:require [ :as io]
                    [clojure.edn :as edn]))
  #?(:cljs (:require-macros [app.config :refer [get-config]])))

#?(:clj
   (defmacro config-get [k]
     (let [config (-> "config.edn"
                      io/resource
                      slurp
                      edn/read-string)]
       `(get ~config ~k {}))))

(comment
  (config-get :api/endpoints)
  :rcf)
But maybe it is just easier to use cljs files and control it from an environment variable.

thheller07:02:35

what I'd advise instead is doing (def config-str (if ^boolean js/goog.DEBUG (rc/inline "development.edn") (rc/inline "production.edn"))) and delaying the parsing to runtime

thheller07:02:59

the compiler will eliminate the development config for release builds

thheller07:02:23

or, if you'd rather do that without having both configs in the build at dev time

thheller07:02:44

use (:shadow.build/mode &env) in the macro to determine whether its a :dev or :release build

thheller07:02:24

also use https://clojureverse.org/t/using-none-code-resources-in-cljs-builds/3745 as otherwise shadow-cljs won't know you are inlining your config like that

thheller07:02:32

and won't recompile when your config changes

pez08:02:18

Thanks! Totally appreciate this! 🙏

thheller08:02:45

all frankly way better options than switching the classpath

🙏 1
thheller08:02:25

or what I prefer and think is the best option is to not have such config in the build at all

thheller08:02:56

rather supply it at runtime, not statically compiled in

thheller08:02:29

but that kinda depends on what you are doing. your server could provide it, your html could, many ways to do it

pez08:02:32

Html could work in my case. Love having a lot of options that all are better than switching the classpath. 😃

thheller08:02:38

<script type="text/edn" id="app-config">{:good-old "edn"}</script> in the html

thheller08:02:09

(.-textContent (js/document.getElementById "app-config")) and reading that

pez09:02:18

Sorry, wrong thread.

pez10:02:52

I went with the html embedded config for now, btw. Thanks again!

👍 1