- 1. Introduction
- 2. Installation
- 3. Usage
- 4. REPL
- 5. Configuration
- 6. Build Configuration
- 7. Targeting the Browser
- 8. Targeting JavaScript Modules
- 9. Targeting React Native
- 10. Targeting node.js
- 11. Embedding in the JS Ecosystem The :npm-module Target
- 12. Testing
- 13. JavaScript Integration
- 14. Generating Production Code All Targets
- 15. Editor Integration
- 16. Troubleshooting
- 17. Publishing Libraries
- 18. What to do when things don’t work?
- 19. Hacking
16. Troubleshooting
16.1. Startup Errors
Sometimes shadow-cljs
can fail to start properly. The errors are often very confusing and hard to identify. Most commonly this is caused by a few dependency conflicts on some of the important dependencies. When using just shadow-cljs.edn
to manage your :dependencies
it will provide a few extra checks to protect against these kinds of errors but when using deps.edn
or project.clj
these protections cannot be done so these errors happen more often when using those tools.
Generally the important dependencies to watch out for are
org.clojure/clojure
org.clojure/clojurescript
org.clojure/core.async
com.google.javascript/closure-compiler-unshaded
Each shadow-cljs
version is only tested with one particular combination of versions and it is recommended to stick with that version set for best compatibility. It might work when using different versions but if you encounter any kind of weird issues consider fixing your dependency versions first.
You can find the required dependencies for each version on clojars:
The way to diagnose these issues vary by tool, so please refer to the appropriate section for further info.
Generally if you want to be sure you can just declare the matching dependency versions directly together with your chosen shadow-cljs
version but that means you must also update those versions whenever you upgrade shadow-cljs
. Correctly identifying where unwanted dependencies may be more work but will make future upgrades easier.
shadow-cljs
will likely always be on the very latest version for all the listed dependencies above so if you need to stick with an older dependency you might need to stick with an older shadow-cljs version as well.
shadow-cljs
is very often several versions ahead on the com.google.javascript/closure-compiler-unshaded
version it uses, so if you are depending on the version org.clojure/clojurescript
normally supplies that might cause issues. Make sure the thheller/shadow-cljs
version is picked over the version preferred by org.clojure/clojurescript
.
If you want to make your life easier just use shadow-cljs.edn
to manage your dependencies if you can. It is much less likely to have these problems or will at least warn you directly.
If you have ensured that you are getting all the correct versions but things still go wrong please open a Github Issue with a full problem description including your full dependency list.
16.1.1. deps.edn / tools.deps
When using deps.edn
to manage your dependencies via the :deps key in shadow-cljs.edn
it is recommended to use the clj
tool directly for further diagnosis. First you need to check which aliases you are applying via shadow-cljs.edn
. So if you are setting :deps {:aliases [:dev :cljs]}
you’ll need to specify these aliases when running further commands.
First of all you should ensure that all dependencies directly declared in deps.edn
have the expected version. Sometimes transitive dependencies can cause the inclusion of problematic versions. You can list all dependencies via:
$ clj -A:dev:cljs -Stree
This will list all the dependencies. Tracking this down is a bit manual but you’ll need to verify that you get the correct versions for the dependencies mentioned above.
Please refer to the official tools.deps documentation for further information.
16.1.2. project.clj / Leiningen
When using project.clj
to manage you dependencies you’ll need to specify your configured :lein
profiles from shadow-cljs.edn
when using lein
directly to diagnose the problem. For example :lein {:profile "+cljs"}
would require lein with-profile +cljs
for every command.
# no profile
$ lein deps :tree
# with profile
$ lein with-profile +cljs deps :tree
This will usually list all the current conflicts at the top and provide suggestions with the dependency tree at the bottom. The suggestions aren’t always fully accurate so don’t get mislead and don’t add exclusions to the thheller/shadow-cljs
artifact.
Please refer to the Leiningen documentation for more information.
16.2. REPL
Getting a CLJS REPL working can sometimes be tricky and a lot can go wrong since all the moving parts can be quite complicated. This guide hopes to address the most common issues that people run into and how to fix them.
16.2.1. Anatomy of the CLJS REPL
A REPL in Clojure does exactly what the name implies: Read one form, Eval it, Print the result, Loop to do it again.
In ClojureScript however things are a bit more complicated since compilation happens on the JVM but the results are eval’d in a JavaScript runtime. There are a couple more steps that need to be done due in order to "emulate" the plain REPL experience. Although things are implemented a bit differently in shadow-cljs
over regular CLJS the basic principles remain the same.
First you’ll need a REPL client. This could just be the CLI (eg. shadow-cljs cljs-repl app
) or your Editor connected via nREPL
. The Client will always talk directly to the shadow-cljs
server and it’ll handle the rest. From the Client side it still looks like a regular REPL but there are a few more steps happening in the background.
1) Read: It all starts with reading a singular CLJS form from a given InputStream. That is either a blocking read directly from stdin
or read from a string in case of nREPL
. A Stream of characters are turned into actual datastructures, "(+ 1 2)"
(a string) becomes (+ 1 2)
(a list).
2) Compile: That form is then compiled on the shadow-cljs
JVM side and transformed to a set of instructions.
3) Transfer Out: Those instructions are transferred to a connected JavaScript runtime. This could be a Browser or a node
process.
4) Eval: The connected runtime will take the received instructions and eval
them.
5) Print: The eval
result is printed as a String in the JS runtime.
6) Transfer Back: The printed result is transferred back to the shadow-cljs
JVM side.
7) Reply: The JVM side will forward the received results back to initial caller and the result is printed to the proper OutputStream (or sent as a nREPL message).
8) Loop: Repeat from 1).
16.2.2. JavaScript Runtimes
The shadow-cljs
JVM side of things will require one running watch
for a given build which will handle all the related REPL commands as well. It uses a dedicated thread and manages all the given events that can happen during development (eg. REPL input, changing files, etc).
The compiled JS code however must also be loaded by a JS runtime (eg. Browser or node
process) and that JS runtime must connect back to the running shadow-cljs
process. Most :target
configurations will have the necessary code added by default and should just connect automatically. How that connect is happening is dependent on the runtime but usually it is using a WebSocket to connect to the running shadow-cljs
HTTP server.
Once connected the REPL is ready to use. Note that reloading the JS runtime (eg. manual browser page reload) will wipe out all REPL state of the runtime but some of the compiler side state will remain until the watch
is also restarted.
It is possible for more than one JS runtime to connect to the watch
process. shadow-cljs
by default picks the first JS runtime that connected as the eval
target. If you open a given :browser
build in multiple Browsers only the first one will be used to eval
code. Or you could be opening a :react-native
app in iOS and Android next to each other during development. Only one runtime can eval and if that disconnects the next one takes over based on the time it connected.
16.2.3. Missing JS runtime
No application has connected to the REPL server. Make sure your JS environment has loaded your compiled ClojureScript code.
This error message just means that no JS runtime (eg. Browser) has connected to the shadow-cljs
server. Your REPL client has successfully connected to the shadow-cljs
server but as explained above we still need a JS runtime to actually eval
anything.
Regular shadow-cljs
builds do not manage any JS runtime of their own so you are responsible for running them.
:target :browser
For :target :browser
builds the watch
process will have compiled the given code to a configured :output-dir
(defaults to public/js
). The generated .js
must be loaded in a browser. Once loaded the Browser Console should show a WebSocket connected
message. If you are using any kind of custom HTTP servers or have over-eager firewalls blocking the connections you might need to set some additional configuration (eg. via :devtools-url). The goal is to be able to connect to the primary HTTP server.
:target :node-script, :node-library
These targets will have produced a .js
file that are intended to run in a node
process. Given the variety of options however you’ll need to run them yourself. For example a :node-script
you’d run via node the-script.js
and on startup it’ll try to connect to the shadow-cljs
server. You should see a WebSocket connected
message on startup. The output is designed to only run on the machine they were compiled on, don’t copy watch
output to other machines.
:target :react-native
The generated <:output-dir>/index.js
file needs to be added to your react-native
app and then loaded on an actual device or emulator. On startup it will also attempt to connect to the shadow-cljs
server. You can check the log output via react-native log-android|log-ios
and should show a WebSocket connected
message once the app is running. If you see a websocket related error on startup instead it may have failed to connect to the shadow-cljs process. This can happen when the IP detection picked an incorrect IP. You can check which IP was used via shadow-cljs watch app --verbose
and override it via shadow-cljs watch app --config-merge '{:local-ip "1.2.3.4"}'
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论