webpackclojurescriptreagentshadow-cljsclojurescript-javascript-interop

How to import a shadow-cljs project into another shadow-cljs project with a different configuration


I currently have 2 shadow-cljs projects with 2 different ways to manage their dependencies to npm libraries.

One uses the default shadow-cljs.edn configuration :js-options {:js-provider :shadow}, we will call it project A.

The other, we will call it project B, uses the external configuration with the use of webpack inside the shadow-cljs.edn file :js-options {:js-provider :external :external-index "target/index.js"} as described in the following article How about webpack now?

Locally I can run these project A and project B independently without errors.

However, I would now like to import project A into project B, and use the method my-function from project_A.core.


(ns project_A.core)

(defn ^:export my-function  [] ...)

I tried to release project A by specifying the :target field of the shadow-cljs.edn file to the value :npm-module.

Project A > shadow-cljs.edn :

{
[...]
 :builds   {:app {:target           :npm-module
                  :output-dir       "release/"
                  :entries          [project_A.core]
                  :js-options       {:js-provider :external :external-index "target/index.js"}}}
[...]
}}

Then I install it in project B i did a npm install path/to/project_A, as for a classic npm package and to use it the same way as the others.

I tried to add the local dependency like this:

Project B > package.json :

{
  "scripts": {[...]},
  "devDependencies": {[...]},
  "dependencies": {
    [...]
    "project_A": "file:path/to/project_A",
    [...]
  },
  "name": "projet B",
}

And I try to import the package inside the ns require field. However project B does not compile.

Is there a clean way to import a project into the other one while taking into account their different configuration?


Solution

  • Your approach is incorrect. You never include the build output of one CLJS library/project in another. You always include the sources directly instead, and then build the proper final output only where you are going to use it. You cannot include CLJS libraries via npm package.json, neither should you want to.

    So, for the purpose of this explanation the build config in A is entirely irrelevant. It doesn't need to be built, we only want access to the sources.

    In B you have a couple options of using it. Say you have everything in ~/code/project-a and ~/code/project-b.

    In ~/code/project-b/shadow-cljs.edn you can specify :source-paths ["src/main" "../project-a/src/main"]. This will make all sources from A directly accessible when building B. It does however require manually transferring the :dependencies from A to B.

    Another option is using a local install (or maven install), but for this you'll need lein or deps.edn to build the .jar. shadow-cljs itself does not support publishing "libraries".

    Another option is using the clojure tools deps.edn with :local/root.

    ;; ~/code/project-a/deps.edn
    
    {:paths ["src/main"]
     :deps {...}
     :aliases
     {:dev {:extra-deps {thheller/shadow-cljs {:mvn/version "2.22.8"}}}}}
    
    ;; ~/code/project-b/deps.edn
    
    {:paths ["src/main"]
     :deps {my.company/project-a {:local/root "../project-a"}}
     :aliases
     {:dev {:extra-deps {thheller/shadow-cljs {:mvn/version "2.22.8"}}}}}
    
    ;; ~/code/project-b/shadow-cljs.edn
    
    {:deps {:aliases [:dev]}
     :builds ...}
    

    Also make sure you actually need to split projects in the first place. It is perfectly fine and even encouraged to build mutiple outputs out of one project. So, often I see people splitting things because of "best practices". Which often isn't necessary and just makes things complicated for no good reason. IMHO, YMMV. If you really must, deps.edn is the most flexible option.

    As far as npm dependencies are concerned you project-a can express them via a deps.cljs file on the classpath. So, with the above config ~/code/project-a/src/main/deps.cljs with {:npm-deps {"the-dep" "the-version"}}. These must be manually added, as the package.json is not "inherited". When you start shadow-cljs in B it'll pick those :npm-deps and transfer them to the package.json in B. You then build everything as usual in B.