Fork me on GitHub
#shadow-cljs
<
2022-12-28
>
hifumi12304:12:52

How would I make the :asset-path "absolute"? My frontend app works fine with routes like /login, but assets immediately fail to load when I navigate to a route like /app/panel, since the rendered views will look for assets in /app/assets/... instead of /assets/...

thheller06:12:48

:asset-path is only used in a few places for JS files, you just specify an absolute path, so :asset-path "/js" or so

thheller06:12:05

any other "assets" you control yourself via HTML or code, so just emit absolute paths there

hifumi12306:12:51

I guess my question is then "is there a natural way to communicate server URL from shadow-cljs.edn to my frontend code?"

thheller06:12:16

why would it need to? are your assets served by a separate server?

thheller06:12:43

otherwise just use an absolute path, ie. starting with a /

hifumi12306:12:20

Sorry. I just fixed the issue by doing exactly what you suggested. Thanks for the help!

mbertheau12:12:47

If I have to delete .shadow-cljs to get a compilation working, that's a bug, right?

markus@ubuntu2204:~/src/specter-demo$ npx shadow-cljs compile :bootstrap
shadow-cljs - config: /home/markus/src/specter-demo/shadow-cljs.edn
shadow-cljs - connected to server
[:bootstrap] Compiling ...
------ ERROR -------------------------------------------------------------------
 File: /home/markus/src/specter-demo/src/main/fenster/mcvchcljc.cljc
Exception: No namespace: fenster.mcvchcljc found
	clojure.core/the-ns (core.clj:4163)
	clojure.core/ns-publics (core.clj:4190)
	clojure.core/ns-publics (core.clj:4190)
	shadow.build.macros/find-macros-in-ns (macros.clj:67)
	shadow.build.macros/find-macros-in-ns (macros.clj:65)
	shadow.build.macros/load-macros (macros.clj:121)
	shadow.build.macros/load-macros (macros.clj:85)
	shadow.build.compiler/post-analyze-ns (compiler.clj:49)
	shadow.build.compiler/post-analyze-ns (compiler.clj:46)
	shadow.build.compiler/post-analyze (compiler.clj:92)
	shadow.build.compiler/post-analyze (compiler.clj:89)
	shadow.build.compiler/analyze/fn--13241 (compiler.clj:265)
	shadow.build.compiler/analyze (compiler.clj:252)
	shadow.build.compiler/analyze (compiler.clj:211)
	shadow.build.compiler/analyze (compiler.clj:213)
	shadow.build.compiler/analyze (compiler.clj:211)
	shadow.build.compiler/default-analyze-cljs (compiler.clj:415)
	shadow.build.compiler/default-analyze-cljs (compiler.clj:404)
	clojure.core/partial/fn--5908 (core.clj:2642)
	shadow.build.compiler/do-analyze-cljs-string (compiler.clj:321)
	shadow.build.compiler/do-analyze-cljs-string (compiler.clj:278)
	shadow.build.compiler/analyze-cljs-string/fn--13326 (compiler.clj:518)
	shadow.build.compiler/analyze-cljs-string (compiler.clj:517)
	shadow.build.compiler/analyze-cljs-string (compiler.clj:515)
	shadow.build.compiler/do-compile-cljs-resource/fn--13354 (compiler.clj:637)
	shadow.build.compiler/do-compile-cljs-resource (compiler.clj:618)
	shadow.build.compiler/do-compile-cljs-resource (compiler.clj:572)
	shadow.build.compiler/maybe-compile-cljs/fn--13458 (compiler.clj:969)
	shadow.build.compiler/maybe-compile-cljs (compiler.clj:968)
	shadow.build.compiler/maybe-compile-cljs (compiler.clj:944)
	shadow.build.compiler/par-compile-one (compiler.clj:1089)
	shadow.build.compiler/par-compile-one (compiler.clj:1044)
	shadow.build.compiler/par-compile-cljs-sources/fn--13498/iter--13520--13524/fn--13525/fn--13526/fn--13527 (compiler.clj:1162)
	clojure.core/apply (core.clj:667)
	clojure.core/with-bindings* (core.clj:1990)
	clojure.core/with-bindings* (core.clj:1990)
	clojure.core/apply (core.clj:671)
	clojure.core/bound-fn*/fn--5818 (core.clj:2020)
	java.util.concurrent.FutureTask.run (FutureTask.java:264)
	java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1128)
	java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:628)
	java.lang.Thread.run (Thread.java:829)

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------markus@ubuntu2204:~/src/specter-demo$ 
markus@ubuntu2204:~/src/specter-demo$ rm -rf .shadow-cljs/
markus@ubuntu2204:~/src/specter-demo$ npx shadow-cljs compile :bootstrap
shadow-cljs - config: /home/markus/src/specter-demo/shadow-cljs.edn
shadow-cljs - updating dependencies
shadow-cljs - dependencies updated
[:bootstrap] Compiling ...
[:bootstrap] Build completed. (90 files, 90 compiled, 0 warnings, 18.35s)

thheller15:12:48

yes, that would be a bug

thheller15:12:02

not sure what is happening there though

mbertheau09:12:16

@U05224H0W related, but not necessarily the same issue: I suspect that at least cljc files on the source path that are intended to and also practically only used for self-hosted CLJS (i.e. :target :bootstrap) are run through the Clojure compiler during reload when a watch is active for the normal ClojureScript build (`:target :browser`), and then complain about, for example, no such namespace: js. These files work as intended in the self-hosted environment. Is this intended to work differently? Can I have self-hosted CLJS-only files in my sources that can assume to be run only in a CLJS context?

thheller11:12:06

one build never affects another

thheller11:12:02

the browser build will only compile files it required itself. note that it is extremely easy to make mistakes with .cljc files, thus I rarely recommend using them

thheller11:12:57

bootstrap will also compile more files than other builds do, so it might be deep in some macro files that otherwise are never compiled

thheller12:12:24

my guess is that a macro file somehow fails to load but the error is lost somewhere

thheller12:12:38

maybe because of some ns form that is not valid in clj

thheller12:12:54

and then the macro ns is not defined and leading to the above error

mbertheau12:12:48

I'm still at a stage where I want to make specter work in an self-hosted environment. For that I'm trying to understand how macros work in the self-host environment provided by shadow-cljs :bootstrap. For that I'd like to step through the code and look at stuff. Not sure how to do that though. I'm on emacs and use cider.

thheller13:12:37

there is nothing special to macro compilation. the ns is just compiled as normal, using :cljs branches in .cljc files

mbertheau14:12:32

I believe you, but that doesn't yet match the mental model I have built based on my observations so far 🙂 Something must be different though because js/some.name.space.js and js/some.name.space$macros.js are different

thheller14:12:15

I don't know what you mean by that. you should under no circumstance ever refer to any CLJS namespace via js/

thheller14:12:54

if you mean the output files then yes, there are 2. can't write two namespaces into one file

thheller14:12:30

the way you should approach this is that it is basically two entirely separate namespaces, that just happen to have the same name from the code side (so same ns) but different internally

mbertheau15:12:39

I mean the output files, yes. The two are different, but they are produced from the same file. So something must be different about the compilation process.

mbertheau15:12:28

Another way to state the problem is that (macrovich/deftime (defmacro ...)) macros don't make it to the $macros output file for the namespace in my situation currently. Also I read that defmacro is ignored – or produces no output – when compiled in CLJS, maybe that's one difference?

mbertheau15:12:23

More close to my original problem, which is that select is undefined in CLJS in a self-hosted environment, I seems clear now why the upstream version behaves like this - it has (defmacro select ...) in #?(:clj. A PR/fork/branch that was reportedly working better has it in (macrovich/deftime ...) instead. However, select still didn't appear in the ...$macro.js output file. I assumed that that must be correct, but maybe it isn't. Reading https://cljs.github.io/api/cljs.core/defmacro now, that looks helpful.

mbertheau15:12:33

Hmm, when the cljs js compiler asks for the source of a namespace as a non-macro namespace, shadow-cljs effectively (modulo analyze caching and so on) offers output generated form .cljs or .cljc files, and when asked for a macro namespace, it offers .cljc or .clj, is that accurate? Does that effectively mean that for bootstrapped / self-hosted cljs we can put CLJS code in .clj files?

mbertheau16:12:32

Also, if I have code that should run at compile-time, and that code is different for cljs jvm (clj compiles macro ns) and cljs js (cljs compiles macro ns) and I want separate files for macro ns from non-macro ns, I end up with a .clj file with #?(:cljs reader conditionals, correct?

thheller16:12:17

can you point me to the actual sources?

mbertheau16:12:44

In the PR the effective change was replacing #?(:clj with (macrovich/deftime.

mbertheau16:12:33

I have a local repo that shows that defmacros in macrovich/deftime in cljc files don't end up in the compiled js and are thus not available for source code to be compiled by the self-hosted compiler.

mbertheau16:12:52

Based on quickstart-browser, which I was about to push to github

thheller16:12:50

deftime seems useless to me here, as is the conditional. just remove it entirely, so the code is always loaded

thheller16:12:40

I mean I can't tell if there is any actual java interop in there, but otherwise if its just clojure code just keep it always active

thheller16:12:02

the defmacroalias is also a problem

thheller16:12:14

can't alias vars like that for cljs

mbertheau16:12:28

Yeah, there's a separate solution for that in the PR that I'll take.

mbertheau16:12:06

Thanks, I'll try that. I still need to understand it though 🙂 So defmacros` in macrovich/deftime aren't supposed to end up in the JS output?

thheller16:12:59

not a clue. I've never used macrovich, don't know what it means

thheller16:12:16

the problem is that #(:clj ..) hides it from the CLJS macro compilation which will take the :cljs branch. so thats why select is missing, it is never there in the first place

thheller16:12:55

(defmacroalias richnav macros/richnav) don't know why this is there

thheller16:12:26

why not just have the macros directly use macros/richnav. I never get these aliasing things, so not sure why it has to be in this ns

mbertheau16:12:18

deftime is just 1 line:

(defmacro deftime
  "This block will only be evaluated at the correct time for macro definition, at other times its content
   are removed.
   For Clojure it always behaves like a `do` block.
   For Clojurescript/JVM the block is only visible to Clojure.
   For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace."
  [& body]
  (when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*))))
    `(do ~@body)))

thheller16:12:44

that sounds like it should be what you want

mbertheau16:12:24

I thought so, but defmacro s defined in a macrovich/deftime don't appear in JS output produced by a shadow-cljs :bootstrap build.

mbertheau17:12:48

Ah, I may have to put the namespace in :macros in the config.

thheller17:12:48

again, this does not seem like its needed to me

thheller17:12:19

shadow-cljs already ignores defmacro in .cljc files unless compiling macros, so having them there doesn't do anything "extra"

mbertheau17:12:25

I understand that I don't need deftime here, but I'd like to understand why it doesn't match my mental model. You said before that deftime sounds like it should be what I want. But nothing that's inside deftime ends up in the JS output. Is that expected, given its definition above?

thheller17:12:59

I cannot say without seeing the full actual code

thheller17:12:21

the defmacro should definitely be in the generated $macros.js file, but not in the regular ns .js file

mbertheau17:12:54

Alright, let me push something to github to show you

mbertheau17:12:18

I just noticed a possible misunderstanding. I didn't mean that the deftime macro itself doesn't end up in the JS, but that what's inside a call to the deftime macro doesn't end up in the $macros.js file.

thheller17:12:12

goog.provide('friday.myns$macros');
console.log("hello from toplevel");
friday.myns$macros.toplevelfunction = (function friday$myns$macros$toplevelfunction(){
return console.log("hello from toplevel function");
});
var ret__5824__auto___14156 = friday.myns$macros.toplevelmacro = (function friday$myns$macros$toplevelmacro(_AMPERSAND_form,_AMPERSAND_env){
console.log("hello from toplevelmacro expansion");

return cljs.core.sequence.cljs$core$IFn$_invoke$arity$1(cljs.core.seq(cljs.core.concat.cljs$core$IFn$_invoke$arity$2((new cljs.core.List(null,new cljs.core.Symbol("js","console.log","js/console.log",-2005248266,null),null,(1),null)),(new cljs.core.List(null,"hello from toplevelmacro result evaluation",null,(1),null)))));
});
(friday.myns$macros.toplevelmacro.cljs$lang$macro = true);

console.log("hello from usetime");

friday.myns$macros.usetimefunction = (function friday$myns$macros$usetimefunction(){
return console.log("hello from usetimefunction");
});

var ret__5824__auto___14160 = friday.myns$macros.usetimemacro = (function friday$myns$macros$usetimemacro(_AMPERSAND_form,_AMPERSAND_env){
console.log("hello from usetimemacro expansion");

return cljs.core.sequence.cljs$core$IFn$_invoke$arity$1(cljs.core.seq(cljs.core.concat.cljs$core$IFn$_invoke$arity$2((new cljs.core.List(null,new cljs.core.Symbol("js","console.log","js/console.log",-2005248266,null),null,(1),null)),(new cljs.core.List(null,"hello from usetimemacro result evaluation",null,(1),null)))));
});
(friday.myns$macros.usetimemacro.cljs$lang$macro = true);


//# sourceMappingURL=friday.myns$macros.js.map

thheller17:12:17

seems to be all there for me?

mbertheau17:12:16

Not deftimemacro though

thheller17:12:29

oh what is usetime then? confusing names 😛

thheller17:12:47

just saw some time stuff and thought thats it 😛

thheller17:12:41

could be that *ns* isn't actually bound to the $macros ns? never checked

mbertheau17:12:05

Is that a peculiarity of shadow-cljs bootstrapped cljs? shadow-cljs does compile the cljs to js outside the browser. Is that different in this regard compared to compiling all the namespaces in the browser?

thheller18:12:36

the macro compilation is not done for regular builds, so this is different

mbertheau18:12:59

I pushed a commit that logs (ns-name *ns*) in all the places and only ever get null or markus.pumpkin, never markus.pumpkin$macros.

thheller06:12:14

*ns* is definitely bound to the correct value. most of your additions were incorrect though. I don't understand the problem with deftime either, but I do not see the point anyways so just do not use it

niwinz13:12:43

Hello, I think I found a bug. For now I don't have an isolated reproducible setup, but I consistently trigger it on penpot project compilation. In summary: when module split + function tht uses reify. The result is: there are a function1 that uses reify, which will define the class once the function1 is used (defined in module shared.js); and for some reason a one method impl is splitted to the main.js module where the prototype is extended with the missing method, but the object is obviously does not exists until the function1 is called. Code sample from shared.js:

function $promesa$exec$scheduled_executor$cljs$0core$0IFn$0_invoke$0arity$0variadic$$($p__32931$$) {
    var $map__32932__$1$$ = $cljs$core$__destructure_map$$($p__32931$$)
      , $parallelism$$ = $cljs$core$get$$.$cljs$core$IFn$_invoke$arity$3$($map__32932__$1$$, $cljs$cst$172$parallelism$$, 1)
      , $factory$jscomp$2$$ = $cljs$core$get$$.$cljs$core$IFn$_invoke$arity$2$($map__32932__$1$$, $cljs$cst$173$factory$$);
    if ("undefined" === typeof $promesa$$ || "undefined" === typeof $promesa$exec$$ || "undefined" === typeof $promesa$exec$t_promesa$0exec32933$$) {
        $promesa$exec$t_promesa$0exec32933$$ = function($p__32931$jscomp$1$$, $map__32932$jscomp$1$$, $parallelism$jscomp$1$$, $factory$jscomp$3$$, $meta32934$$) {
            this.$p__32931$ = $p__32931$jscomp$1$$;
            this.$map__32932$ = $map__32932$jscomp$1$$;
            this.$parallelism$ = $parallelism$jscomp$1$$;
            this.$factory$ = $factory$jscomp$3$$;
            this.$meta32934$ = $meta32934$$;
            this.$cljs$lang$protocol_mask$partition0$$ = 393216;
            this.$cljs$lang$protocol_mask$partition1$$ = 0;
        }
        ,
        $promesa$exec$t_promesa$0exec32933$$.prototype.$cljs$core$IWithMeta$_with_meta$arity$2$ = function($_32935$$, $meta32934__$1$$) {
            return new $promesa$exec$t_promesa$0exec32933$$(this.$p__32931$,this.$map__32932$,this.$parallelism$,this.$factory$,$meta32934__$1$$);
        }
        ,
        $promesa$exec$t_promesa$0exec32933$$.prototype.$cljs$core$IMeta$_meta$arity$1$ = function() {
            return this.$meta32934$;
        }
        ,
        $promesa$exec$t_promesa$0exec32933$$.prototype.$promesa$protocols$IScheduler$_schedule_BANG_$arity$3$ = $JSCompiler_stubMethod$$(0),
        $promesa$exec$t_promesa$0exec32933$$.$cljs$lang$type$ = !0,
        $promesa$exec$t_promesa$0exec32933$$.$cljs$lang$ctorStr$ = "promesa.exec/t_promesa$exec32933",
        $promesa$exec$t_promesa$0exec32933$$.$cljs$lang$ctorPrWriter$ = function($writer__5331__auto__$jscomp$87$$) {
            return $cljs$core$_write$$($writer__5331__auto__$jscomp$87$$, "promesa.exec/t_promesa$exec32933");
        }
        ;
    }
    return new $promesa$exec$t_promesa$0exec32933$$($p__32931$$,$map__32932__$1$$,$parallelism$$,$factory$jscomp$2$$,$cljs$core$PersistentArrayMap$EMPTY$$);
}
And a code sample form main.js
$promesa$exec$ScheduledTask$$.prototype.$promesa$protocols$ICancellable$_cancel_BANG_$arity$1$ = $JSCompiler_unstubMethod$$(1, function() {
    // ...
});
$promesa$exec$t_promesa$0exec32933$$.prototype.$promesa$protocols$IScheduler$_schedule_BANG_$arity$3$ = $JSCompiler_unstubMethod$$(0, function($G__32936__$jscomp$255$$, $ms$jscomp$4$$, $f$jscomp$369$$) {
    var $done$$ = $cljs$core$volatile_BANG_$$(!1)
      , $tid$$ = setTimeout(function() {
        try {
            return $f$jscomp$369$$.$cljs$core$IFn$_invoke$arity$0$ ? $f$jscomp$369$$.$cljs$core$IFn$_invoke$arity$0$() : $f$jscomp$369$$();
        } finally {
            $cljs$core$_vreset_BANG_$$($done$$, !0);
        }
    }, $ms$jscomp$4$$);
    $G__32936__$jscomp$255$$ = {
        done: $done$$,
        cancelled: !1,
        "cancel-fn": function() {
            return clearTimeout($tid$$);
        }
    };
    return $promesa$exec$__GT_ScheduledTask$$.$cljs$core$IFn$_invoke$arity$1$ ? $promesa$exec$__GT_ScheduledTask$$.$cljs$core$IFn$_invoke$arity$1$($G__32936__$jscomp$255$$) : $promesa$exec$__GT_ScheduledTask$$($G__32936__$jscomp$255$$);
});
function $cljs$core$persistent_BANG_$$($tcoll$jscomp$14$$) {
   /...
where you can see that $promesa$exec$t_promesa$0exec32933$$ is used in main.js, but this one will only be defined if the $promesa$exec$scheduled_executor$cljs$0core$0IFn$0_invoke$0arity$0variadic$$ called. And this function is not called until long after the first browser load+eval Is this make sense?

niwinz13:12:40

I guess I can workaround it by not using reify on promesa library and replace it with a deftype, but still is very strange that a method definition of some reify object (that is conditionally initialized on first call) is relocated to other module and called on module load+eval time

thheller15:12:25

known problem

thheller15:12:17

work arround is to set :compiler-options {:cross-chunk-method-motion false} in the build config

niwinz08:12:52

thanks! \o/