Fork me on GitHub
#shadow-cljs
<
2019-11-05
>
thheller00:11:19

you can sort of if you pass the runtime-id when switching the REPL

thheller00:11:39

(shadow/nrepl-select :the-build {:runtime-id ...})

ro600:11:57

Ok. What I'm doing right now that works is:

:reql/quit
(shadow/repl-runtime-select :browser-extension
    (:runtime-id (first (shadow/repl-runtimes :browser-extension))))
(shadow/repl :browser-extension)

thheller00:11:00

hmm or maybe not

thheller00:11:08

I don't know anymore to be honest

thheller00:11:33

the nREPL stuff has been a little frustrating

ro600:11:08

I didn't realize I could pass a :runtime-id to nrepl-select, so I'll try that.

ro600:11:35

No worries, I realize I'm way in edge-use-case land here, but it's interesting nonetheless.

thheller00:11:58

you can't actually pass :runtime-id to the nrepl

thheller00:11:12

it currently only expects that as part of the nrepl message (which no tool actually sends)

thheller00:11:26

but I could change it so it respects it if set via nrepl-select

thheller00:11:03

but all of this is part of the stuff I'm currently working through

thheller00:11:23

so I hope it'll become a bit clearer in the future

ro600:11:55

No worries at all, I appreciate that it works as well as it does. I'm not really impeded here, just trying to make it more ergonomic so I can eval in different JS environments really efficiently.

thheller00:11:13

that should take care of it

thheller00:11:23

but headed to bed now. will test and probably release tommorrow

ro600:11:46

It might require more tooling integration (eg from Cursive) but in my "ideal future" I'd be able to configure rules such that "send to repl" from a certain file automatically gets eval'd in a particular environment, rather than switching a session singleton. I'm not sure how practical such a system would be beyond what I'm doing, but maybe there's a more general "route eval requests to environments" behavior hiding in there.

ro600:11:56

Cool, thanks for your help!

thheller00:11:13

yes, I wish tools had support for "runtimes"

thheller00:11:48

but the regular CLJS repls don't expose them so it would be shadow-cljs only right now

lilactown00:11:17

I have an interesting problem I’m trying to tackle

lilactown00:11:32

React recently shipped native support for hot reloading components

lilactown00:11:54

actually, before I type all this out and bother y’all with thinking through this, I’m going to do some experiments and see where I actually fall over

lilactown00:11:39

the problem I’m currently afraid of is if I use React’s way of performing an update without restarting the app, it might have some stale references to e.g. a utility function I just changed

lilactown00:11:40

so I might need some way of determining what “kind” of change was done (in webpack they use some heuristic to determine of a file only exports components), and decide whether to run performRefresh or render

lilactown00:11:22

after writing it out, I'm pretty sure this will be an issue I need to tackle if I want to use the React hot reloading stuff

lilactown00:11:14

I don't suppose there's any information that the before-load/`after-load` fns can be given to help with this?

lilactown01:11:39

since all def and defns are exported by default, I don't think I can rely on that heuristic to ensure only the React components in a particular file are used elsewhere. I'm thinking right now if I could annotate an ns with some metadata, and get fed that in my after-load hook, then I could rely on the programmer to reasonably discern if a ns is safe to just refresh and add the meta themselves

lilactown01:11:09

ex:

(ns ^:react/refresh my-app.components
 ,,,)

lilactown01:11:51

ah interesting. I was reading figwheel docs, and it distinguishes between reloading and recompiling dependent namespaces. it's possible that by reloading dependent namespaces (not recompiling them) we can have the desired behavior.

lilactown01:11:08

my preliminary tests show it actually all works fine 😅

👍 4
lilactown01:11:48

ah shoot. it only works one ns deep in the tree

lilactown02:11:44

e.g. if I have namespaces a, b and c where a requires b requires c: a -> b -> c then if I change c, only b is reloaded.

lilactown02:11:58

if we reloaded the entire dependency tree of c -> b - a then I think things would work correctly. I don't know how heinous that would be in large apps 😬

vinurs02:11:03

is there any example that shows how to call my component in react component, my own component is defined in cljs

Saikyun05:11:03

@thheller thanks for the info! 🙂

thheller08:11:11

@lilactown the last time I looked at the fast-refresh stuff it required processing the generated JS to extract the signatures of the hooks used

thheller08:11:27

very very very doubtful that'll work for CLJS

lilactown08:11:44

It doesn't. We can easily use macros to generate the signature

lilactown08:11:15

I have a working poc of this

lilactown08:11:44

The only actual requirement is to register the signatures with the runtime somehow (Babel, macros, etc.) and to call a method to trigger a "refresh" on hot reload

lilactown08:11:01

The last real hurdle is to correctly get all references to my changed ns to reload correctly

lilactown08:11:39

Going to sleep. Will check back later

thheller08:11:57

@haiyuan.vinurs how did you define your component? how is the JS code getting access to it? :npm-module + webpack?

vinurs08:11:20

i’ve solved the problem, in cljs ns pages.dashboard.questionnaire

(def ^:export QuestionnairePage
  (r/reactify-component
   (fn [a]
     (println "questionnaire page is" a)
     [:div "questionnaire page"])))
and in js
const { QuestionnairePage } = window.pages.dashboard.questionnaire
and i can use the component like this
<QuestionnairePage a="b" c={style} />

👍 4
yyoncho10:11:54

Hi - I have 2 npm packages generated by shadow-cljs that are a dependency of a third application. Is it possible to share the timbre configuration between the two packages in the packaging project?

thheller10:11:43

sorry, I don't know anything about timbre

thheller10:11:05

it is not advisable to create 2 npm packages intended to be consumed in one project. they'll contain a lot of duplicated code

roklenarcic10:11:42

I have a .clj file with a macro in it under src/asciidocs/core.clj, and then I have src/asciidocs/app.cljs file which declares (:require-macros [asciidocs.core :as core]). When I try to use the macro the shadow-cljs complains:

6 |   (println (core/load-asciidoc "resources/docs/cv.asc"))
------------------^-------------------------------------------------------------
No such namespace: asciidocs.core, could not locate asciidocs/core.cljs, asciidocs/core.cljc, or JavaScript source providing "asciidocs.core"
--------------------------------------------------------------------------------
I am not sure why this doesn't work. I thought I was supposed to have my cljs macros in a clj file like this.

henrik11:11:29

Is there any way to get the compiler to not evaluate stuff in #?(:clj blocks? I don’t want to compile the required NS into CLJS just to get rid of an error, as it’s not otherwise used in this file.

henrik11:11:21

Of course, I can just require something random, like #?(:cljs [clojure.string :as pc]) to get around it, or create a dummy NS to include for this purpose.

thheller11:11:13

don't import it as a macro then?

thheller11:11:56

I'm confused about your question. you ask about :clj blocks but then fix it in :cljs block?

thheller11:11:24

ah sorry ... now I understand. no that is not possible

thheller11:11:52

it has to be syntactically correct (so the alias needs to be resolvable)

thheller11:11:11

or just don't use .cljc 😉

thheller11:11:34

it is the reader failing here. not the compiler. it isn't eval'ing the :clj blocks. it is just trying to read and for that is must resolve the ::pc/input alias

👍 4
henrik13:11:16

Ah, crud. Alright, I’ll make a dummy NS to use in these cases. I thought I’d colocate the defmutations for both frontend and backend to the same file for fun and profit.

thheller11:11:23

might be interesting to modify tools.reader so it doesn't try to resolve whenever it is in a read-cond block that isn't active

thheller11:11:34

doesn't really matter what ::pc/input resolves to in those cases

mhuebert13:11:56

nice result from the wild: I recently converted a fairly large client codebase to compile with shadow-cljs - live-reload became ~3x faster upside_down_parrot and more reliable (was previously getting lots of errors when refreshing the page during recompile)

👍 4
thheller13:11:09

only 3x? 😛

henrik13:11:57

Speaking of the wild, I don’t want to use the words “de facto cljs compiler” yet, but I feel like I’m seeing a lot more shadow-cljs out there these days as opposed to only a year ago or so. Have you seen any concrete indications as to this @thheller (Visitors to the site etc.)?

thheller13:11:59

numbers keep going up yes. no clue how it compares to others though

thheller13:11:14

the yearly clojure survey seems to be a good indicator

thheller13:11:34

last one was about 25% shadow-cljs. we'll see in the next one 🙂

4
👍 8
knubie14:11:53

does anyone have a good method of running single tests in a :node-test script?

thheller14:11:29

shadow-cljs node-repl (require '[your.test-ns :as x]) (x/that-test)

knubie14:11:23

Thanks, that seems to work, but I'm using spectron (https://github.com/electron-userland/spectron), which is supposed to open up an electron instance to run the spec with webdriver. But running the spec in the REPL doesn't do that so maybe it's not possible from the node-repl?

knubie14:11:06

or maybe it's because the tests are async

knubie14:11:27

scratch that, I ran the test with a .catch and am looking into the error now

knubie14:11:50

:face_palm: Had to run fixtures

thheller16:11:10

I guess the :node-script output could take additional command line args too

thheller16:11:31

so node the-output.js --only some.test/foo or so. if someone is interested in working on that

knubie17:11:28

wouldn't that be possible without any changes to shadow-cljs?

thheller17:11:36

sure if you write a custom runner

thheller17:11:44

the default one doesn't take any command line args

lilactown17:11:48

hey thheller, I made a short POC of what I'm trying to do with react's HMR support (react-refresh) here: https://github.com/Lokeh/react-hmr-cljs

lilactown17:11:19

if you have time to look at it, I would greatly appreciate it

lilactown17:11:34

I'm able to generate the signatures and get hot reloading of same-file changes working just fine

lilactown17:11:37

however, in the example I have a dep-b and dep-c namespace where core depends on dep-b, and dep-b depends on dep-c. if I change the greeting in dep-c to be "Bonjour!", it won't pick up the change on hot reload until the next render

thheller17:11:39

thats missing an example how the use actually looks

thheller17:11:48

or I'm just blind 😛

thheller18:11:21

yeah I'm just blind sorry 😛

lilactown18:11:38

sorry yeah it's laid out a little weird. react-hmr.core has the example usage

thheller18:11:37

now you only need to figure out how to get rid of all the related code in production 😉

lilactown19:11:51

yes, it’s easy enough to wrap in (when goog/DEBUG ...

thheller19:11:16

not so easy to get rid of the react-refresh require though 😉

lilactown19:11:45

true. could be done with a preload and some alter-var-root / global JS shenanigans

lilactown19:11:55

but does my problem I need help with make sense?

thheller19:11:49

don't know enough about the refresh stuff to comment. it likely happens because core didn't change it just cancels the re-render

lilactown19:11:55

right, so the issue is that the react-refresh stuff only re-renders components that have had their “type” (the function component registered with the ID) changed

lilactown19:11:51

the way that they’re suggesting people implement it is to detect whether a file only exports components. if it exports anything else than components, then call React.render instead (and blow away the state)

lilactown19:11:09

which is… one solution

lilactown19:11:35

I would need to be able to somehow detect whether a namespace is “safe” to refresh in my after-load hook

lilactown19:11:51

the other option could be to reload the entire dependency chain

lilactown19:11:28

which if you are editing a file that has many dependents, could be very onerous

lilactown19:11:42

I think either way, I need some help from shadow-cljs / figwheel to make it work

thheller19:11:10

sorry, don't know enough about react-refresh to make sense of that. Don't know why "only exports components" is relevant at all. makes no sense to me right now

lilactown19:11:39

it’s a super dumb heuristic to try and detect, “does anything other than React call something from this file”?

vinurs19:11:28

when i require ant-design in cljs and build a browser target like main.js, is that all ant-design files will bundled in the main.js?

thheller19:11:33

@haiyuan.vinurs yes if you require antd directly

vinurs19:11:06

is there a method exclude it?

thheller19:11:37

only import what you actually need

lilactown19:11:01

if I put what I’m asking for concretely, with my understanding of the problem: 1. would you be willing to add the ability for the load hooks to get passed any info about the ns that’s being loaded? symbol + metadata? 2. would you be willing to add a setting that would reload all dependent namespaces up to the entry point?

vinurs19:11:26

ok, understand

lilactown19:11:11

either 1 or 2 would potentially allow me to solve the reloading problem. 2 I feel like might have the most potential for problems, but I don’t know the complexity for 1

thheller19:11:37

1) the client side doesn't have that info. 2) I kinda chose not to add it https://github.com/thheller/shadow-cljs/issues/349

lilactown19:11:19

could that info could be passed to the client side from the compiler state?

thheller19:11:44

in theory yes.

thheller19:11:07

but you'll first need to make a convincing case why it is necessary at all

thheller19:11:35

I mean react-refresh is not even released yet. not going to add features for it until the design is finalized

thheller19:11:36

also would kinda like to understand it first

lilactown19:11:49

yeah totally

thheller19:11:30

but how do you even tell a component from a regular function?

thheller19:11:36

seems completely pointless to me?

lilactown19:11:15

I agree that the heuristic they chose is not a good one

lilactown19:11:45

the way I understand react-refresh to work today, is each time a component is registered (e.g. via a reload of the namespace), if the ID already exists it marks that ID as dirty. when performReactRefresh is run, it then goes through the React tree from the root and re-renders all of the components marked as dirty. if the hooks “signature” has changed since the last refresh, it will re-mount the component instead of re-rendering.

lilactown19:11:12

so the issue shows up when: - a function gets changed that some component uses inside of it’s render that’s not a registered component - that component’s namespace wasn’t reloaded, so it’s not marked as dirty - React doesn’t re-render it since it thinks it’s clean

tslocke19:11:30

Is it possible for a macro to detect :dev vs :release, e.g. to elide logging code?

thheller19:11:30

@tslocke in theory yes but you probably want your own setting for this

thheller19:11:00

:compiler-options {:external-config {:your/key ...}}

tslocke19:11:11

OK that's what I've been doing but I'm having trouble switching back to having the logging active after doing a release build. TBH I think I'm not clear on the correct way to do a release build, deploy it to the server, and then go back to developing.

thheller19:11:10

how did you access it exactly? and where did you put it?

thheller19:11:31

if you put it into the incorrect places or access it incorrectly the cache won't be invalidated

thheller19:11:54

the correct way to make a release build is shadow-cljs release your-build

tslocke19:11:04

I have it in my shadow-cljs.edn, under :builds -> :app -> :dev

thheller19:11:25

have what in that

tslocke19:11:14

When I run shadow-cljs release, it overwrites the same main.js as shadow-cljs watch - is that expected?

thheller19:11:28

if you configured it that way yes

tslocke19:11:40

(have what) :compiler-options {:external-config {:your/key ...}}

thheller19:11:42

:release {:output-dir "somewhere/else"} would change it

tslocke19:11:10

OK, maybe that's the simplest way to fix the issue then - I'll have the release build go somewhere else

thheller19:11:42

rm -rf public/js && shadow-cljs release app && copy-public-js-to-server

tslocke19:11:06

And I access it with (get-in @cljs.env/*compiler* [:options :external-config :logging])

thheller19:11:57

ok looks good then. release builds don't share any cache with the watch/compile builds so they usually don't interfere

mazin19:11:02

is there a way to specify the :lein option outside of the top level? context is that im trying to use lein with shadow and specify a different lein profile for :dev and :release within a build

thheller19:11:22

@mazin just call lein directly then

thheller19:11:42

lein with-profiles +release run -m shadow.cljs.devtools.cli release app

thheller19:11:48

same as calling shadow-cljs release app (just with the extra profile)

tslocke19:11:53

@thheller thanks so much. I'm once again amazed by how available you are to help out. Don't you get swamped?

✔️ 4
thheller19:11:25

right now I'm just hanging out and reading stuff

thheller19:11:31

helping out is more interesting 😉

mazin19:11:47

gotcha, thanks so much.

lilactown20:11:11

I went ahead and opened an issue on React to try and see if we can solve this without needing to change shadow-cljs 😛 https://github.com/facebook/react/issues/17281

Filipe Silva20:11:44

@lilactown so is the concept of react-refresh that re-registered components get refresh, but nothing else does?

lilactown20:11:17

re-registered components get re-rendered (possibly re-mounted if the hooks signature has changed), otherwise all components are left alone

Filipe Silva20:11:56

so it's essentially partial dependency invalidation

Filipe Silva20:11:34

which kinda makes sense because react only know how to invalidate and hot-reload a certain class of things, namely components

Filipe Silva20:11:02

react is saying "if you tell me when my stuff changes, I can make an honest effort at refreshing them because I know their semantics"

Filipe Silva20:11:48

but that's always been the problem at HMR in general... your app needs to work in a way amenable to HMR

Filipe Silva20:11:08

HMR concerns bleed into the application architecture

lilactown20:11:04

The thing that puzzles me right now is that, if React re-rendered the entire app, it would fix this most of the time I think

lilactown20:11:34

So I'm not sure why they only re-render the components which have changed

Filipe Silva20:11:45

re-rendering the whole app is the sort of hot reload shadowcljs already has today, right?

lilactown20:11:23

It is except calling React.render also re-MOUNTS everything

lilactown20:11:34

So you lose all local state

lilactown20:11:12

If you could re-render the whole app without losing local state, you have achieved the dream 😂

Filipe Silva20:11:47

and this refresh thing re-renders without re-mounting?

Filipe Silva20:11:11

then I think what you need to make this work is the dependency graph that shadowcljs has around

Filipe Silva20:11:06

then you can reason about it and say things like "namespace.path changed so I will mark all components that depend on it dirty"

Filipe Silva20:11:46

but I guess that's why you wanted the component metadata in the first place?

Filipe Silva20:11:33

if you go down to the symbol level and still have the dependencies it might be easier

Filipe Silva20:11:22

then you could have some reverse mapping that says "this react component in refresh stuff maps to that cljs function" and also "this reload affected all these cljs symbols" and cross reference those

lilactown20:11:42

yes, you can take the surgical approach where you analyze each components dependencies and detect whether any have changed

lilactown20:11:49

you can also take the sledgehammer approach, which is that shadow-cljs or figwheel could reload ALL dependent namespaces which would in turn mark any components within them as dirty

lilactown20:11:40

both of these require quite more work on the tooling side, which I understand why thheller is skeptical 😉

thheller20:11:41

you could just keep a registry of all and :dev/after-load make it dirty yourself

4
thheller20:11:55

I saw a register! call somewhere already

lilactown20:11:17

right, yes hmmm

lilactown20:11:30

each component gets roughly this emitted:

(def set-my-component-signature! (create-signature))

(def my-component ...)

(set-my-component-signature! my-component "hooks signature goes here")

(register! my-component "my-app.core/my-component")

lilactown20:11:46

I’m not sure how I would make it dirty. I suppose I could wrap each in a fn? that seems gross

thheller20:11:42

dunno either. does it use identity? like regular react or does react-refresh have different rulres?

lilactown20:11:24

I assume it does

thheller20:11:22

(register! my-component "my-app.core/my-component" (fn [new-component] (set! my-compoent new-component)) maybe

thheller20:11:46

probably needs an indirect call then though

thheller20:11:45

so (defn actual-component* ...) and (defn component [...] (actual-compoent* ...))

lilactown20:11:56

hmm not sure I get it yet

lilactown20:11:47

I think the gist of the thing is to give the call to react-refresh-runtime/register a new function

lilactown20:11:40

how much work would it be to add support for doing a full recompile of dependent namespaces? if just to experiment with it in relation to react-refresh

lilactown20:11:53

I want to see how bad it would be with a large project number of namespaces

thheller21:11:27

not easy to add right now. full recompile is also madness.

lilactown21:11:38

Yeah def not full recompile