This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-06
Channels
- # announcements (14)
- # babashka (14)
- # beginners (22)
- # calva (56)
- # cider (20)
- # clerk (8)
- # clj-commons (10)
- # clj-kondo (18)
- # cljs-dev (11)
- # clojure (87)
- # clojure-conj (3)
- # clojure-europe (29)
- # clojure-nl (1)
- # clojure-poland (5)
- # clojure-portugal (1)
- # clojurescript (100)
- # data-science (3)
- # datahike (1)
- # datomic (13)
- # events (2)
- # fulcro (10)
- # funcool (2)
- # helix (19)
- # hoplon (6)
- # humbleui (2)
- # hyperfiddle (40)
- # leiningen (5)
- # lsp (22)
- # malli (26)
- # nrepl (2)
- # off-topic (19)
- # reagent (32)
- # releases (1)
- # shadow-cljs (266)
- # spacemacs (6)
- # tools-build (9)
- # vim (1)
Hey @thheller 👋
Can you help me with this error that I’m coming across?
• I’ve been using shadow-cljs version 2.20.18 for a while and I tried upgrading to the latest version.
• Unfortunately, my project started to display warnings from shadow-cljs during hot reloads:
◦ Failed to load app/main.cljs TypeError: goog.provide is not a function
◦ This error seem to originate at version 2.22.0
• My build settings are configured around using the npm-module output alongside a JS bundle tool (parcel).
◦ Here’s my minimal reproduction of the error: https://github.com/seanstrom/cljs-dev-template/tree/debug-build
• I would like to debug this more, but I’m not sure how to setup shadow-cljs for dev.
◦ Any instructions or tips for debugging each commit of shadow-cljs?
Let me know if you need any more information, would really appreciate the help since I’m a bit stuck.
the problem appears to be that there is a different goog
in scope, one that isn't properly initialized. not sure why though.
Based on the exception it seems to be saying that when my app.main.js
file is reloaded it evaluates a goog.provide
expression and doesn’t have that defined.
Parcel is being used to handle the bundling of the npm-module, it should be only importing the assets declared inside of here: https://github.com/seanstrom/cljs-dev-template/blob/debug-build/src/cljs-index.js
From what I can see, after I save a change to main.cljs
, a script is evaluated for the changes by the shadow devtools.
In version 2.21.0 the script will evaluate a script shaped like this:
"var COMPILED = false;
var cognitect=$CLJS.cognitect || ($CLJS.cognitect = {});
var clojure=$CLJS.clojure || ($CLJS.clojure = {});
var cljs=$CLJS.cljs || ($CLJS.cljs = {});
var shadow=$CLJS.shadow || ($CLJS.shadow = {});
var goog=$CLJS.goog || ($CLJS.goog = {});
var app=$ || ($ = {});
var com=$ || ($ = {});
$CLJS.SHADOW_ENV.setLoaded("app.main.js");
goog.provide('app.main');
app.main.render = (function app$main$render(){
var change_me_21185 = "Hello";
var node_21186 = document.getElementById("root");
var text_node_21187 = document.createTextNode(change_me_21185);
node_21186.appendChild(text_node_21187);
return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.prim_seq.cljs$core$IFn$_invoke$arity$2(["[main]: render"], 0));
});
In version 2.22.0 it evaluates a script shaped like this:
"goog.provide('app.main');
app.main.render = (function app$main$render(){
var change_me_21191 = "Good Bye";
var node_21192 = document.getElementById("root");
var text_node_21193 = document.createTextNode(change_me_21191);
node_21192.appendChild(text_node_21193);
return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.prim_seq.cljs$core$IFn$_invoke$arity$2(["[main]: render"], 0));
});
It seems something related to how the devtools code reload npm-modules has changed between these two versions. I noticed in the change logs that there could be some related changes in the release of version 2.22.0
Any ideas what could be happening here?but that is the source of the problem. hot-reload will eval in the global scope, thus use the goog
object. which for some reason only exists partially
var goog = $CLJS.goog = {};
this line should make sure that it is always the same object
Well during startup that’s present, but at reload it’s missing, so goog
becomes the window.goog
and not the $CLJS.goog
right?
this might be the problem. this isn't something shadow-cljs injects. might be parcel doing that?
I added the :runtime :browser
, but it didn’t fix the problem.
I started using the npm-module
format because I couldn’t use the esm
format for similar hot-reloading issues. In the esm target, the hot reloading attempts to redefine an existing namespace and throws an exception. Until recently I didn’t have any issues with npm-module format, but I would prefer esm as an option too.
I’m not sure what :js-provider :external
will do here, but I can try.
In general it seems that maybe the code was relying on that variable statement to always be within the hot reloaded script. I assume it was removed to keep things small, so maybe I can override goog
manually and see if my problems are fixed temporarily.
if (process.env.NODE_ENV !== "production") {
require("../cljs-dist/shadow.cljs.devtools.client.browser.js")
window.goog = $CLJS.goog;
}
When the script is evaluated it seems to use the window.goog because window is the default global context right? Maybe most of the code just uses the $CLJS
namespace most of the time?
Well if window.goog
is suppose to be $CLJS.goog
could that be defined in cljs_env.js
?
I’ll try esm and I can show you the other output that I get, one sec.
but I remember something now. I might actually have broken this not too long ago unintentionally
note sure but I'm guessing it was this commit https://github.com/thheller/shadow-cljs/commit/7d6ca44906143a69eb5999ce23d67641fe870073
I’m trying to debug each commit inside the 2.22.0 release. How do I build the project with all pieces pinned to a specific commit so I can run each commit as a release?
If I make a changes to the source code I do I test the changes with a build. I need the latest changes so I can’t use the old build otherwise it just works
and then add :source-paths ["src/main" "../shadow-cljs/src/main" "../shadow-cljs/target/classes"]
that used to add a wrapper for code to pull in all the "locals", so it didn't use any globals
it doesn't have all the old random hacks that :npm-module
has, so it should be preferred
The compile output for esm can’t be hot reloaded atm, the source is reloaded and throws an error. I would like to use esm but I don’t think it works with the hot reload.
The esm target throws an exception related to namespace already being defined by goog
Okay here’s an another version of the project but with ESM setup: https://github.com/seanstrom/cljs-dev-template/tree/debug-build-esm
This is the kind of error I get when I hot reload:
Error: Namespace "goog.asserts" already declared.
ok, it is time you need to explain what your goal is with all of this. you are using two build tools, each trying to provide basically the same features. and they are interfering with each other big time.
parcel even tries to reload sometimes while shadow-cljs is still writing the file, so I get EOF errors
The goal is to use a JS bundler for bundling JS dependencies and watching reloading JS and CSS. I don’t want to deal with JS externs and other issues with importing JS dependencies through the normal build pipeline, and I want to hot reload css changes.
Yeah I gave up on using Parcel because I cant prevent it from watching the CLJS output. I pushed up a working build configured with the ESM output from Shadow and Vite.
none of that eliminates the need for externs? in fact you'll need MORE if shadow-cljs isn't processing npm dependencies?
What about importing svg files? or Sass files, or any other media types that the JS tooling supports?
The point is to use the JS tooling for pieces of the JS ecosystem that I can’t get from Shadow
And I want to shadow to manage the CLJS stuff because it basically the only option for compiling stuff with hmr (outside of Figwheel and building my own).
Ideally I would have a cljs to esm compiler, with repl and hot-reloading support. And that should be able to work as along as I don’t use to hot reloaders for the CLJS stuff
Because it seems that way shadow hot reloads stuffs is more specific than just a re-eval the whole file right?
your template seems to work, just need to update shadow-cljs. template is using 2.22.0, 2.22.9 works
https://code.thheller.com/blog/shadow-cljs/2019/08/25/hot-reload-in-clojurescript.html
Version 2.22.9 does not work for me with the Parcel JS configuration. It works only if I add the extra window.goog = $CLJS.goog
to it.
but literally. your use case sounds exactly like what :js-provider :external
is designed to do
you don't lose anything. you only lose headaches because vite/parcel/whatever don't need to process the CLJS output
js-provider :external
would only configure the CLJS output to represent JS dependencies as require
expressions right?
That would happen when I import stuff into CLJS that’s JS right? If I have to independent systems of JS and CLJS I need a bundler that works with both. The viewpoint here is that it’s a headache to do JS stuff in the CLJS tooling without going all-in on the CLJS way of doing stuff.
I don’t think we’re talking about the same stuff. Look at the original example, it broke because the semantics of npm-module hot reloading has changed right?
I can add this snippet: window.goog = $CLJS.goog
somewhere in my file, but that seems like a hack
So im trying to avoid my builds exploding because the outputs from CLJS don’t quite mesh well with every other JS bundle tool because of google closure stuff and side effects for declaring namespaces inside of all the modules
No matter how many ways I can configure :js-provider :external
that doesn’t solve the issue of organizing a polyglot project
It seems that the best way to avoid headaches is to disable Vite’s (or Parcels) HMR for any CLJS assets. How is that a hack?
The vite was working for me with 2.22.0, but I’ll also try 2.22.9 just to confirm. Would you say this is a hack when: • Shadow controls all the CLJS reloading still • Vite only controls unrelated CSS and JS stuff that isn’t being touched by the CLJS
no, not a hack. that is a supported use case, which may have rough edges since not many people do this
:js-options {:js-provider :import}
you need to add to the build config once you want to start using actual JS code
vite config you may want to change to ignored: ['**/cljs-dist/cljs-runtime/**'],
, so it'll still pick up new npm depencencies and such
I guess the bonus here is that I’m using the esm format now, so I don’t have that npm-module bug
the problem is that :npm-module
is using the same shadow.cljs.devtools.client.browser
namespace as the :target :browser
does
but they are fundamentally incompatible with each other in how they need to reload stuff
so I'd like to avoid having a bunch of if
in that ns, checking what it is supposed to do
I like that vite only touches the import
paths and nothing else it seems. that is ideal as far as shadow is concerned
Overall, thanks for the help, and sticking through to confirm that this new solution seems more maintainable
For the npm-module stuff, do you think it would be easier to include all of the window.goog = $CLJS.goog
inside of cljs_env.js
? I think before it was inside each script when reloaded, but now it’s the minimal amount of JS update the namespace
i meant all of the possible one’s inside the script? or are they different per script?
ok, long story: once upon a time I decided to try to make :npm-module
be a good citizen and NOT expose any globals whatsoever. how then do you make sure the REPL and hot-reload and CLJS in general still work, since everything in that world assumes to live in the same global scope. so in my younger days I decided to be clever and recreate and fake that "global" scope in every single namespace
it turns out this was a terrible idea, and not actually useful and gets in the way more than I'd like
hmm okay, so we want to avoid that fake global stuff and just define all the global on window?
so instead, everything should live in global during development. and only be local in release
builds
but that also doesn't play with certain tools, such as parcel which seems to have a weird notion about what global
means
in :esm
it just uses globalThis
which is sort of standard and I guess respected by vite
so the choice I have to make in :npm-module
is a) remove all the fake-global stuff or b) keep the hacks and "fix" the hot-reload problem browsers now have
but it is basically rewrite everything :npm-module
related type of thing, and given how many hacks that has I'm not sure it will not break in unexpected ways in other peoples builds actively using it
I'm trying to avoid breaking changes at all costs, but :npm-module
is giving me nightmares at this point. so I guess I might need to do this at some point
Well if we had a global object will all of the definitions you’d want on global, you could pass that into the script eval as the context?
the eval already happens in the global scope, that is precisely the problem after all. it doesn't see the fake global properly, so there are two worlds. one that is fully initialized on startup, one that is partially broken on hot-reload
yup yup, and I’m thinking that when we know something is “reloading”, we can reference the fake global inside script evals arguments
:npm-module
also had a bunch of node
specific things baked in, which always get in the way when not actually building for node
you can't fake a global in eval, can't control how the browser resolves stuff after all
:npm-module
has been by far the most problematic targets of any of them. thats why I hate touching it so much, fix one thing always breaks another 😛
the cleanup should make things much nicer, just need to find and revert all the hacks first
Okay so what’s our best guess as to why the global stuff is incorrect, because Parcel overrides the global?
Yup exactly, though I thought you were saying that window.goog
should already be the same as $CLJS.goog
?
but I only changed how reloading works in the new shadow.cljs.devtools.client.npm-module
(intended for node
), and not the shared browser
ns
yes, because when the code was initially loaded everything was properly on the $CLJS
global object
yes, during normal loading it avoids global
completely except for one actual global $CLJS
which is used to bridge stuff
but after that shadow.cljs.devtools.client.browser
just assumes everything is global, because in all other targets it is used in it is
ahhh so this extra tricky because you’re trying to remove this fake global stuff, but some code still uses it, and new code depends on it not being there at all.
Is it possible to have a separate shadow.cljs.devtools.client.npm-module-legacy
file that adds back scoping stuff?
I would lean towards what you recommended earlier about removing all the fake global stuff and maybe just using globalThis, but that would seem to break anyone using the npm-module stuff right?
can't reliably use globalThis
since it might not be supported in all environments but can probably test for it and fallback for global
if it doesn't exist
Hello there!
I'm building my (template) project skeleton in a monorepo
fashion that will have clj/cljs sources under the src/{clj/cljs}
folders.
I'm using the shadow-cljs feature that reads the dependencies from deps.edn, and it worked well for development by defining a :cljs
alias with my clojurescript dependencies and referencing it with {:deps {:aliases [:cljs]}
in my shadow-cljs.edn.
The problem is that when I also added a clojurescript build step in my build.clj using the shadow-cljs api (shadow.cljs.devtools.api/release app)
the :build
alias could not find my clojurescript dependencies.
What would a proper solution be?
• Should I move all clj and cljs dependencies in the root :deps
of deps.edn and use {:deps true}
in shadow-cljs.edn? Are there any pitfalls or reason I should avoid this?
• Should I duplicate the cljs dependencies in both :cljs
and :build
aliases?
no clue. never used tools.build yet. can't you just activate the cljs alias yourself when calling the build task?
but yes, cljs compilation needs to have the dependencies available, which build tasks by default do not have
another option is to shell out to run shadow-cljs via command line, instead off the CLJ api
yes I thought of the third one too, its a shame that the api does not read the shadow-cljs.edn config
thats not how this works at all though. it absolutely reads the shadow-cljs.edn
config. it just doesn't start a new JVM, since it was never designed for tools.build or how that decided how to do stuff
FWIW tools.build starts a new JVM for CLJ compilation as well. just have to do the same for CLJS.
but how are you running the build task in the first place? isn't it something like clj -X:foo
?
I'm incorporating the shadow-cljs phase into my uberjar phase and I'm calling it like clj -T:build uber
I believe it works, I'm trying to keep as clean as possible the project build commands, just experimenting a bit
either that or swap these https://github.com/clojure/tools.build/blob/f096376cd4e301dbf90ce447fee901397d3490c0/src/main/clojure/clojure/tools/build/tasks/compile_clj.clj#L103-L104
Ok I tried to shell out, and (b/process {:command-args ["npx" "shadow-cljs" "release" "app"]})
gives:
shadow-cljs - config: /home/user/workdir/webapp/clj-fullstack/shadow-cljs.edn
shadow-cljs - starting via "clojure"
Execution error (FileNotFoundException) at clojure.main/main (main.java:40).
Could not locate shadow/cljs/devtools/cli__init.class, shadow/cljs/devtools/cli.clj or shadow/cljs/devtools/cli.cljc on classpath.
Full report at:
/tmp/clojure-15436134260688801745.edn
or the alias not activated. make sure it is in the :cljs
alias and :deps {:aliases [:cljs]}
in shadow-cljs.edn
Another quick question: Why is the shadow-cljs dependency needed in :cljs alias? I thought that when using the shadow-cljs wrapper/binary it injects it itself, although I may need to rtfm again
just better to be in there all the time so all other deps.edn related things can also see it
well it adds itself when using shadow-cljs.edn
:dependencies
, but not deps.edn
or project.clj
so it should still be somewhat close to the version in deps.edn
, but the version in deps.edn
decides all compilation stuff
I suppose I could remove it completely too if I went with clj -M:cljs -m shadow.cljs.devtools.cli release app
in theory yes, although I still recommend installing it as that will still provide other npm packages you may need when using shadow-cljs. i.e. the ws
package when building node stuff
frankly I don't see the point in wrapping one build tool in another, so I would always use shadow-cljs as a separate step
Well in the end you are right, but through this process I get to understand shadow-cljs build process a little better
I'm having trouble with methods on a defclass
object. I want to create a JS class that gets called from JS with the usual foo.bar()
style, but I get
(defclass Foo
(constructor [_this])
Object
(bar [_this] 17))
(let [foo (Foo.)]
(.bar foo))
;=> TypeError: foo.bar is not a function
I'm not sure what I'm missing - this seems like it should work and the https://github.com/thheller/shadow-experiments/blob/master/src/main/shadow/experiments/grove/ui/vlist.cljs#L26 I found seem to call Object
methods that way.ah, to avoid the XYZ problem: what I'm really trying to do is create a subclass of Object
like this
class Foo {
constructor(guts) {
this.guts = guts;
}
raw() {
return this.guts;
}
}
if there's an alternative (something with deftype
?) that would be fine too.hmm. I just got it to work with a basic deftype
; I suppose that solves my problem. but I couldn't get the defclass
to work.
> (require '[shadow.cljs.modern :refer (defclass)])
nil
cljs.user=> (defclass Foo
(constructor [_this])
Object
(bar [_this] 17))
#object[Function]
cljs.user=> (let [foo (Foo.)]
(.bar foo))
17
cljs.user=>
weird. I'm running a node REPL via Conjure, but that shouldn't break this?
that's probable, I suspect I broke my REPL state with an early attempt.
(it seemed like there was no prototype at all. (.-prototype Foo) ;=> {}
)
no rush; I'm unblocked by using deftype
for this simple case.
huh, that's a strange gap. Node is V8 (right?) so I don't see where the gap would be. something about exporting, maybe?
Yeah, only the REPL.
Hi, I am facing an issue with loading our own react components which has newer syntax features such as Object.assign, Object destructuring, spreading, and the nullish coalescing operator. I see the polyfills discussion on https://github.com/thheller/shadow-cljs/discussions/827 and tried with :output-feature-set :es8
but still having the same issue. Here is the dependency versions,
shadow-cljs version: 2.16.12
com.fulcrologic/fulcro version: 3.4.14
react version: 16.13.0
Error in console,
Uncaught TypeError: fulcro_xxxxkit.xxxxkit.ui_box is not a function
at eval (xxxxx_xxxxxx.ui.pages.xxxxx_xxxxx.js:sourcemap:68:477)
at $fulcrologic$fulcro$components$wrapped_render [as wrapped_render] (com.fulcrologic.fulcro.components.js:1547:111)
at Function.xxxxx_xxxxx$ui$pages$xxxxx_xxxxxx$render_Root (xxxxx_xxxx.ui.pages.xxxxx_xxxxx.js:sourcemap:66:42)
Please suggest how to resolve it. Your help will be greatly appreciated. Thanksis that the only error? otherwise impossible to say without seeing code. I mean it says its not a function, did you check what it is? if it actually exists or so?
Yes you are right, it returns empty object
though we have wrapped the component in fulcro
I am wondering it works for other components such as fulcro_xxxxkit.button
Hi @thheller, Here is my code testkit.cljs where wrapped with fulcro
(ns fulcro-atlaskit.testkit
(:require
["@testkit/react" :refer [Box GlobalHeader]]
[com.fulcrologic.fulcro.algorithms.react-interop :as react-interop]))
(def ui-box (react-interop/react-factory Box))
(def ui-global-header (react-interop/react-factory GlobalHeader))
And consuming Here
(ns xxxxx-xxxxxx.ui.pages.xxx-xxxx
(:require
..
[fulcro-atlaskit.testkit :as testkit]))
(defsc Root [this _]
(dom/div
:.container
(dom/h1 (tr " Get started"))
(testkit/ui-box)))
I appreciate your help @thheller , I will debug in the way why getting empty object.