Fork me on GitHub
#shadow-cljs
<
2023-04-07
>
shaunlebron02:04:37

is there a way to make the dev-server auto-open the browser on init

thheller08:04:38

not currently no

Aron11:04:42

macros are weird. and I don't get it why it has to be different for browser builds than as for node-script builds

thheller11:04:38

they aren't?

Aron12:04:00

well, I am not sure what the heck I am doing wrong, but they do behave differently for me

thheller13:04:29

I can look at some code if you want. besides my blog post on the subject I don't have much to add in generic advice

Aron13:04:36

(ns server.main
  (:require
   [ui.hiccup :refer [hcc-server]]
   [ui.html :as ui-html]
   ["express" :as express]
   ["serve-static" :as serve-static]
   ["node:http" :as http]
   ["react" :as react :refer [createElement]]
   ["react-dom/server" :as rdc :refer [renderToPipeableStream]]))

(defonce server (atom nil))

(def serve (serve-static "build", #js {:index false}))


(defn request-handler [^js req ^js res next]
  (let [did-error (atom false)
        stream (atom nil)]

    (if (not (= (.-url req) "/"))
     (serve req res next)
     (reset!
      stream
      (renderToPipeableStream
       (clj->js (hcc-server ui.html/index))
       #js {:onError (fn [e]
                       (reset! did-error true)
                       (js/console.error "Error rendering" e))
            :onAllReady
            (fn []
              (js/console.log "ALL READY"))
            :onShellError
            (fn [err]
              (set! (.-statusCode res) 500)
              (.send res (str "ERROR:" err)))
            :onShellReady
            (fn []
              (set! (.-statusCode res) (if @did-error 500 200))
              (.setHeader res "Content-Type" "text/html")
              (.pipe ^js @stream res)
              (.end res))})))))

(defn start [bs]
  ^:dev/after-load
  (let [app (express)]
    (.use app request-handler)
    (reset! server (http/createServer app))
    (.listen ^js/Node.http.Server @server 8000 (fn [] (println "Server running on port 8000")))
    bs))

Aron13:04:53

server code that I run with node

Aron13:04:04

hcc-server is the macro

Aron13:04:08

I have ui.hiccup cljs

(ns ui.hiccup
  (:require-macros [ui.hiccup] [hicada.compiler :refer [compile]]))
and cljc
(ns ui.hiccup
  (:require 
    [ui.html]
    [hicada.compiler :refer [compile]]))

(defmacro hcc-server [body]
  (hicada.compiler/compile 
    (eval body)
    {:create-element 'react/createElement 
     ;:transform-fn (comp)
     :array-children? false}))

(defmacro hcc-client [body]
  (hicada.compiler/compile 
    body
    {:create-element 'js/React.createElement
     ;:transform-fn (comp)
     :array-children? false}))

Aron13:04:02

notice the eval body that I needed there in hcc-server, without that, it was not working. Now I am trying the client side and it's breaking with eval, which makes sense, but can't get it to work with anything

thheller13:04:47

well, what is it doing in the first place. (clj->js (hcc-server ui.html/index)) this makes no sense to me

thheller13:04:54

why clj->js?

Aron13:04:13

because otherwise react complains that can't really do anything with the returned clojurescript object

thheller13:04:34

so what is this macro supposed to return

thheller13:04:49

body is the ui.html/index SYMBOL when the macro is called

Aron13:04:55

react createElement calls, last code snippet has the macros in full

Aron13:04:14

that should be evaled so that instead of the symbol it's a hiccup edn

thheller13:04:46

ok, but eval evals the code in clojure. I assume this is CLJS code?

thheller13:04:24

what is ui.html/index?

Aron13:04:33

hiccup edn

Aron13:04:46

literally just some vectors with keywords and strings

Aron13:04:04

(def index [:div "x"]) or something like that

Aron13:04:56

but it's large, I can't always type it and maybe I want to use different hiccup edns in multiple projects, so I need a way to require and use them both on the server and on the client to render react nodes

thheller13:04:59

ok, so I assume that your intent is to compile that form. this isn't possible in CLJS.

Aron13:04:17

I am not sure I follow

thheller13:04:15

what is getting called at macro expansion time is (hcc-server ui.html/index)

Aron13:04:19

it works on node

thheller13:04:34

but you want to know the runtime value of ui.html/index, which doesn't work

Aron13:04:38

the server returns the correct html generated from hiccup

Aron13:04:44

no, nothing like that

Aron13:04:02

it's edn. it's literals, totally static.

thheller13:04:22

then move the compile call

Aron13:04:30

the macro should read it in clojure and create the react call

thheller13:04:38

turn that into (def index (html [:div "x"])), so the macro is getting the actual EDN, not a symbol referring to it

Aron13:04:13

again, I want to be able to reuse it, so keeping it in edn form is important

thheller13:04:24

what does reuse mean?

Aron13:04:31

and again, I already made it work in nodejs

Aron13:04:45

so I know it's possible, very confused why you say it's not

thheller13:04:43

I do not know what the hicada compiler is doing, but did you verify that the code you think you get you actually get?

thheller13:04:58

might just be something that behaves the same, but isn't actually the same

Aron13:04:58

yes, I opened up the javascript file

thheller13:04:09

so with the eval?

thheller13:04:26

ok, then I assume the ui.html file is cljc?

Aron13:04:52

i wish it could be .edn

thheller13:04:52

ok, then clojure can actually find the code, but the CLJ variant

Aron13:04:13

yes, but the macro just needs to know the edn, nothing else, doesn't call cljs or js anything

thheller13:04:38

then I don't understand what you are trying to do in the first place. everything seems to behave as you want then?

Aron13:04:01

not really, when I do the same eval code for the client, it breaks 🙂

thheller13:04:33

well, ... this is exactly why I warn against using .cljc code. I assume that is the part you don't correctly understand.

Aron13:04:55

well, i would say it's highly likely i don't understand any of this correctly 🙂

thheller13:04:07

.cljc is two files in one. a CLJS file and a CLJ file. clj eval can ONLY find the CLJ parts, it has no knowledge of CLJS parts

Aron13:04:22

that part I understand

thheller13:04:30

make your life easier and don't use .cljc files at all if you can avoid it

Aron13:04:51

I tried to use your custom reader thing, but it just didn't work at all

thheller13:04:09

can you explain what you are trying to do in the first place?

Aron13:04:25

sorry for not being more specific here, I couldn't get the reader conditionals to activate, that's why I have two macros

thheller13:04:29

don't know why custom reader would factor into anything here, so that statement just confuse me 😛

thheller13:04:50

why are you using .cljc files?

Aron13:04:51

😄 how far should I go back

thheller13:04:16

is your intent in any way to run this in CLJ? or is this purely CLJS running the browser and/or node?

Aron13:04:43

I need to write this macro because of hicada. And doing hiccup needs macro anyway, since I don't want js to evaluate static stuff over and over again

thheller13:04:47

why do you say you'd prefer .edn files?

Aron13:04:51

so that's where the clj comes in

thheller13:04:51

what is it your are trying to do?

thheller13:04:02

why do you want hicada?

Aron13:04:56

because edn is just data, it doesn't matter what runtime. clj cljc cljs and who knows how many others are all context dependent and behave very differently under different builds and runtimes

thheller13:04:39

when it is truly just data then make it a .edn file, nothing stopping you from doing that

Aron13:04:43

i don't want hicada, but manually writing this thing for react is a huge task, and hicada seemed the freshest of all the options for hiccup and clojurescript

thheller13:04:47

but I have my doubt that they are truly just data?

thheller13:04:16

hicada looks like an unmaintained dead library to me? why do you say "freshest"?

Aron13:04:17

it was really difficult to load then the edn files into the client build. node server was very easy of course.

thheller13:04:12

you can use the edn file via macros, they don't need to be loaded at runtime. but I'm still guessing at which problem you are trying to solve exactly

thheller13:04:37

you want to optimize over using just hiccup if I understand correctly?

Aron13:04:11

it seemed the one with recent activity, others were either older or didn't use hiccup or did it with runtime transformation

Aron13:04:41

but I may be wrong about this, not saying I spent more than 1-2 days googling hiccup libraries and reading their docs

Aron13:04:56

yes, I do want to optimize the hiccup parsing to compile time, at least where it's feasible, like with SSR. So everything that can be render on the server, shouldn't then be re-parsed on the client side

thheller13:04:29

thats not what is going to happen? the server spits out html, it doesn't spit out hiccup?

Aron13:04:44

I need to try this edn into macro thing again.

thheller13:04:55

or whatever renderToPipeableStream spits out, I don't actually know.

Aron13:04:04

it spits out html, yes

thheller13:04:18

edn file or not doens't matter to the compilation part one bit

Aron13:04:49

it was a week ago when I was trying to do this, so I don't have all the details. need change the code again.

Aron13:04:47

If I understand you correctly, I should use some clojure/java thing to read the file, then the edn reader to parse it, and then somehow I can give it to the macro, hopefully. 😄

Aron19:04:12

ok, it works, and it's much simpler build wise. thanks again!

seanstrom13:04:12

Does shadow-cljs support hot-reloading in node while targeting esm? Or how would I configure shadow-cljs to support a repl in node while using the esm target?

seanstrom13:04:25

I’m compiling cljs code with shadow, and I’ve configured it with this:

{:output-dir "./cljs-server"
                   :target :esm
                   :runtime :node
                   :js-options {:js-provider :import}
                   :modules {:app.server {:exports {add app.server/add}}}
                   :release {:compiler-options {:optimizations :advanced}}}

seanstrom13:04:11

Is it possible to connect to a running repl after importing the CLJS modules into a node program?

thheller13:04:23

it does not currently

seanstrom14:04:00

Is it possible to use the devtools for the node or npm_module for this? Or does it need to have its own devtools?

seanstrom14:04:01

I would like to try implementing this, can you recommend a good place to start?

thheller14:04:24

ok, in the build config you may set :runtime :custom :devtools {:client-ns your.attempt}

thheller14:04:43

your.attempt is a namespace. you can probably start with a copy of one of the defaults

thheller14:04:13

don't start with this one, as it is the most complicated since it also handles the HUD and CSS reloading stuff

thheller14:04:19

which you don't need it node

thheller14:04:00

feel free to ask questions about them, they all work the same just adapted to specific platforms

thheller14:04:10

they'll connect to the websocket, and then handle the reloading parts

thheller14:04:00

the common bits are extracted out already, the main differences are in the eval parts

thheller14:04:11

than the others, because of the specifics of commonjs

thheller14:04:27

mainly the require exports etc "specials"

thheller14:04:43

for esm things are already in the global scope somewhat

thheller14:04:09

so you could probably take the browser, rip out everything related to HUD and CSS and change it to use the ws package instead of js/WebSocket

thheller14:04:39

at least that is likely how I would start. I have not yet done any work related to this

thheller14:04:14

could maybe also start with the npm-module client, but rip out all the commonjs bits

seanstrom14:04:22

Would it be worth to use a js/WebSocket polyfill based on ws ?

thheller14:04:55

thats all it takes to swap out the impl, yes you could probably polyfill that in different ways

thheller14:04:10

if you remove all DOM/CSS related bits from the browser ns it'll likely just work

seanstrom14:04:39

yup yup that’s what i was feeling it could be

thheller14:04:24

node always makes me want to throw me pc out the window. so I haven't looked at it in a while 😛

thheller14:04:41

esm on node is even more niche

thheller14:04:58

why not just use a different target for node? why does it have to be esm?

thheller14:04:52

or is it again because of the JS parts that need to interact with the CLJS code?

seanstrom14:04:22

yup yup you got it

seanstrom14:04:51

im trying to run the CLJS code in runtimes like node, like Deno or Bun.sh

seanstrom14:04:19

esm is a nice format to design for all of these different tools

thheller14:04:39

deno is also open 😉

thheller14:04:00

esm is ok, the platform specific parts are the problem

thheller14:04:21

ie. node wants ws package, browser wants js/WebSocket, don't even know what deno or bun want

thheller14:04:35

maybe it all works somehow if they all have a js/WebSocket available 😛

seanstrom14:04:50

they’ll probably try to use the node option but deno may try to implement js/WebSocket

seanstrom14:04:05

these runtimes are trying to converge on the web apis even for the server stuff

seanstrom14:04:21

so they could work haha, which would be great!

thheller14:04:37

that'd be great indeed

thheller14:04:55

having everything be platform specific was never great to begin with

seanstrom14:04:27

how should i attempt to test my changes? do still use the shadow-cljs sources in my own source paths? or do I need to build the cli and link that to node_modules?

thheller14:04:46

like I said. you put your.attempt ns in your project in your source path

thheller14:04:57

there is no need to either fork or work on shadow-cljs directly

thheller14:04:23

don't even need the shadow-cljs sources at all. changes to the CLJ parts should not be required in any way

thheller14:04:35

its just a regular CLJS namespace, nothing special about it

seanstrom14:04:21

I thought I would importing shadow-cljs devtools modules if I was copying the node or browser implementation. Can I still re-use some code with my implementation?

thheller14:04:10

I don't understand that question. there are no "shadow-cljs devtools modules". they are just regular CLJS namespaces, that you compile with your build

thheller14:04:24

you can access any of the existing namespaces just fine yes

seanstrom14:04:30

I see, so a namespace like this would be available to my code: shadow.cljs.devtools.client.env?

seanstrom14:04:52

ah sorry, that’s new to me

seanstrom14:04:08

okay i understand now what you’re saying

👍 2
thheller14:04:09

its all just CLJS namespaces, you can even replace existing ones by just putting them on your source path with the correct name

thheller14:04:52

did you get the setup working? any progress?

seanstrom15:04:33

Yup yup I got it setup, just need to tinker on it

seanstrom15:04:57

Okay happy to report that copying the browser version and replacing roughly two lines works so far

seanstrom15:04:38

Probably just need to shake out the unrelated browser stuff and it should be fine I think

thheller15:04:57

good to know

seanstrom16:04:02

Should we try to replace the HUD calls with a server hud?

thheller16:04:23

I never look at the hud anyways, so I wouldn't

seanstrom16:04:37

Okay sounds good, I’ll continue to tweak this until I think it’s ready for a review, then I’ll try to setup a PR