Fork me on GitHub
#shadow-cljs
<
2023-11-15
>
itaied05:11:15

hello, following https://shadow-cljs.github.io/docs/UsersGuide.html#target-node-test , how can I execute a specific test or test file / suite?

thheller06:11:08

run node the-output.js --help

itaied06:11:34

When I println I don't see it in the console. How can I debug my tests / run println?

thheller06:11:22

don't know what you mean. whereever you run node the-output.js it should show the println

borkdude10:11:09

If I switch :runtime :node to :runtime :browser in my ESM project (since I want to import the .js into a index.html file for testing), shadow begins to complain about:

Module Entry "cljs.pprint" was moved out of module ":cljs.pprint".
It was moved to ":compiler" and used by #{:cljs.pprint :squint_tests :node.nrepl_server :compiler}.
I have moved cljs.pprint to its own module since I don't want to have it in the main .js module

thheller10:11:47

the error simply means that cljs.pprint is required in multiple modules and the configured hierarchy doesn't work

borkdude10:11:59

compiler is the main module

borkdude10:11:12

I think it might be due to shadow using it in its own stuff

borkdude10:11:21

This used to be the case for node too which you fixed

borkdude10:11:38

if I remember correctly

thheller10:11:55

yes, it likely is because of the devtools injected into the browser. it does a bunch more stuff than the node one (e.g. the hud)

borkdude10:11:36

I can disable that right?

thheller10:11:59

if you don't want a repl or hot-reload yes

borkdude10:11:06

yes that's fine

thheller10:11:17

:devtools {:enabled false} in the build config

borkdude10:11:03

that fixed the problem, awesome, thanks

thheller10:11:22

FWIW :runtime :node basically also just disabled this since that doesn't exist still 😛

thheller10:11:11

:browser actually exists, so that just enabled it. when I eventually get to actually implementing :runtime :node it'll break as well since the devtools themselves require cljs.pprint

borkdude10:11:47

I'm still dealing with the root problem though, I'm getting some kind of warning about "fs" in the browser which I cannot copy paste. I'm pretty sure this part of my code doesn't use "fs"

thheller10:11:34

well something must be, shadow-cljs doesn't

borkdude10:11:47

when I do a "release" build it works

borkdude10:11:21

this is why I switched to :runtime :browser

borkdude10:11:34

but to no avail

borkdude10:11:21

I can make a small repro if you're interested

thheller10:11:23

how do I get it to the failure state?

borkdude10:11:53

I pushed a branch here: https://github.com/squint-cljs/squint/tree/module-in-browser Run bb dev (or the equivalent shadow command for this bb task) And then host a web server in the root http-server --dir . on port 8090 And then visit localhost:8090 (or change the port in index.html

borkdude10:11:02

I can make a smaller repro if that's better for you

borkdude10:11:09

When you run bb build instead, the example works

thheller10:11:09

can I do it without bb? not installed

borkdude10:11:18

yes, just look inside the bb.edn for what to run

thheller10:11:46

dev (t/watch-squint) that doesn't tell me much 😛

borkdude10:11:50

oh sorry :)

borkdude10:11:14

(defn watch-squint []
  (fs/create-dirs ".work")
  (fs/delete-tree ".shadow-cljs/builds/squint/dev/ana/squint")
  (spit ".work/config-merge.edn" (shadow-extra-test-config))
  (bump-core-vars)
  (shell "npx shadow-cljs --aliases :dev --config-merge .work/config-merge.edn watch squint"))

thheller10:11:23

ah yeah found that

borkdude10:11:29

fuck, this is hard to do without running bb ;)

borkdude10:11:00

just let me make a smaller repro later today if this is too much hassle for you

thheller10:11:17

I just make the changes to the build config manually I guess

thheller11:11:00

works for me? can't see any fs errors?

borkdude11:11:52

with watch?

thheller11:11:17

{:deps {:aliases [:cljs]}
 :dev-http {3001 "."}
 :builds
 {:squint
  {:js-options {;; don't bundle any npm libs
                :js-provider :import}
   :compiler-options {:infer-externs :auto}
   :target :esm
   :runtime :browser
   :output-dir "lib"
   :devtools {:enabled false}
   :modules
   {:compiler {:exports
               {compileString squint.compiler/compile-string}}
    :compiler.sci {:depends-on #{:compiler :compiler.node}
                   :init-fn squint.compiler.sci/init}
    :compiler.node {:depends-on #{:compiler}
                    :exports
                    {compileFile squint.compiler.node/compile-file-js
                     compileString squint.compiler.node/compile-string-js}}
    :cljs.pprint {:entries [cljs.pprint]
                  :depends-on #{:compiler}}
    :node.nrepl_server {:depends-on #{:compiler.node :cljs.pprint}
                        :exports {startServer squint.repl.nrepl-server/start-server}}
    :cli {:depends-on #{:compiler :compiler.node}
          :init-fn squint.internal.cli/init}}
   :build-hooks [(shadow.cljs.build-report/hook
                   {:output-to "report.html"})]}}}

thheller11:11:30

npx shadow-cljs watch squint yes

thheller11:11:52

index.html changed to import { compileString } from '/lib/compiler.js'

borkdude11:11:04

ah I used /index.js

borkdude11:11:20

I'll try yours

thheller11:11:43

also works as it just reexports the same thing

thheller11:11:23

what might be relevant here and what I don't have because of no bb is this (bump-core-vars) thing

borkdude11:11:24

yeah. weird, perhaps some kind of caching problem

thheller11:11:25

whatever that does

borkdude11:11:54

ok, I'll check some more. gotta run now. thanks for verifying on your side

borkdude11:11:19

bump-core-vars checks squint/core.js for what exports are defined there and dumps it in an edn file

thheller11:11:46

not sure if I'm supposed to do anything special to trigger this

thheller11:11:08

I can click the compile! button and it spits out some code and an alert

thheller11:11:26

so seems working to me

borkdude11:11:46

yeah, weird. I'll try cloning in a fresh directory later today

borkdude10:11:28

@U05224H0W Sure enough, I got it working when I make a fresh clone. In my regular checkout, no matter what I do: delete .shadow-cljs, .work, the "fs" problem keeps coming back. Like, wtf :)

borkdude10:11:49

But thanks for double checking again

borkdude10:11:13

I'll just swap the fresh clone with my regular dev folder checkout and be done with it

borkdude10:11:57

crap, no, I made a mistake, my index.html was loading my non-local version. problem also persists with the fresh checkout

borkdude10:11:27

maybe it's due to this:

$ rg 'from "fs"'
cljs-runtime/shadow.esm.esm_import$fs.js
3:import * as esm_import$fs from "fs";

borkdude10:11:13

commenting out:

// import "./cljs-runtime/shadow.esm.esm_import$fs.js";
// SHADOW_ENV.setLoaded("shadow.esm.esm_import$fs.js");
// import "./cljs-runtime/shadow.esm.esm_import$path.js";
// SHADOW_ENV.setLoaded("shadow.esm.esm_import$path.js");
in lib/compiler.js makes the thing work, but not idea why those are in there

borkdude10:11:25

I suspect this is due to the test module also being compiled and fs + path (which are used in there) are maybe pushed to the (most common) compiler module

borkdude11:11:06

although when I just remove those lines, the tests still work

thheller11:11:33

well, yes the test file has a direct require for fs?

thheller11:11:36

not sure why its moved to the compiler module though, let me check

thheller11:11:45

it is moved to compiler because :compiler.node and :squint_tests require it and their only common other dependency is :compiler

thheller11:11:24

you should be able to add a :node {:entries [] :depends-on #{:compiler}} module and have the node things depend on that to catch all the node things

borkdude11:11:36

it isn't necessary for shadow to move these deps anywhere since they are built-in to node right?

thheller11:11:04

well due to the way imports are handled it is necessary

borkdude11:11:13

ok, I'll try your recommendation

borkdude11:11:44

can you put js libs in entries?

thheller11:11:46

unfortunately still have to work arround the closure compiler and hiding any traces of ESM 😛

thheller11:11:54

you can, but not necessary

borkdude11:11:18

I don't have to put anything in entries?

thheller11:11:21

just use :entries []. that should be enough. entries is allowed to be empty in this case yes

borkdude11:11:38

it worked! thanks

borkdude20:11:30

In a similar project, I ran into the same "fs" issue. I tried to introduce a :node module, but this gives me:

Module Entry "clojure.string" was moved out of module ":clojure.string".
It was moved to ":cljs.core" and used by #{:cljs.core :clojure.string :compiler :cli}.
Note sure how to deal with that. This is the shadow-cljs.edn (in the branch playground). https://github.com/squint-cljs/cherry/blob/playground/shadow-cljs.edn#L36

borkdude20:11:01

npx shadow-cljs watch cherry
will give a repro (no bb scripts involved to make it easier)

thheller20:11:42

well its the same reason

thheller20:11:01

you are trying to force clojure.string into a separate module, but the module :depends-on structure doesn't allow it

thheller20:11:14

so the compiler has to move it someplace it fits, but can't because you forced it

thheller20:11:55

adding the :node module in this instance does absolutely nothing

thheller20:11:21

in the other build the :node module was used as a collection spot where the namespace "mover" can put stuff

thheller20:11:35

a namespace can only be in one module, and the "mover" has to pick where to put it

borkdude20:11:36

is there something I can do to still have the clojure.string module but not make this complain in watch? it's only a watch problem

thheller20:11:31

no clue why its getting moved

thheller20:11:22

add :devtools {:enabled false :console-support false}

borkdude20:11:03

that's it 🙌

thheller20:11:14

what are you after?

thheller20:11:58

I mean would it make more sense to get an output like :npm-module would produce? i.e. a .js file per CLJS ns?

thheller21:11:11

feels like that is what you are going for there?

thheller21:11:27

just in a much more manual way than :npm-module would need?

borkdude21:11:04

I want to have ES6 output

thheller21:11:10

yes, I get that. but it looks like you are trying to get a .js file per namespace

thheller21:11:22

nevermind for a second that :npm-module isn't ESM

thheller21:11:50

but what if there was a mode for :esm where you didn't need to specify :exports and instead just give a list of namespaces you want ESM-ified

thheller21:11:52

ie as in :entries [cljs.core clojure.string clojure.walk ...] and you get a ESM cljs.core.js, clojure.string, etc

thheller21:11:02

with all public vars exported as actual ESM export

thheller21:11:04

I mean :npm-module has the rule of needing ^:export which you can't add to those namespaces above

thheller21:11:13

but it could just as well pick any public var in the namespace

thheller21:11:56

not sure how useful that all would be in the end, but to me that is what you are going for there with that config setup

borkdude21:11:53

that's true, I'm exporting core, string, walk, etc because cherry is going to import that as its standard library via import (ESM). Exporting all public vars from a namespace is something that could be handy but not sure how many more I'll add to this project

borkdude21:11:34

also I may want to exclude stuff too

borkdude21:11:57

I'm programmatically creating the configuration for this reason

borkdude21:11:02

but for the repro, I didn't

borkdude21:11:17

now I'm back to the "fs" problem with cherry too btw. inserting the node module as I did with squint doesn't help, I'm seeing:

import "./cljs-runtime/shadow.esm.esm_import$fs.js";
SHADOW_ENV.setLoaded("shadow.esm.esm_import$fs.js");
in lib/compiler.js

thheller07:11:32

I don't know how to explain it further. any JS require is basically a namespace in how shadow-cljs is designed

thheller07:11:18

every namespace can only be in one module, so your namespace require structure decide what goes into which module

thheller07:11:33

and if your config moves stuff into places it can't use then things break

thheller07:11:27

introducing the extra :node module just gives the compiler a place to put stuff, that still satisfies the dependency graph

thheller07:11:23

the :node name here has no special naming for the compiler, it is just an extra node in the graph

thheller07:11:05

so in cherry you either need to make whatever module also depend on :node, or change your require structure

borkdude07:11:11

That’s what I figured too, all clear on that. The compiler module has no fs require and also no transitive fs require. Still I get this issue. I understand that node isn’t a special name, just an extra place for moving stuff to

thheller07:11:52

thats the wrong way at looking at it, the compiler module doesn't need a fs require. the compiler module is the first common dependency module it can be moved to, thus it ends up there

👍 1
borkdude07:11:50

Can I lock fs to the node module so I can debug what other modules must depend on node? (AFAIK I already have this currently, just not the locking down of fs).

thheller08:11:37

sure, just :entries ["fs"], at least I think that should work. never actually tried 😛

borkdude08:11:25

I think I tried that yesterday but I’ll try in an hour or so. Thank you for your patience :-)

thheller08:11:57

if you have a repro I'm happy to take a look, maybe I can think of a better error description. that error message has always been less then helpful :P

borkdude09:11:57

alright, I have :node {:entries ["fs"] :depends-on #{:compiler :cljs.core}} which isn't complained about, but also I still get the "fs" error message in the browser console 😆 I'll try to make a repro

borkdude09:11:04

This is what node.js looks like:

import "./compiler.js";
import "./cljs.core.js";
import "./cljs-runtime/shadow.module.node.prepend.js";
SHADOW_ENV.setLoaded("shadow.module.node.prepend.js");
import "./cljs-runtime/shadow.module.node.append.js";
SHADOW_ENV.setLoaded("shadow.module.node.append.js");

borkdude09:11:06

ah when I change "fs" to fs I get:

Module Entry "fs" was moved out of module ":node".
It was moved to ":compiler" and used by #{:cherry.tests :node :cli}.
Now we're talking

borkdude09:11:42

yep, all good now!

borkdude09:11:03

cli didn't depend on node yet, which was the issue

borkdude09:11:06

thanks a bunch!

Ryan16:11:23

We’re moving our front-end app deployment to Github Actions.. and as such I’m refactoring how it all works.. presently we inject a few environment variables with some babashka tasks before we call npx shadow-cljs release :target, but I’m wondering if there’s a mechanism for passing parameters into the build, then injecting via hooks to simplify our tooling.. is there a facility for passing params into a release and getting at them with compile hooks to handle the injection before compile step? Thanks in advance!

borkdude16:11:33

You can do this with a macro possibly, or perhaps goog-define will work too? Or do you change the shadow config using an env var?

borkdude16:11:06

> but you should consider using --config-merge instead. Gotcha, that's what I also use in combination with bb

thheller16:11:30

many options to do pretty much anything, without more specific details of what you actually need I cannot say what would be best

thheller16:11:04

build hooks are definitely NOT recommended. I regret adding them since they are used for so much stuff they shouldn't be used for.

🙏 1
Ryan16:11:59

Thanks for the pointers, super helpful

Ryan16:11:33

trying to configure a few endpoint URLs at build time, and were using a very convoluted way to doing it involving babashka, S3, dynamically building the index file.. all sorts of stuff.. trying to get it way simpler to make the whole building-in-a-container thing a bit less complicated 🙂

thheller16:11:01

why do runtime configuration at build time at all?

thheller16:11:14

why not use actual runtime configuration?

borkdude16:11:40

or you can use a macro or goog-define ;)

Ryan16:11:16

Ah, not enough coffee yet, I meant build-time!

thheller16:11:42

anything like endpoint URLs are what I call runtime configuration in general I keep out of the build config at all

thheller16:11:17

and instead often load it via html, or have the runtime fetch some json/edn/transit or whatever on startup

thheller16:11:32

allows changing stuff easily without even having to recompile

Ryan16:11:15

Our deployment story is several single-tenant instances of the front-end app, and we don’t want to deal with the client changing which tenant its pointed at.. but I mean your approach makes sense

thheller16:11:45

well if the client wants to change something you are not stopping them via some build constants 😛

Ryan16:11:10

well, ok, there is that 😛

borkdude16:11:34

env.clj

(defmacro env [s]
  (System/getenv s))
app.cljs
(:require-macros [env :refer [env]])

(js/fetch (env "CLIENT_URL"))

thheller16:11:34

this is a bad idea, do not do this. caching is not aware of this, thus will not recognize that something may need recompilation and give you cached output

borkdude16:11:13

I agree with Thomas, but if you're not able to do it at runtime, that's also an option. Yeah, this is true about caching, but you can disable that for specific namespaces or you can bust stuff in .shadow-cljs related to that thing ;)

borkdude16:11:27

and else, goog-define is really what is made for this I guess

thheller16:11:59

yeah just don't. if you want environment variables use what is provided. otherwise your build output will be unreliable and annoying to debug.

borkdude16:11:22

"use what is provided" - what exactly? goog-define?

thheller16:11:48

all the links I linked above. yes, for environment variables goog-define + shadow/env

borkdude16:11:10

does vanilla CLJS also cache macro usage?

thheller16:11:59

there is not such thing as a "macro usage cache". there is a namespace cache, based on a few things the compiler decides to either use the cache (not expand macros at all) or recompile (actually expand macros)

thheller16:11:13

I don't know what the heuristics in regular CLJS are anymore, so can't say

thheller16:11:36

but given how many people run CI systems caches are often wiped completely after a build, so that point may be irrelevant

borkdude16:11:39

btw FWIW I also injected these configs into the HTML when I worked on an app the needed this kind of config

borkdude16:11:15

Ryan16:11:19

so many things to consider… this is all great food-for-thought.. we already leverage some env stuff in the index file directly.. which is at least easy to sanity check with view-source.. and getting them there via goog-define seems like a good way to go.. thank you both for all your insights.. my job would be no fun at all without bb and shadow-cljs 🙂

🎉 1
jeaye18:11:18

Hey folks. I've added an nginx reverse proxy in front of shadow's local file serving, as well as some local BE services, to handle self-signed TLS for me. Everything's peachy except for shadow's websocket connection, which is failing on startup. Based on my combing the docs, I'm not seeing anything regarding special handling of secure websockets through a reverse proxy, but perhaps some of you have done a similar thing and have worked around this. Error message included in 🧵. My understanding of reverse proxying here is that shadow shouldn't have to care if TLS is being used on the front end of the proxy, since that's gone by the time it gets to shadow. Perhaps that's different with websockets, though. To be fair, the proxying of files from shadow is working well, so this is indeed websocket specific and I'm likely missing something.

jeaye18:11:52

I suppose it's worth verifying that shadow connects its websocket to the same port as the file serving, but on the /api/ws path, correct?

jeaye18:11:12

In case it's useful, I'm proxying using something like this:

# Front-end.
  server {
    listen 8000 ssl;
    http2 on;

    include /etc/nginx/snippets/ssl-params.conf;
    include /etc/nginx/snippets/self-signed.conf;

    location / {
      proxy_pass ;
      proxy_http_version 1.1;
      proxy_set_header Host ;
      proxy_redirect  ;
      proxy_redirect  ;
    }

    location /api/ws {
      proxy_pass ;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }

    access_log on;
    error_log /var/log/nginx/error.log debug;
  }

thheller20:11:35

what is the error that occurs? the screenshot is missing the actual error?

thheller20:11:40

the default will take the document host and combine it with the shadow-cljs port

thheller20:11:17

so if this nginx is running on a different host this likely doesn't work?

thheller20:11:38

you can set :devtools {:use-document-host false} in your build config to disable that and make it connect to localhost instead

jeaye20:11:09

Hey! Thanks so much for the response. I was thinking that the screenshot was the actual error. Just an exception in attempt-connect!. There is no other info around that.

jeaye20:11:36

Lemme try that document host flag.

jeaye20:11:10

Ohhhh, my man! That works. 🙂

jeaye20:11:33

I'll explore with/without proxy configurations to ensure everything still works when it's set to false. Again, thanks for your time.

thheller20:11:38

see https://shadow-cljs.github.io/docs/UsersGuide.html#proxy-support btw, if you really want to proxy the websocket over your nginx

jeaye20:11:15

I originally looked at that, but I don't think I interpreted it correctly. Now knowing the issue, I suppose you're saying that if I set :devtools-url that I don't need to set :use-document-host?

thheller20:11:39

either one is fine really, just in some setups the actual host shadow-cljs is running on may not be accessible. so the :devtools-url would allow you to specify what it should talk to, i.e. your proxy

thheller20:11:52

if its directly reachable :use-document-host is enough

jeaye20:11:14

Perfect. Thank you. 🙂

thheller20:11:25

say you want to access the app over wlan on your phone or something

thheller20:11:32

it trying to access localhost won't work

jeaye20:11:57

Understood.