This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-02-09
Channels
- # announcements (20)
- # beginners (115)
- # calva (2)
- # clj-kondo (1)
- # clojure (48)
- # clojure-uk (21)
- # clojurescript (20)
- # css (1)
- # cursive (3)
- # datascript (11)
- # datomic (6)
- # duct (26)
- # emacs (5)
- # funcool (6)
- # off-topic (45)
- # perun (1)
- # precept (4)
- # quil (2)
- # re-frame (1)
- # shadow-cljs (251)
- # tools-deps (27)
- # uncomplicate (9)
I am having trouble finding how shadow-cljs is doing it in its source. Could you point me please?
I don’t know, the sources are too complex for me to understand just by reading them, if I had the project at hand, I would simply break in DevTools and look how shadow-cljs does it
try to put breakpoint here: https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/boot/browser.js#L125
Ok, thank you!
I have attempted to make the chromex example/sample (https://github.com/binaryage/chromex/tree/master/examples/sample) work with shadow-cljs :chrome-extension
I pretty much have the background page working (modulo some issue with the test-storage
function).
But the biggest problem is I can't figure out how to configure shadow-cljs to properly hook in the extension popup. I was just guessing it was related to the :browser-action
but my guess is clearly not working. I am still pretty unwashed when it comes to cljs / shadow-cljs and chrome extensions.
My attempt at this is at https://github.com/omnyway-labs/chromex-sample-shadow. The README details the issues.
If anyone has any helpful hints, I would be greatly appreciated. It seems pretty close and once this is made to work, it could be a good template for building chrome extensions with shadow-cljs.
@rberger I will look into this today. Will try to use your project as my start point and try to make it work.
The problem is that the outputs should be specified in manifest.edn, not in shadow-cljs.edn.
Hello, so I'm trying to containerize a basic ClojureScript/shadow-cljs development environment using Lando (https://docs.lando.dev). So far I've got the dev server serving static HTML/JS as it should be, but the HMR server only ever reports {:type :client/stale}
. Even when I ssh into the container and curl it directly:
$ curl 'localhost:9630/ws/worker/dev/ac9d0f31-11a6-4c6b-9c75-a6f6a5ed1756/8dcdadc5-c951-4188-9c08-c5881f658fe7/browser' -H 'Accept: */*' --compressed -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Extensions: permessage-deflate' -H 'Sec-WebSocket-Key: kC/iKKLLhR2/TcOVtWVO7Q==' -H 'Connection: keep-alive, Upgrade' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' -H 'Upgrade: websocket'
{:type :client/stale}
Not sure what to poke next to troubleshoot. Thanks!I should clarify that HMR works perfectly when I run shadow-cljs watch dev
directly on my machine. So something about the container environment is keeping it from seeing and/or reporting fresh compilations.
@rberger I made a pull request which should make things work.
Thanks, I woke up thinking that at least some of the configuration needed to be in the manifest.edn. The comments in https://github.com/thheller/shadow-cljs/issues/279#issuecomment-392007641 made it seem like some or all could/should be in shadow-cljs.edn.
@ctamayo :client/state
means that the js files you loaded in your HTML were not produced by the watch
you started. so I'm guessing you are serving old files or have some cache enabled that serves them
@rberger @posobin I got it working here as well: https://github.com/binaryage/chromex/commit/c73dedb0b0e2b43f2ead0aad8775782ec4016898
had to hack shadow-cljs for now, to get source maps working: https://github.com/binaryage/chromex/commit/c73dedb0b0e2b43f2ead0aad8775782ec4016898#diff-cf6355f4ec475b1cac556fde53d8914aR188-R192
@thheller is that determination made by proc-id? Here in ws.clj?
(not= proc-id (:proc-id worker-proc))
(send-ws-error :client/stale)
If so, that could mean that the newer watch may not be generating new files at all, correct?
no no no, this is definitely a bug in my code. I'm just trying to understand how stuff works. 😄
right, so what I'm trying to determine is what "paths and stuff" are likely to get messed up, so I know which direction to go in. So let me rephrase: it's possible that something about the paths and stuff make it so that the watch is not actually producing new files, or not putting them in the right place?
by "bad browser cache" do you mean the browser itself could be erroneously caching the result of a previous request to the WebSocket?
the issue is that the JS you are loading is outdated. it thus has an outdated websocket URL, which the server is telling you.
all you have to do is look at your :output-dir
and make sure that is the actual files you are serving
I just tried to compile the example chromex extension with shadow in release mode, backgropund page and content script work, but popup (browser action) was generated without dependencies
This is the compilation output with --pseudo-names
:
https://gist.github.com/darwin/a2ce8ee9079f78842f62e9e89dfc6615#file-popup-js-L96
$chromex$ext$runtime$connect_STAR_$$
is missing
not sure if this is caused by :chrome-extension
:target
:
https://github.com/binaryage/chromex/tree/master/examples/shadow
https://github.com/binaryage/chromex/blob/master/examples/shadow/shadow-cljs.edn#L23
aah, let me try it, btw. interesting is that in dev mode this worked event without shared.js
so last standing issue is with source maps, devtools does not allow loading .map files from chrome-extension://…/something.map
URLs
I was able to reproduce the source map issue after removed that custom hack. In current Chrome and also in Chrome Canary. You have to open extension’s background page to get spam of
`
DevTools don’t see those source maps, so for example clicking on a link to source code in console does not work, it shows generated .js file , not cljs file as expected
that is just a misleading message from current Chrome - Chrome Canary has it improved and communicates what went wrong
the source maps are perfectly ok, just the code responsible for loading sourcemaps for chrome devtools is picky about URL scheme
when I use my prefix-hack to load the same source maps via dev-server it works as expected
maybe they are calling some internal chrome API which has some security hardening and uses different code path than is used when you load page in normal tab
this should not be related because DevTools is not the same context as extension itself
this is the originating place for the error message: https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/sdk/SourceMap.js#L232 I’m digging further
btw. this is long-standing issue, I never found a good solution for it, that is also why I advocated inlined source maps as option in shadow-cljs
I also did https://clojure.atlassian.net/projects/CLJS/issues/CLJS-1902, which is still waiting for merge
that devtools code finally uses this at the bottom: https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/host/ResourceLoader.js#L172
it does serve it with text/plain though. maybe it doesn't like that for some reason?
so it depends on host implementation of that loadNetworkResource fn, and I guess that is picky about that chrome-extension:// URI
I have a leiningen multimodule build with shadow compiled reagent ui. now, shadow modules are meant for runtime modules and shadow doesn't seem to understand lein-modules. Is there a way to include cljs sources from a different lein-module? part of the ui is supposed to be deployed as a lib and shared between two different applications
the best I have for now is just to set the src path to my shared module. It's not too bad but requires everybody else to checkout the same layout even though they may not work on all of the other parts
> part of the ui is supposed to be deployed as a lib and shared between two different applications
that also makes no sense to me. you mean the sources should be shared right? compiled output can't be shared between builds
@thheller, do you have any suggestions on how to fix this? https://clojurians.slack.com/archives/C6N245JGG/p1581204099310900
(sorry, looks like many questions about extensions today)
thanks @thheller. lein-modules are https://github.com/jcrossley3/lein-modules but perhaps I need Leiningen checkouts instead. Yes, we'll I don't have the share lib/module yet, but yes I figured easiest just to share the source
Tldr: content scripts are hot reloaded when code changes, but on page refresh the old content script is loaded by chrome. How can I make my content script fetch the last script from shadow-cljs and execute it on page load?
but pretty sure that some things can't be worked around since its just how chrome exts work
Yeah, but if that script that is loaded only once says to load the main content script from shadow server, that file on disk doesn't need to be refreshed by chrome. I just had trouble finding where shadow-cljs does hot reloading in its sources.
That should work, but I don't know how to do that.
Basically I want to do hot reloading not only on file changes, but also when the browser loads the page and executes the content script.
ad that chrome ext caching problem - I proposed a kind of workaround, where content script code would init with a check for stale source code situation and did one initial hot-reload pass if stale content was detected but I guess this smells like a lot of hairy work
maybe having content script impl setup as a dynamic module and doing intial dynamic module load every time on init could also fix it
any magic other method that tries to read that file would also get it the cached version
it is cached by extension subsystem, that has little to do with regular network cache in chrome
I think I could try to implement example of this tomorrow, content script would only contain dynamic module load, and that dynamic module would be the main content script implementation
I just tried the js/chrome.runtime.reload
and it is not a good solution
it blows extension state, including background page and others
The runtime.reload is the same as reloading from the extensions setting page.
I am still trying to understand how shadow hot reloads the file.
I tried that idea with the dynamic code eval, I’m able to fetch it from dev-server, but I ran into issues with goog.provide, it throws when stuff gets redeclared
Yeah, was looking at that.
I think we could be able to hook do-js-reload
and on content script init replay all reloads since last extension reload
I haven't figured out the resources ids so far.
Whew, I have finally done it.
(defn refresh-source []
(let [this-file
{:resource-id [:shadow.build.classpath/resource "ext/content.cljs"]
:module :content-script :ns 'ext.content
:output-name "ext.content.js"
:resource-name "ext/content.cljs"
:type :cljs
:provides #{'ext.content}
:warnings []
:from-jar nil}]
(shadow.browser/handle-build-complete
{:info {:sources [this-file] :compiled #{(:resource-id this-file)}}
:reload-info {:never-load #{} :always-load #{}
:after-load
[{:fn-sym 'ext.content/loaded
:fn-str "ext.content.loaded"}]}})))
Init function has to call this with some timeout (I use 500ms) to let the shadow cljs make the connection I think, not exactly sure why.
content.cljs
is the name of the content script file, loaded
is the function that you want to run when the content script loads (not necessary to add any tags to it).
@posobin cool, but I wonder if this works more generally if you have multiple cljs files in the content script output
You can specify multiple files there.
Or do you want to get the list automatically?
For that you would have to query the shadow cljs server for the list of changed files first, I have not seen such methods in its sources.
here is what I did: 1. wrap js/shadow.cljs.devtools.client.browser.do_js_load and record all reloaded sources there 2. replay recorded sources on intial load in content script the problem is “where to record that data”, I ended up sending it to background page and requesting it back on initial load
How did you do that?
I haven't figured out how to change the shadow sources.
btw, hooking that fn just for printing sources for your method could be helpful as well
Yeah, I was messing with the compiled output...
Basically spent the whole day yesterday on that...
an alternative would be a macro which could give you all relevant namespaces in the content script project and you would synthetise resources (sources) from it
And how do you request it on init?
Also how do I patch the shadow server?
Ah, you record it in background.
Not on the server.
Yes, I understand that. I thought it was doing the post-message to server.
this is request to reload sources on init load, didn’t finish this part properly background page then replies with all recorded sources and I feed it into do-js-load
Nice.
potential optimization would be to prune all sources with the same resource it and use only the last one
but in this form my code does not need to understand sources structure, it just records it and replays it on init load in the same order
it looks like when I load the same js source it complains and when shadow-cljs does it, it works
Wait 500ms before requesting.
That solved it for me.
This exact problem.
this seems to work: https://github.com/binaryage/chromex/commit/16588dc73e4f2ec65ef27ce2d6a0ecf8d5f9e59d
as I wrote above, it could be a bit smarter and filter out all repeated resources and use only the most recent one for each
here: https://github.com/binaryage/chromex/commit/f508b6e25d2be5972c22fec7f460b31036ea43d1
I’m done for now, will leave it in experimental branch for people interested in something like this
I really don't quite follow anymore what you are doing here but the solution looks crazy to me. there has to be a better way.
to recap the problem is that a content-script does not load the correct source from disk once it has been injected right?
ie. did anyone bother too look at the actual content-script.js to see if it gets updated correctly?
the problem is that content script lifetime is dependent on the hosted page - if you refresh the page in browser, the content script’s javascript context is destroyed and new instance of content script is injected into the page - unfortunately the extension subsystem injects cached version, because extension itself wasn’t refreshed (the background page is still running and want it to stay)
when you have content script injected and don’t refresh the hosting page, and when you modify sources, shadow-cljs correctly hot-reloads changes, but these changes get lost on next page refresh
if I understand it correctly, I would have to implement some process watching pages being opened and inject code depending on what page is opening
then I would probably need my content script to be single file, which would limit build config, I guess
the way this is currently handled is already problematic so there needs to be a better way anyways
maybe I could put some time in and fix this in chrome itself: https://bugs.chromium.org/p/chromium/issues/detail?id=104610#c1
the solution would be for chrome extension sub-system to watch for modified files on disk in dev mode
let me try to setup a simple reproduce for this first .. I still don't quite understand what this is all about
btw. if it was too much to setup, you could follow https://github.com/binaryage/chromex/tree/master/examples/shadow
I'm sorry but that setup is so far away from my usual setups that I cannot follow it at all
I don't like splitting things into multiple source paths like that when one would do 😛
IMO, if you perfectly understand what your build tool does, then yes, otherwise it is just defensive stategy which won’t hurt 🙂 this layout was created by me in leiningen + cljs-build plugin times
@thheller steps to reproduce what's not working: load your extension example in browser, open any page, "▶️❤️◀️" will be printed in the console, change that string in content.cljs
, reload the page, "▶️❤️◀️" will still be printed instead of a new string.
i'm not quiet understand what am i asking, but how do i specify js deps while shadow-cljs managed by lein? webjars?
js deps are handled via npm, so package.json. shadow-cljs does not use/support webjars.
so for a fullstack app that will be project.clj for backend deps and project.json for front?