Hi folks! What is be a better way to structure my project? My needs: 1. Polylith-based Clojure backend 2. ClojureScript Re-Frame frontend 3. NodeJS service written in ClojureScript 4. WebSocket interaction but I don’t think that’s matter 5. I want them all in one monorepo
AI suggest this:
my-project/
├── deps.edn
├── bases/
│ └── app/
│ └── src/
│ └── my_project/
│ └── app/
│ └── core.clj
├── components/
│ └── shared/
│ └── src/
│ └── my_project/
│ └── shared/
│ └── utils.cljc ;; Shared code between Clj and Cljs
├── projects/
│ └── app/
│ └── src/
│ └── my_project/
│ └── app/
│ └── main.clj
├── cljs/
│ ├── frontend/
│ │ └── src/
│ │ └── my_project/
│ │ └── frontend/
│ │ └── core.cljs
│ ├── node-module/
│ │ └── src/
│ │ └── my_project/
│ │ └── node_module/
│ │ └── core.cljs
│ └── shadow-cljs.edn ;; Configuration for ClojureScript builds
└── resources/
└── public/
└── index.html
Looks nice but I interested in answer from experienced human users)FYI cljs support is incoming, you might want to check out https://github.com/polyfy/polylith/pull/524
But I can't understand how I can mix clj and cljs files in one workspace when I have to set :dialects option to either :cls or :cljs ?
Joakim or Furkan should be able to answer that. I haven't used cljs with poly yet, just saw the pr
You can set it to :dialects #{"clj" "cljs"} to support both. The best approach in your case is to have a single Polylith workspace in the standard Polylith structure. Write regular components and bases in clj, cljs, cljc or a mixture of all. Then, pick those bricks in any combination to create your deployable artefacts (projects) based on your requirements.
We at work have a such Polylith workspace where we have JVM and NodeJS targeting backend services and a regular browser application. We use shadow-cljs with deps.edn support to build NodeJS and browser applications. We use https://classic.yarnpkg.com/lang/en/docs/workspaces/ to achieve a dependency resolution similar to what deps.edn gives with local/root.
Even though it's not final (it works, but I want to add more examples and docs), I created an example project mixing Clojure and ClojureScript in one Polylith workspace on https://github.com/furkan3ayraktar/clojure-polylith-realworld-example-app/tree/cljs-frontend branch.
Thank you for reaching out! Means much. I will take a look immidiately
Let me know if you have further questions! The branch I shared has a CI config, if you want to see how I build/test frontend application. Also, build.clj is a good place to look.
In that branch, the frontend bit is still pretty simple, just one component, web-ui and one base web-app with cljs code. However, you can go crazy and have many components and bases where you can mix dialects.
Sure I will ask if something will be not clear. Now I will research your code)
So I still can't figure out how to run one REPL for everything in development project. We have to use shadow-cljs somehow?
Yes, I prefer shadow-cljs, but other ways of running REPLs dor ClojureScript should work I assume. shadow-cljs already boots up two REPLs: one for jvm and another for Node/Browser depending on the target configuration. When I use Calva’s jack-in command in a shadow-cljs project, the IDE connects to correct REPL automatically based on the extension of the file you are currently on. If you are on a cljc file, it has a toggle at the bottom allowing to switch between JVM or Node/Browser REPLs.
Something weird about polylith commit you used:
▸ clj -M:poly create component name:config
Cloning:
Error building classpath. Commit not found for polylith/clj-poly in repo at 6ca059f206e7771c5b0173622673df9ef76d25ec It's because the branch on the polylith repo was rebased. You can use the latest commit from the cljs-support branch in the polylith repo
Simply, replace the commit SHA for the :poly alias in the root deps.edn with the latest SHA: 7d8f41cc8c87a4d4635a0189856227d98787d188
Okay, I'm unsure what channel this question should be in, but I'm working on a Polylith project. I have a component called config where I have placed an edn file in the resources directory of that component. The config component is used by a base called api . I have created a project that uses the base and the config with a build.clj file to build an uber jar. When I run things through the REPL environment, everything works as expected. The edn file is read when the config component loads and everything is good. When I run the jar file, I get errors saying the edn file can't be found. I'm not too familiar with building jar files, so I'm sure I'm doing something wrong here. Any help would be appreciated. I can post my code if that is helpful, it's just a learning project anyway.
The config file just contains some settings for the ring-jetty server and system definition for integrant.
How are you reading the config file? io/resource?
Your config component's deps.edn should have :paths ["src" "resources"] (assuming resources/config.edn is where your config file is).
Yes, I'm using io/resource and the config component's deps.edn file does have the paths specified. The actual path is resources/config/config.edn.
I'm thinking it's probably something with either how the project is set up or how the jar file is created.
OK, so you have :paths ["src" "resources"] in the component, and (io/resource "config/config.edn")
Next thing to check is whether it is in your uberjar: jar -tvf path/to/the-uber.jar | fgrep config.edn
This is the build.clj in the project directory - ./projects/polytask-api
(ns build
(:refer-clojure :exclude [test])
(:require [clojure.tools.build.api :as b]))
(def main 'polytask.api.core)
(def class-dir "target/classes")
(defn- uber-opts [opts]
(assoc opts
:main main
:uber-file "target/polytask-api-standalone.jar"
:basis (b/create-basis)
:class-dir class-dir
:src-dirs ["src"]
:ns-compile [main]))
(defn uber "Build the uberjar." [opts]
(b/delete {:path "target"})
(let [opts (uber-opts opts)]
(println "\nCopying source...")
(b/copy-dir {:src-dirs ["resources" "src"] :target-dir class-dir})
(println (str "\nCompiling " main "..."))
(b/compile-clj opts)
(println "\nBuilding JAR...")
(b/uber opts))
opts)Ah, okay I'll check that
OK, so (io/resource "config/local.env.edn") should work just fine inside the JAR when you run it.
I think most Polylith repos have build.clj in the root and build specific requested projects. See https://github.com/furkan3ayraktar/clojure-polylith-realworld-example-app/blob/master/build.clj for example, although the usermanager-example Polylith version has it in the specific project folder https://github.com/seancorfield/usermanager-example/blob/polylith/projects/usermanager/build.clj
I'll take a look at that. I wonder if using a relative path might have something to do with this now that you you have pointed out how the resources should be connected. Here is the function that is reading the config file.
(defn- read-env-file
"Given an environment keyword, return a map of the environment file contents
stored in an edn file."
[env]
(let [config-file (str "./config/" (name env) ".env.edn")
file (io/resource config-file)]
(if file
(parse-env-file file)
(throw (Exception. (str "Could not find config file: " config-file))))))Should the path just be "config/local.env.edn"?
I would try that, yes.
You can test it in a REPL BTW:
java -cp path/to/the-uber.jar clojure.main
user=> (require '[clojure.java.io :as io])
nil
user=> (io/resource "config/local.env.edn")
???That was it!
rlwrap java -cp ... if you want a slightly less basic REPL
I'm not sure I understand why that makes a difference.
File under "Doctor, it hurts when I do this!" Doctor: "Well, don't do that then..."
Lesson learned.
Thanks for working through that with me.
You mentioned something about the build script usually being at the top level.
So, if I had two projects, would I have a build script at the top level that would have functions that would build the specific projects?
Per the one I linked to in the real world poly example, https://github.com/furkan3ayraktar/clojure-polylith-realworld-example-app/blob/master/build.clj#L32-L35 you tell uberjar which project you want to build.
clojure -T:build uberjar :project polytask-api
At work, our build.clj is in the workspace root and we build 24 different projects from it.
Our build.clj also has functions to run antq (for outdated dependencies), athos/check-clj (for reflection warnings), poly-check, poly-test, etc, etc, etc
That makes sense.
(although we recently added bases/build and everything is in that brick, but we can still do clojure -T:build ...)
Hmm, a build base...
I like that.
Our :build alias:
:build
{:extra-deps {ws/build {:local/root "projects/build"}}
:jvm-opts ["--enable-preview"
"-client"
"-Dclojure.core.async.go-checking=true"
"-Dclojure.spec.check-asserts=true"
"-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"
"-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"
"-Djdk.httpclient.allowRestrictedHeaders=host"
"-Djdk.tracePinnedThreads=short"
"-Dlog4j2.formatMsgNoLookups=true"
"-Dlog4j2.julLoggerAdapter=org.apache.logging.log4j.jul.CoreLoggerAdapter"
"-Dlogged-future=synchronous"
"-Duser.timezone=UTC"
"-XX:-OmitStackTraceInFastThrow"
"-XX:+TieredCompilation"
"-XX:TieredStopAtLevel=1"
;; for the subprocess that poly test invokes:
"-Dpoly.test.jvm.opts=:poly-test-jvm-opts"]
:ns-default ws.build}(and projects/build/src is where we have our user.clj -- so it can't interfere in anything else)
I feel like I'm going to have to start paying you with Old Speckled Hen to keep you on Clojure question retainer.
LOL!
"Will Code for Beer!" 🙂