Fork me on GitHub
#shadow-cljs
<
2024-03-10
>
magra15:03:37

I need a macro to behave differently depending on the module that is being build to emit different code to a web worker and the browser window (and also the backend clj server.) Since I need this at compile time reader-features do not work, they always choose :clj. On the deps.edn backend I can have different source-paths depending on build. How can I either point to a different file with the same namespace to get different implementations or have a var, or reader conditional set in the compile time environment to emit different code? I also tried using the reader-features in a cljc file to require the macros from different namespaces but I could not get this to work. Has anyone done this?

magra16:03:13

It seems I fail to get reader-features working on module level.

thheller16:03:43

it isn't clear what you are trying to do, but seems like reader-features is not what you are looking for. they apply to the entire build, not the module?

magra16:03:43

When I target a browser-window and a webworker should I put this into different builds? I need either a macro to expand differently in webworker and browserwindow or a require pointing two two different macros depending on whether I am in the webworker or the browserwindow.

thheller16:03:16

why does the MACRO need to expand differently? doesn't seem like a macro should be aware of this at all?

thheller16:03:40

maybe you want to introduce a namespace that only runs in the worker and only does what the worker needs

thheller16:03:43

same for the browser part

magra17:03:44

Fulcro mutations use fully qualified symbols as names that play nice if they live in namespaces that match their names. I just duplicated a lot of functionality from the server to the worker. So the worker and browser should do different stuff with the same data in the same namespaces. So maybe the worker and the browser need to be different builds.

thheller17:03:38

this tells me absolutely nothing about what problem you are actually trying to solve

thheller17:03:47

no, workers generally do not need to be separate builds

thheller17:03:17

they of course can be, but macro and reader-features do NOT play together at all. they solve two entirely separate problems

thheller17:03:48

so whatever it is you are trying to do, when it involves a macro then reader conditionals are not the solution

thheller17:03:02

if you care to describe what you are actually trying to do I can maybe make suggestions

thheller17:03:06

but so far I have nothing

magra18:03:58

I think I am not communicating clearly when I talk of the (defmacro defmutation) and when I talk about (:require [defmutation]) (defmutation my-mutation) I have a lot of code duplication between server, worker and browser. The namespaces for fulcro mutations should be the same on server and client. I have three different implementations of (defmacro defmutation) for server, worker and browser. (defmutation my-mutation ...) combines what my data does from database asserts to server-push to ui-sorting-follow-up. The three implementations of (defmacro defmutation) ignore the stuff their environment does not need and add house-keeping and other things. In deps.edn I would solve this by using three extra-paths so server, worker and browser get the three different flavors of (defmacro defmutation). Another aproach would be to have (defmacro defmutation) call three sepearte implementations defmutation* depending on the build. But to do this I would need compile-time awareness of the build. A third approach would be to have (:require #?(:client [my-client :refer [defmutation]] :worker [my-worker :refer [defmutation]])) in the cljc file where (defmutation my-mutation ..) lives.

magra18:03:12

I will probably have to abandon the common namespace and separate the one namespace into two, one browser only and one shared between server and worker. Between worker and server I can use different paths.

magra10:03:37

After more thinking in circles I always come back to the same thing. I want defmutation to to basically the same thing in the browser and worker. But some of the resulting actions will only be called in the worker and they pull in a database that I do not want the browser to pull in since it will never call it. So I want to delegate this decision to compile time. So either I (:require []) two different implementations of the macro defmutation, one having the performance optimization of leaving out what the environment will not use, or I would like the macro to be aware of the build so it can decide what to leave out.

magra10:03:34

This will make almost no difference at dev time but advanced build should not pull in a database it will not use.

thheller10:03:50

I want to give you tips but you have still basically given me zero information

thheller10:03:02

like give me an example of how you want this to look

thheller10:03:52

in my head this all makes no sense, as in all my setups the stuff backend does looks drastically different than what frontend does. so I wouldn't want that in the same ns or even less single form

thheller10:03:37

it might make total sense in your head, but I don't know what that looks like and I still don't know what problem you are actually trying to solve

thheller10:03:21

is it that you want to emit different code for CLJ and CLJS? that is an easy answer. for workers/browser not so much.

thheller10:03:22

if you are experimenting with something that moves all database actions to a worker then I recommend putting those into a separate namespace

thheller10:03:38

I did that some years ago. it works but in the end wasn't worth it for me.

magra11:03:12

Thank you! I have some half baked musings here https://markusgraf.net/2024-03-11-The-Big-Macro.html

thheller11:03:50

and as someone who knows nothing about what this actually does, I can't tell which parts are supposed to end up on the server/worker/browser?

thheller11:03:34

like what would the three different results look like?

thheller11:03:20

I would very very strongly recommend NOT doing this btw. I do not think this is a good idea. There is no value in forcing this into one form. IMHO, YMMV.

thheller11:03:48

partial solution is checking (:ns &env) in the macro, this is true-ish for CLJS but not CLJ. so easy way to separate the output

thheller11:03:05

for browser/worker this is not possible since the namespace can only exists once in the build, if it is shared between the worker and browser it'll end up in the shared module. the modules don't get their own version each

magra12:03:29

You are probably right. Thank you for your help!!!

thheller12:03:46

FWIW you can figure this out at runtime. so could just emit (if (is-in-worker?) (do-a) (do-b)) from the macro

magra12:03:29

The differentiation between worker and browser tripped me up. The worker/server differentiation works. I just wanted it to follow the same model as the browser/worker differentiation to have consistent code.

danielneal17:03:48

I’m trying to get custom colors to work with shadow-css, I’ve added colors to the repl function utility that builds the css…

(defn generate-css []
  (let [result
        (-> @css-ref
            (cb/generate '{:qrart {:include [qrart.*]}
                           :colors {:primary "#A83C3A" 
                                    :secondary "#375155"
                                    :light "#F4EDD9"
                                    :dark "#07081E"}})
            (cb/write-outputs-to (io/file "public" "css")))]

    (prn :CSS-GENERATED)
    (doseq [mod (:outputs result)
            {:keys [warning-type] :as warning} (:warnings mod)]
      (prn [:CSS (name warning-type) (dissoc warning :warning-type)]))
    (println)))

danielneal17:03:04

And then I’m trying to use in situ like [:div {:class (css :bg-primary)}]

danielneal17:03:41

But I’m not seeing the colors appear. The built in colors, e.g. (css :bg-red-100) work fine. What am I missing?

danielneal17:03:35

UPDATE: Oh, are the colors supposed to be merged into the css-ref? I missed the thread first arrow

danielneal17:03:20

UPDATE: Oh, no, that’s still not it. Also tried having the colors as strings rather than keywords…. I’m missing something important I think

thheller20:03:31

so if you modify the colors after start finished you need to call this again

thheller20:03:49

or just take what the start fn does, put it into your build and just set the colors before calling it

thheller20:03:58

I suggest looking at the actual build state, via tap> for example

thheller20:03:31

also look at the structure of the original colors, since you need to keep that structure for anything to work https://github.com/thheller/shadow-css/blob/e1ec8099910357e99014575a3e400652c2489fa1/src/main/shadow/css/colors.edn

thheller20:03:17

so {"primary" {"" "#A83C3A"} ...}

danielneal20:03:35

Ah, yep, got that, thanks 🙂

danielneal20:03:46

So I need to set them before cb/start

danielneal20:03:53

otherwise the aliases won’t be generated

thheller20:03:00

no. either you do not use start at all, and just do what it does manually in your code, so you can set it before it tries to generate the aliases

thheller20:03:43

OR you call the generate-color-aliases fn AGAIN after setting it

thheller20:03:52

generates things twice but that doesn't matter much

thheller20:03:33

note that I switched my projects all away from doing this incremental stuff

danielneal20:03:38

`(defn start ([] (start (init))) ([build-state] (-> build-state (load-preflight-from-classpath) (load-default-aliases-from-classpath) (load-colors-from-classpath) (load-indexes-from-classpath) (generate-color-aliases) (generate-spacing-aliases))))` Is start - if I call it with {:colors … won’t it generate the aliases?

thheller20:03:00

(defn css-release [& args]
  (let [build-state
        (-> (cb/start)
            (cb/index-path (io/file "src" "main") {})
            (cb/generate
              '{:ui
                {:include
                 [*]}})
            (cb/write-outputs-to (io/file "public" "css")))]

    (doseq [mod (:outputs build-state)
            {:keys [warning-type] :as warning} (:warnings mod)]

      (prn [:CSS (name warning-type) (dissoc warning :warning-type)]))))

danielneal20:03:03

> note that I switched my projects all away from doing this incremental stuff oh, how come

thheller20:03:14

(defn css-release [& args]
  (let [build-state
        (-> (cb/init)
            (cb/load-preflight-from-classpath)
            (cb/load-default-aliases-from-classpath)
            (cb/load-colors-from-classpath)
            (update :colors merge {"primary" {"" "red"}})
            (cb/load-indexes-from-classpath)
            (cb/generate-color-aliases)
            (cb/generate-spacing-aliases)
            (cb/index-path (io/file "src" "main") {})
            (cb/generate
              '{:ui
                {:include
                 [*]}})
            (cb/write-outputs-to (io/file "public" "css")))]

    (doseq [mod (:outputs build-state)
            {:keys [warning-type] :as warning} (:warnings mod)]

      (prn [:CSS (name warning-type) (dissoc warning :warning-type)]))))

thheller20:03:31

its simpler and fast enough to now have to worry about incremental updates

thheller20:03:38

I just call the css-release fn from the watch

danielneal20:03:26

Oh nice, thanks!

danielneal20:03:03

I just switched to the code you linked - I noticed that you have server/start and server/stop as function calls rather than metadata {:shadow/requires-server true} like in the shadow-css readme. What’s the difference there? I’ve noticed that if I don’t include {:shadow/requires-server true} the call to start exits

thheller20:03:13

my workflow is running npx shadow-cljs server in a terminal, then connecting a REPL and hitting my keybind which executes (require 'repl) (repl/go)

thheller20:03:59

but yes, if you run via npx shadow-cljs clj-run repl/start then you'd need :shadow/requires-server

danielneal21:03:19

ahah cool, I’ll try your workflow

thheller21:03:27

same end result I guess, for me its just habit. clj-run is actually better if you ask me, just too lazy to switch my setups

rafaeldelboni19:03:50

Maybe my question is more like is there any way to configure this to work without any custom hooks, but reading the docs I think it's not possible.

rafaeldelboni19:03:37

Would be cool to module-hash-names has and extra config where you could set a list of custom js files, even css and shadow generate an copy with the hash name and declare then on the manifest.edn

thheller20:03:36

I don't quite understand the question. :module-hash-names works for the shadow-cljs output yes. besides that shadow-cljs has no knowledge of what happens to the :external-index it generated, and as such doesn't care whether you assign a hashed name to it or not

rafaeldelboni20:03:27

I yeah sorry, I was wondering if was possible to add custom files on the hash name task, probably I will write a custom hook to do this.

thheller20:03:35

seems easier to me if you just create a function that reads the manifest.edn, adds whatever you want and writes a new file? independent, as part of whatever builds your external-index?

rafaeldelboni20:03:54

yeah I will probably I will go into this direction

rafaeldelboni20:03:37

Btw, not totally related, my friend was doing some experiments with esbuild to tree shake the release using :external-indexand got good reduction in the bundle size, thanks for the help in the other thread

thheller20:03:31

nice. what generated that image/report?

rafaeldelboni20:03:59

you can add the --metafile=something.json to the esbuild and then upload to the site

thheller20:03:52

ah neat. didn't know that existed