I'm hunting down a mystery related to constructor literals (record literals, #ns.Record{:field val}). I've had these work in a project, but now that I'm trying to make a minimal repro, they don't seem to be compiling in CLJS. See https://github.com/opqdonut/cljs-record-literal-repro . I'm probably missing something obvious...
That was probably a bit too opaque, let me inline the relevant stuff here:
(ns record
#?(:cljs (:require-macros record)))
(defrecord Foo [field])
(ns repro
(:require [record])
#?(:cljs (:require-macros record)))
(prn #record.Foo{:field 1})
This fails to compile in CLJS with ClassNotFoundException, but works in CLJ.
I've tried various permutations of :require-macros, no effect.FWIW the reason this is complicated and CLJS and probably shouldnt be used is that the reader+compiler is not part of the runtime
meaning the reading happens at compile time and yields the CLJ instance of an object. the compiler then needs to know how to turn that CLJ/JVM object into JS code. which is ... not what happens for CLJ
even for CLJ though it often falls back to use read-eval, which also kinda sucks 😛 talking about a AOT context
I'm having some problems setting up a browser REPL in ClojureScript. I have a build with an :output-dir of "target/cljs/js" and this all works fine. However, the REPL doesn't seem to behave. When passed the same options as the build, it creates a ./out directory, despite this not being in the configuration. Why? It also sets the script locations as target/cljs/js which return 404s instead of the actual files. I'm really struggling in my attempts to get a basic build+REPL setup via the API.
I've checked the repl-env argument and that has no ./out directory in any of its options, but for some reason the REPL feels the urge to rebuild to that path and then ignore it. I'm currently looking through the source to try and figure out why it's behaving this way.
The default REPL appears to assume that you're serving from the local directory, and that the asset path is equal to the output directory. I'm unsure why this assumption would be made, as it's almost never true. For whatever reason it's also rebuilding everything unprompted and placing it in an out directory, which isn't defined in the REPL environment map or the option map.
The more I look into the browser REPL included in ClojureScript, the more it looks like a prototype, or something experimental? It appears to be very brittle and only works under particular build configurations.
FWIW the REPL stuff is always going to be a bit more brittle than clojure. in clojure the runtime can't disappear randomly from the REPL. in CLJS it can since you can just reload the browser at any point. so your R.PL parts of the REPL stay alive but the E disappears and loses all state and just reappears
thats why it works completely differently in shadow-cljs as well 😉
I meant more in terms of how the cljs REPL expects that the asset-path is the same as the output directory. And how it uses its own custom HTTP server.
@weavejester I had the same experience. At some point, I spend some time trying to figure out do I really need Figwheel/shadow? Could I do ClojureScript just with the built-in browser REPL. I spend quite a lot of time reading the source and trying to figure out the exact right configurations, and if I remember right, I got it somewhat working, but it still felt brittle, the connection dropped quite often etc. So yeah, it felt a bit more like an experimental think or something you'd use just for the "getting started part" before switching to Figwheel/shadow.
I'm sorry I can't answer to your question (it's been too long since I tried this) but if it help, here's what I ended up having in my compiler options:
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/app/out"
:asset-path "/js/compiled/app/out"
(and yes, as you pointed out, asset-path equals to output dir)
And then in browser-repl options I added:
{:port 3449
;; Let's keep this false. To my experience, if this is set to true,
;; it's common to see "Broken pipe" socket error.
:launch-browser false
:static-dir [;; The index.html file that is served on localhost:<port>
;; by the Browser REPL static file server is located in resources/public
"resources/public"
;; Add output-dir here so that the Browser REPL static
;; file server can find and serve the compiled files.
(:output-dir compiler-options)]}The unprompted build to out sounds like you are running the repl without giving it your compile options, so it is using the defaults. Note the --compile-opts flag in this alias. Similar options can also be given to cljs.repl/repl if you are starting the repl from code.
:aliases
{:repl
{:main-opts ["-m" "cljs.main"
"--compile-opts" "cljs-opts.edn"
"--repl-opts" "{:launch-browser false}"
"--repl"]}}
cljs-opts.edn:
{:output-to "resources/public/js/main.js"
:output-dir "resources/public/js/out"
:asset-path "js/out"
:main }
For what it's worth I've not had any issues with cljs browser repls but it did take a while (and some reading) to figure out.I only use Browser REPL, it's definitely a bit basic, it could be improved but I don't think it gets enough use to get decent reports. It works well enough for me.
I also only use the built in Node.js REPL and (previously) Krell (React Native) was based on the existing REPL stuff.
so I wouldn't say experimental - just probably lacking some care 🙂
Always keen on getting patches and ideas here!
That said CLJS REPLs aren't very simple - it's easily the most complicated thing in the entire code base besides the compiler and analyzer.
I haven't looked through the CLJS REPL in great detail, but one thing I did notice is that it handles the asset path oddly. My assumption would be that the relative path of the file to the output-dir would first be computed, and then the asset path added to the relative path. For example, if my output-dir is target/cljs/js and I have an asset target/cljs/js/main.js, then first I'd assume it calculates the relative path - main.js - and then adds the asset-path /js to the front separating by a forward slash if needed: /js/main.js.
Instead, the CLJS REPL appears to assume that the asset-path is always identical to the output-dir. So if the output-dir is target/cljs/js then the path of the asset is assumed to be /target/cljs/js/main.js. Unless the output-dir is absolute, for example /tmp/cljs/js, in which case the path of the asset is /main.js.
hrm, pretty sure :asset-path is absolute path for web servers.
Is the CLJS REPL in ClojureScript complex just because it needs to create its own web server? I (perhaps naively!) thought it looked relatively straightforward otherwise: compile CLJS to javascript using a compiler environment that persists through the loop, send to browser, browser evaluates javascript and returns the result (if printable). Am I missing something important?
i.e. need to resolve source maps etc.
right, it's important to not conflate CLJS REPL and browser REPL here.
let's focus on why the REPL is complex
problem number one is that the JS runtime is remote
even Node.js is a socket based thing
resolution of files and loading of files is different across all JS targets
we must bring our own resolution mechanism on top of every target, which is a combination of GClosure conventions and classpath
the browser JS runtime is transient (the user can refresh it)
so now we need both reconnect logic, but also we need analysis paths so everything doesn't show up undefined
this is just the initial set of hard problems 🙂
note that cljs.repl is the core functionality
Node.js, Browser, third party stuff like Krell use extension points.
In the old days we had Nashorn and Rhino as well.
the whole thing is sufficiently complicated that Figwheel and Shadow both reimplemented a lot of stuff.
I don't know if they support all the Clojure flags like CLJS attempts to
Thanks for the explanation! I'm currently only interested in a browser REPL, which at least simplifies the target. My current plan is to write my own websocket based REPL server/client, since I don't have the same restrictions that ClojureScript itself has (i.e. I can use external libraries). It may turn out to be that I write a repl-env that I can slot into cljs.repl, though I also want compatibility with rebel-readline.
I've worked with both Figwheel and Shadow in the past, but they're very much designed as tools first - it was hard to use them as libraries.
browser REPL does a bunch of stuff to make it "easy" for first time users and to avoid any web server deps
it's not a great starting point
whether your interested in React Native or not one goal w/ Krell was to show how to use what's in CLJS as a library
there's still a bit to digest here, but I think I had a primitive thing working in a week or so.
Thanks! That looks like a pretty useful reference along with cljs.repl.browser.
so the key thing is you can skip all the complexity if you start w/ eval
that's how I did all of the existing ones, and I think stubbed out file loading etc.
once you can eval (+ 3 4)
next step is "bootstrapping"
which is enough code to load deps
and then the rest becomes more clear
feel free to ask questions in #cljs-dev is you need more assistance
Would bootstrapping be required if there's already files from cljs.build.api? i.e. if I build the ClojureScript files first, with the REPL server as a preload, then the browser should already have all the JS necessary to goog.require etc. right? Or is that naive?
Also thanks for the #cljs-dev tip! I'll try not to ask too many questions - I really appreciate you taking time to answer.
goog.require doesn't actually do anything at runtime. goog.require/provide are essentially metadata for the compiler
you still need to bootstrap because goog.require etc. don't understand reloading namespadces
so no build stuff is not sufficient
browser REPL does this hack
but also you could do it differently, Shadow doesn't use the debug loader for example
Ah, I see. Presumably I can just use the ClojureScript bootstrapper to patch goog.require?
that should work
Excellent. Again, hugely appreciate everyone taking their time to educate me on this.