Deploying to Heroku: dependencies of npm packages not getting installed when deploying app

I am writing a web application with the backend in Clojure and the frontend in Clojurescript with React and Reagent. I am trying to host it on Heroku.

When I run lein ubjerar locally, it builds fine and the app works.

However, when I try to deploy the app to Heroku via git push heroku master, I always end up with an error like this:

remote:        Preparing npm packages
remote:        Installing npm packages
remote:        npm packages successfully installed
remote:        Running shadow-cljs...
remote:        [:app] Compiling ...
remote:        The required JS dependency "object-assign" is not available, it was required by "node_modules/react/cjs/react.production.min.js".
remote:        Searched in:/tmp/build_c09494ebe081fa0581db343dc809fb45/node_modules
remote:        You probably need to run:
remote:          npm install object-assign
remote:        See:
remote:  !     Failed to build.
remote:  !     Push rejected, failed to compile Clojure (Leiningen 2) app.
remote:  !     Push failed
remote: Verifying deploy...
remote: !	Push rejected to getfluentspanish.
 ! [remote rejected] master -> master (pre-receive hook declined)

This happens in spite of the fact that object-assign is listed as a dependency of the react npm package, which I already have listed as an item in :npm-deps in my project.clj. I have verified that when I build locally, the package installs fine (i.e. in this case node_modules/object-assign exists).

When I manually add object-assign as an explicit dependency in :npm-deps, I get the same error, but complaining about a different missing dependency that should get installed automatically (and does locally). When I add the new missing dependency, it complains about another. When I build locally with the new explicit dependencies, lein uberjar works but complains that there are now version conflicts because the implicit dependencies are often fixed at a different version.

Any idea how I can fix this?

you you have yarn.lock or package-lock.json in your project? There are used to make sure packages are installed in same same version in every environment.

I’m not familiar with :npm-deps though…

:npm-deps is used by the lein-shadow plugin to keep NPM dependencies in the project.clj file.

shadow-cljs automatically generates the package.json and I believe package-lock.json is created automatically by NPM on install.

I ended up giving up on using Git-based Heroku deployment and instead made a Dockerfile and managed to get deploys working that way (using locally built uberjars).

Here’s the wonderfully simple Dockerfile I used to make it all work. No other changes were necessary:

FROM openjdk:8-alpine
COPY target/uberjar/getfluentspanish.jar /getfluentspanish/app.jar
CMD ["java", "-jar", "/getfluentspanish/app.jar"]

I am really glad I learned how to use Docker because it seems like direct PaaS support for Clojure/Clojurescript apps is pretty limited.

Hope this helps someone else with the same problem.