How to use Capacitor inside of an Nx monorepo

Jorge Vergara pointing at a target showing an arrow in the bullseye

If you’re adding Capacitor to your Nx monorepo, there’s a nice tool called Nx Extensions that lets you add Capacitor and then it generates new nx run commands for you.

It works fine in general, but due to how Capacitor looks for plugins, it made you install your capacitor plugins in the application level package.json file (as shown in the image below), which defeats one of the purposes of the monorepo, to have and maintain only 1 node_modules folder for all my apps and libraries.

Nx Extensions Capacitor documentation showing how to add capacitor plugins

For my personal project it was an inconvenience, because if I wanted to install packages I needed to jump between the 2 folders depending on what I needed to install.

For my work monorepo, it was more of a pain, imagine 200+ developers working on a monorepo, and every time a package changed in our app (for our 5 developer team) we had a lot of messages in the general chat asking why linter was failing for our app.

And we had to go and tell them, “oh, just cd into the app folder and run npm ci” 😅.

We started looking for a solution to this, and found something that works, we still have the 2 package.json files, but now we maintain only one node_modules/ folder, and I’m happy to say, that since I implemented that change, there hasn’t been a single incident where devs from other teams have failed linters or processes because of us 🙌🏽.

The implementation was actually simpler than I expected, first, I removed all the version numbers from my application’s package.json, so in this example, my app is called Smart Checklists, so in my monorepo it’s path is apps/smart-checklists.

So in my apps/smart-checklists/package.json I replaced all the version numbers for *.

{
  "name": "smart-checklists",
  "dependencies": {
    "@capacitor/filesystem": "*",
    "@capacitor/camera": "*"
  },
  "devDependencies": {
    "@capacitor/cli": "*"
  }
}

Then, I removed both the apps/smart-checklists/package-lock.json file and the apps/smart-checklists/node_modules folder.

Then, I installed my 2 plugins in the root package.json file, you do this by opening the terminal in the root of the project and running npm install for the plugins, so in my case it was:

npm install @capacitor/filesystem @capacitor/camera

For my app it worked out of the box, I was able to generate both Android and iOS platforms and versions without issues, for my work application I had to do an extra step.

I needed to go into my Podfile: apps/smart-checklists/ios/App/Podfile

And change the first line from:

require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'

to

require_relative '../../../../node_modules/@capacitor/ios/scripts/pods_helpers'

This is because it was generated with an older version of capacitor, so it wasn’t updated, and it was trying to find the helpers inside the app level node_modules which no longer existed.

And that was it, a lot of research, a bit of work, and a happy ending 🙌🏽