polylith

2025-01-28T12:38:59.508639Z

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

2025-01-28T12:56:19.607599Z

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)

imre 2025-01-28T13:36:39.756919Z

FYI cljs support is incoming, you might want to check out https://github.com/polyfy/polylith/pull/524

🔥 2
2025-01-28T13:40:00.118859Z

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 ?

imre 2025-01-28T13:41:24.736239Z

Joakim or Furkan should be able to answer that. I haven't used cljs with poly yet, just saw the pr

👌 1
furkan3ayraktar 2025-01-28T15:38:29.875049Z

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.

2025-01-28T15:40:15.829019Z

Thank you for reaching out! Means much. I will take a look immidiately

furkan3ayraktar 2025-01-28T15:43:33.169069Z

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.

2025-01-28T15:44:35.526249Z

Sure I will ask if something will be not clear. Now I will research your code)

2025-01-30T17:18:42.864009Z

So I still can't figure out how to run one REPL for everything in development project. We have to use shadow-cljs somehow?

furkan3ayraktar 2025-01-31T06:20:37.139269Z

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.

👌 1
2025-01-29T13:48:13.117029Z

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

furkan3ayraktar 2025-01-29T15:19:19.282819Z

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

furkan3ayraktar 2025-01-29T15:20:28.530149Z

Simply, replace the commit SHA for the :poly alias in the root deps.edn with the latest SHA: 7d8f41cc8c87a4d4635a0189856227d98787d188

👌 1
Andrew Leverette 2025-01-28T18:23:28.743089Z

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.

Andrew Leverette 2025-01-28T18:29:01.400949Z

The config file just contains some settings for the ring-jetty server and system definition for integrant.

seancorfield 2025-01-28T19:15:01.024479Z

How are you reading the config file? io/resource?

seancorfield 2025-01-28T19:16:02.909529Z

Your config component's deps.edn should have :paths ["src" "resources"] (assuming resources/config.edn is where your config file is).

Andrew Leverette 2025-01-28T19:23:13.164579Z

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.

Andrew Leverette 2025-01-28T19:23:42.057489Z

I'm thinking it's probably something with either how the project is set up or how the jar file is created.

seancorfield 2025-01-28T19:25:13.309369Z

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

Andrew Leverette 2025-01-28T19:25:35.255699Z

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)

Andrew Leverette 2025-01-28T19:25:45.910219Z

Ah, okay I'll check that

Andrew Leverette 2025-01-28T19:27:09.702899Z

seancorfield 2025-01-28T19:28:30.582829Z

OK, so (io/resource "config/local.env.edn") should work just fine inside the JAR when you run it.

seancorfield 2025-01-28T19:30:29.210149Z

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

Andrew Leverette 2025-01-28T19:33:59.719249Z

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))))))

Andrew Leverette 2025-01-28T19:34:39.419369Z

Should the path just be "config/local.env.edn"?

seancorfield 2025-01-28T19:35:47.353079Z

I would try that, yes.

seancorfield 2025-01-28T19:36:26.456629Z

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")
???

😲 1
Andrew Leverette 2025-01-28T19:37:34.191819Z

That was it!

seancorfield 2025-01-28T19:37:41.001419Z

rlwrap java -cp ... if you want a slightly less basic REPL

Andrew Leverette 2025-01-28T19:38:20.409439Z

I'm not sure I understand why that makes a difference.

seancorfield 2025-01-28T19:39:16.869069Z

File under "Doctor, it hurts when I do this!" Doctor: "Well, don't do that then..."

🤣 3
Andrew Leverette 2025-01-28T19:42:15.709789Z

Lesson learned.

Andrew Leverette 2025-01-28T19:42:34.744229Z

Thanks for working through that with me.

Andrew Leverette 2025-01-28T19:42:53.543589Z

You mentioned something about the build script usually being at the top level.

Andrew Leverette 2025-01-28T19:44:36.498209Z

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?

seancorfield 2025-01-28T19:49:17.913929Z

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.

seancorfield 2025-01-28T19:49:36.160009Z

clojure -T:build uberjar :project polytask-api

seancorfield 2025-01-28T19:49:59.962119Z

At work, our build.clj is in the workspace root and we build 24 different projects from it.

seancorfield 2025-01-28T19:51:04.298689Z

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

Andrew Leverette 2025-01-28T19:51:27.927059Z

That makes sense.

seancorfield 2025-01-28T19:52:08.713059Z

(although we recently added bases/build and everything is in that brick, but we can still do clojure -T:build ...)

Andrew Leverette 2025-01-28T19:52:38.959359Z

Hmm, a build base...

Andrew Leverette 2025-01-28T19:52:43.122489Z

I like that.

seancorfield 2025-01-28T19:52:51.760489Z

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}

seancorfield 2025-01-28T19:53:32.212179Z

(and projects/build/src is where we have our user.clj -- so it can't interfere in anything else)

Andrew Leverette 2025-01-28T19:53:42.928439Z

I feel like I'm going to have to start paying you with Old Speckled Hen to keep you on Clojure question retainer.

seancorfield 2025-01-28T19:53:51.302039Z

LOL!

seancorfield 2025-01-28T19:54:00.188489Z

"Will Code for Beer!" 🙂

🤣 1