返回介绍

16. Troubleshooting

发布于 2023-07-23 16:03:27 字数 11878 浏览 0 评论 0 收藏 0

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:

Listing all active dependencies
$ 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.

Example listing of deps
# 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.

shadow cljs repl

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文