Fork me on GitHub
#shadow-cljs
<
2024-06-26
>
Linus Ericsson12:06:03

I'm building a clojure app with some artifacts compiled using shadow-cljs. I try to build it in some different docker images. Is it somehow possible to first do all the needed dependency fetching in a node container, and then do a releasebuild in a clojure/leiningen-container? - is it possible to a releasebuild without triggering npm, given that all dependencies is already in place, somehow?

thheller12:06:11

why make it so complicated? shadow-cljs doesn't care where the node_modules folder comes from and it doesn't need npm for anything as long as your dependencies are present

🙏 1
Linus Ericsson13:06:27

So a lein-uberjar prep-step to compile the relase builds like: :profiles {:gen {:prep-tasks ^:replace []} :uberjar {:prep-tasks ["clean" ["clean"] "compile" ["with-profile" "+gen,+dev" "run" "-m" "shadow.cljs.devtools.cli" "--npm" "release" "adminspa" "userspa"]] :omit-source true :aot :all :uberjar-name "uberapp.jar" :source-paths ["env/prod/clj"] :resource-paths ["env/prod/resources"]} should not need any node/npm binaries availiable at all, as long as all dependencies are availiable (in node_module and perhaps in jvm classpath)? (That is what I expect, but I might be wrong)

thheller13:06:53

btw if you use 3 backticks

you
can
do
multi-line

awesome 1
thheller13:06:42

you can skip the --npm flag, it isn't needed when you are not actually running via npm

👍 1
Linus Ericsson14:06:25

I think I still need the npm on my path (or something that returns 0 when called), since it seems to be triggered anyway: https://github.com/thheller/shadow-cljs/blob/673e2580889e37f150c6d3afb71e935328ba49e4/src/main/shadow/cljs/devtools/cli_actual.clj#L141-L142 The stacktrace says

running: npm install --save --save-exact @js-joda/[email protected] @js-joda/[email protected] @js-joda/[email protected]
IOException: Cannot run program "npm" (in directory "."): error=2, No such file or directory
	java.lang.ProcessBuilder.start (ProcessBuilder.java:1170)
	java.lang.ProcessBuilder.start (ProcessBuilder.java:1089)
	shadow.cljs.devtools.server.npm-deps/install-deps (npm_deps.clj:113)
	shadow.cljs.devtools.server.npm-deps/install-deps (npm_deps.clj:75)
	shadow.cljs.devtools.server.npm-deps/main (npm_deps.clj:176)
	shadow.cljs.devtools.server.npm-deps/main (npm_deps.clj:162)
	shadow.cljs.devtools.cli-actual/main (cli_actual.clj:141)
	shadow.cljs.devtools.cli-actual/main (cli_actual.clj:132)
	clojure.core/apply (core.clj:671)
	clojure.core/apply (core.clj:662)
	shadow.cljs.devtools.cli-actual/-main (cli_actual.clj:219)
	shadow.cljs.devtools.cli-actual/-main (cli_actual.clj:217)
	clojure.lang.Var.applyTo (Var.java:705)
	clojure.core/apply (core.clj:667)
	clojure.core/apply (core.clj:662)
	shadow.cljs.devtools.cli/-main (cli.clj:75)
	shadow.cljs.devtools.cli/-main (cli.clj:67)
	clojure.lang.Var.invoke (Var.java:399)
	user/eval140 (form-init4803926420733755391.clj:1)
	user/eval140 (form-init4803926420733755391.clj:1)
	clojure.lang.Compiler.eval (Compiler.java:7194)
	clojure.lang.Compiler.eval (Compiler.java:7184)
	clojure.lang.Compiler.load (Compiler.java:7653)
	clojure.lang.Compiler.loadFile (Compiler.java:7591)
	clojure.main/load-script (main.clj:475)
	clojure.main/init-opt (main.clj:477)
	clojure.main/init-opt (main.clj:477)
	clojure.main/initialize (main.clj:508)
	clojure.main/null-opt (main.clj:542)
	clojure.main/null-opt (main.clj:539)
	clojure.main/main (main.clj:664)
	clojure.main/main (main.clj:616)
	clojure.lang.Var.applyTo (Var.java:705)
	clojure.main.main (main.java:40)
Caused by:
IOException: error=2, No such file or directory
	java.lang.ProcessImpl.forkAndExec (ProcessImpl.java:-2)
	java.lang.ProcessImpl.<init> (ProcessImpl.java:295)
	java.lang.ProcessImpl.start (ProcessImpl.java:225)
	java.lang.ProcessBuilder.start (ProcessBuilder.java:1126)
	java.lang.ProcessBuilder.start (ProcessBuilder.java:1089)
	shadow.cljs.devtools.server.npm-deps/install-deps (npm_deps.clj:113)
	shadow.cljs.devtools.server.npm-deps/install-deps (npm_deps.clj:75)
	shadow.cljs.devtools.server.npm-deps/main (npm_deps.clj:176)
	shadow.cljs.devtools.server.npm-deps/main (npm_deps.clj:162)
	shadow.cljs.devtools.cli-actual/main (cli_actual.clj:141)
	shadow.cljs.devtools.cli-actual/main (cli_actual.clj:132)
	clojure.core/apply (core.clj:671)
	clojure.core/apply (core.clj:662)
	shadow.cljs.devtools.cli-actual/-main (cli_actual.clj:219)
	shadow.cljs.devtools.cli-actual/-main (cli_actual.clj:217)
	clojure.lang.Var.applyTo (Var.java:705)
	clojure.core/apply (core.clj:667)
	clojure.core/apply (core.clj:662)
	shadow.cljs.devtools.cli/-main (cli.clj:75)
	shadow.cljs.devtools.cli/-main (cli.clj:67)
	clojure.lang.Var.invoke (Var.java:399)
	user/eval140 (form-init4803926420733755391.clj:1)
	user/eval140 (form-init4803926420733755391.clj:1)
	clojure.lang.Compiler.eval (Compiler.java:7194)
	clojure.lang.Compiler.eval (Compiler.java:7184)
	clojure.lang.Compiler.load (Compiler.java:7653)
	clojure.lang.Compiler.loadFile (Compiler.java:7591)
	clojure.main/load-script (main.clj:475)
	clojure.main/init-opt (main.clj:477)
	clojure.main/init-opt (main.clj:477)
	clojure.main/initialize (main.clj:508)
	clojure.main/null-opt (main.clj:542)
	clojure.main/null-opt (main.clj:539)
	clojure.main/main (main.clj:664)
	clojure.main/main (main.clj:616)
	clojure.lang.Var.applyTo (Var.java:705)
	clojure.main.main (main.java:40)

thheller14:06:33

this only runs if there is no package.json present with those packages already listed

thheller14:06:47

you can turn it off entirely via :npm-deps {:install false} in shadow-cljs.edn

donavan19:06:01

Good idea to check blame 🙂

thheller19:06:19

basically I wanted a clean thread that nobody could mess with via bindings 😛

thheller19:06:42

fixed a bunch of issues with cider when running things over the repl

donavan19:06:03

Haha… but I want to mess with the thread with bindings 😛

donavan19:06:40

I was trying to use a dynamic var inside a hook…

thheller20:06:16

likely means you should be using a hook 😛

donavan06:06:27

Yeah I know there are many ways of doing what I wanted, however given the constraints it was the most expedient way of achieving my goal and I was surprised it didn’t work… made me question my understanding of thread local bindings though which is good 🙂

donavan07:06:21

Would https://github.com/thheller/shadow-cljs/pull/1189 be an acceptable PR for those that want to live dangerously?

thheller07:06:53

I generally do not make any changes where I don't know what they'll be used for

thheller07:06:10

so start with explaining that, then we can talk about potential solutions 😛

donavan07:06:46

Well, the above should ™️ be a clean refactor… As it stands one can’t use dynamic vars inside build hooks running as part of a watch as the bindings are not conveyed to the thread when it’s created. As to why I want to use a dynamic var in a hook… I’m working on a codebase that is moving towards being a monorepo and there is some interim kludging with dynamic vars in the build process. However they don’t work in hooks. I know there are many ways to in the end achieve what I want, dynamic vars are just the quickest and smallest way to do it. The above PR means there’s a much smaller with-redefs needed to do it without affecting any other users of shadow. Happy if you want to close the PR, it’s not an important PR and not one that is preventing me from doing anything really.

thheller07:06:40

what are you doing in that hook? I regret adding hooks, since 99% of the time they are used they are used for stuff they were not meant for

thheller07:06:11

adding a dynamic var to be accessed in a hook makes me kinda certain it should not be a hook in the first place

thheller07:06:25

dunno what a monorepo has to do with any of this

donavan07:06:06

The hooks are generally file system access, creating assets and resources (some that do actually need information about build state and created js files in particular). The monorepo means file paths change, build configs are no longer at the root of the repo etc., relative paths change and those changing paths are currently done via dynamic vars. Eventually this should all be removed.

m_traven18:06:04

Help, I'm having a tough time getting shadow-cljs to work for what should be a simple case. I have two separate projects, one is a cljs library (blockoid) and the other one wants to include it. Both are compiled with shadow-cljs, but I can't get it to come out right, despite endless fiddling with options. I either get errors like these, or a result that won't load. blockoid.js is generated by the first shadow-cljs build. [:app] Compiling ... [2024-06-26 10:31:25.623 - INFO] :shadow.build.npm/js-invalid-requires - {:resource-name "node_modules/blockoid/target/blockoid.js", :requires [{:line 899, :column 6}]} Closure compilation failed with 3 errors --- node_modules/blockoid/target/blockoid.js:299 Closure primitive methods (goog.provide, goog.require, goog.define, etc) must be called at file scope. --- node_modules/blockoid/target/blockoid.js:300 Closure primitive methods (goog.provide, goog.require, goog.define, etc) must be called at file scope. --- node_modules/blockoid/target/blockoid.js:301 Closure primitive methods (goog.provide, goog.require, goog.define, etc) must be called at file scope. I already tried removing lein-shadow (since that seems deprecated), didn't help. Adding :js-options {:js-provider :require} to the config of the using package made the errors go away, but then the code wouldn't load. Similarly with :external Changing build type to :esm had no effect. As you can see, I'm reduced to blindly searching the space of configs, because I have little understanding of how this is supposed to work. Any help appreciated, more details on request.

thheller19:06:07

yeah seems like you misunderstand how libraries work in CLJS

thheller19:06:20

shadow-cljs is not involved in library creation, since libraries are raw uncompiled CLJS files

thheller19:06:41

so you compiling it and then trying to include compiled files is not a supported use case

thheller19:06:25

no target or other option would allow this. especially not via node_modules

thheller19:06:41

if you are using deps.edn you can use :local/root or :git/url dependencies directly

thheller19:06:00

otherwise you may need to publish to clojars or something

m_traven21:06:28

OK...but there are those options in shadow-cljs for generating a :node-library or :npm-module , that confuses me. Happy to do it the way you describe if it makes my life easier. Publishing to a local or public mvn repo is not a problem.

m_traven21:06:54

Also the blockoid package refers to other, non cljs npm packages, so how do they get packaged up? To be clear, the inclusion is blockly → blockoid → enflame, where • blockly is a non-cljs google library packaged in npm • blockoid is my library, wraps blockly in cljs/re-frame (https://github.com/CANDELbio/blockoid/tree/master) • enflame is a browser application that uses the above I may just give up on blockoid and incorporate it into enflame to avoid these problems, but seems like I shouldn't have to.

hifumi12321:06:04

node-library and npm-module are intended for consumption in pure JavaScript, particularly in Node.js scripts. (note that npm-module is semi-deprecated in favor of the ESM target) You may be able to import compiled ClojureScript code with them, but I wouldn't recommend it because each compiled artifact contains a copy of cljs.core, so you will be loading cljs.core multiple times. Secondly, the compiled code will have munged symbol names and only expose whatever functions were marked with ^:export in CLJS that's why the recommended option is to require cljs files directly rather than consume compiled output

hifumi12321:06:25

in your case, what I'd do is create a JAR of blockoid, containing CLJS sources, not compiled JS. enflame then depends on this JAR (e.g. install locally then add to lein dependencies) and installs npm dependencies used by blockoid

hifumi12321:06:02

you can make cljs automagically install npm dependencies with a deps.cljs file, but I don't recommend that since it forces consumers of the library into the exact versions specified in the file, and in general people dont like it when requiring a library results in arbitrary npm invocation (In fact, all of my shadow-cljs projects have :npm-deps {:install false} set, preferring to explicitly install dependencies myself)

m_traven22:06:51

Thanks for the tips, certainly happy to do it that way if it fits the model better.