Sign inGet started

Using Lerna to manage multiple Ember apps

Imagine your team maintaining multiple apps that are dependent on some shared components. You might think that creating addons for those components would be the way to go—and it is. But depending on how you modularize your projects, this can lead to creating more packages (not just addons) and it can become messy and difficult to maintain.

As a few example, here at Cenchat, we have an addon for our UI components and a package for our eslint rules. We saw the pain of updating those packages in a separate branch and doing regression tests in each of our apps before merging. It wasn’t scalable since it was constantly a repetitive process of updating our package.json and redownloading the package to be tested.

We sought for a monorepo solution in which it came down to using in-repo addons. However, the problem with that it’s built to be used in a single app only. There’s a actually a workaround to make it sharable across apps but you’ll have to not use the Ember CLI commands to generate in-repo addon files. This is something we didn’t want to do in the long run. Thus, we went to Lerna and it’s where we found the less friction to build our apps.

Setup

Let’s see how a Lerna monorepo can look like at a high level view:

+-- packages
|  +-- foobar-eslint-config (type: package, name: @foobar/eslint-config)
|  +-- foobar-addon-1 (type: addon, name: @foobar/addon-1)
|  +-- foobar-addon-2 (type: addon, name: @foobar/addon-2)
|  +-- foobar-app-1 (type: app, name: foobar-app-1)
|  +-- foobar-app-2 (type: app, name: foobar-app-2)
+-- lerna.json
+-- package-lock.json
+-- package.json

As you can see above, our directory is a mixture of Ember apps, addons, and normal NPM packages. Creating the packages is just how you’d normally do it.

  • npm init for normal NPM packages
  • ember addon <addon_name> for Ember addons
  • ember new <app_name> for Ember apps

We also namespaced non-apps for organizational purposes. This is completely optional, but we’ll make use of it for the rest of the article.

Caveats

Okay, so everything seems normal with our setup above, right? Down the line however, you’ll encounter some caveats. Let’s discuss each one below with the possible workarounds for it.

1. Nested git version control in our monorepo

One additional thing you’ll need to do for Ember apps and addons is to delete the .git folder inside of it. This is automatically generated by Ember CLI and is something we need to remove in order to prevent having nested git version control in our monorepo.

2. Installing Ember addons

Let’s say that our foobar-app-1 already depends on our local package @foobar/eslint-config. Our devDependencies should now look like this:

{
  "devDependencies": {
    "@foobar/eslint-config": "^0.0.1",
    "@foobar/addon-1": "^0.0.1",
    "ember-cli": "~3.10.0"
  }
}

Now if we install a remote Ember addon such as ember-concurrency, we’ll get the following error:

npm ERR! code E404
npm ERR! 404 Not Found: @foobar/eslint-config@^0.0.1

This is because because under the hood, ember install ember-concurrency will run npm install at some point. Since npm install is incompatible with local packages managed by Lerna (because those packages doesn’t exist in the NPM registry), we get the error above.

What you should instead do is to run lerna add then follow it up with ember generate:

lerna add ember-concurrency --dev
ember generate ember-concurrency

If the Ember addon doesn’t have any default blueprint for it, ember generate <addon_name> would cause an Unknown blueprint error. But this is something you can safely ignore.

Alternatively, you can temporarily remove those local packages in your package.json dependencies and then run ember install ember-concurrency. Once it finishes, you copy back the removed local packages and run lerna bootstrap.

Tips

These are optional but something you just might want to do either way.

1. Setup Travis tests

Since we’re now in an awesome monorepo, we’ll want to take advantage of running tests for all packages in one go. Below is a sample Travis config file which you should place at the root of your repo.

---
language: node_js
node_js:
  - "lts/*"

sudo: false
dist: trusty

addons:
  chrome: stable

cache:
  directories:
    - $HOME/.npm

env:
  global:
    # See https://git.io/vdao3 for details.
    - JOBS=1

before_install:
  - npm config set spin false

script:
  - ./node_modules/.bin/lerna bootstrap
  - ./node_modules/.bin/lerna run lint:hbs
  - ./node_modules/.bin/lerna run lint:js
  - ./node_modules/.bin/lerna run test

The config above is similar to the one generated in an Ember app or addon. The only thing we changed is the script section in which we’re now using Lerna commands.

Next, we’ll have to configure our ember test command to automatically pick up an available port in all our Ember apps and addons. If we leave it at the default of 7357, our Travis tests may sometimes get the error of port number is already being used. Here’s how it should look like in your package.json:

{
  "scripts": {
    "test": "ember test --test-port 0"
  }
}

2. Marking packages as private

Just for safety, you might want to set "private": true in each of your package’s package.json file. This prevents an accidental publishing in the NPM registry.

Wrapping up

There’s definitely still some frictions when using Lerna to manage multiple Ember apps. However, those things are something we can live with in exchange of all the goodies we can get from going into a monorepo. Hopefully, the compatbility of Ember CLI with Lerna will improve.

Lastly, there might be better ways to do things so please do send us a message if you know of any.