Typescript,  Node,  monorepo

Adding a local library to a monorepo

In this article we will be adding a shared local library to a monorepo.

Intro Some concepts that are going to be discussed i this article.

  • Repo is a storage location for software packages. Most popular repo cloud storage providers are of course Github and Bitbucket.
  • Branch is a version of a repository. In other words it is a smart copy of a repository.
  • JavaScript libraries facilitate code re-use by providing packaged code that can be used in multiple projects.
  • Monorepo is a repository that contains multiple projects or modules.
my-monorepo/
  ├── project1/
  │   ├── src/
  │   ├── package.json
  ├── project2/
  │   ├── src/
  │   ├── package.json
  ├── shared-component/
  │   ├── src/
  │   ├── package.json
  ├── package.json

In theory, using a monorepo makes it easier to manage dependencies, but in practice, adding a local shared library to a TypeScript monorepo turned out to be a minor nightmare.

In our case we already had a monorepo and a shared library that was stored in a different repo and we wanted to move it into the main monorepo.

The problem we were trying to solve We certainly follow the “don’t touch it if it works” principle, but only until it makes sense to do so. The biggest problem of having a shared component in a separate repository was that if a developer was working on a feature in his local branch and had to modify the shared library, then he needed to create a separate local branch in the shared library. The issue was that he could not publish it until all the branches were merged. So what developers ended up doing was to modify and publish the shared library before they were ready to commit and merge the feature branch. In many cases, the developer refactor their code and rollback the changes they made to the shared library, and then introduce new and breaking changes to the library.

The problem we encountered So we moved our shared library into a folder named shared-component. It all worked beautifully until we decided to re-map imports to lookup locations. In other words, to switch our imports from:

import { inspire } from '../../shared-component/src/shared-function';

to:

import { inspire } from '@shared-component/shared-function' ;

we also added it to project1’s tsconfig.json file:

{
   "compilerOptions": {
      "resolveJsonModule": true,
      "paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
         ...
         "@shared-component":[
               "../shared-component/src/index.ts"
         ]
      },
    },
    "include": [
        "src/**/*",
        ...
        "../shared-component/src/**/*",
    ],
    ...
}

and immediately received the following error:

Cannot find module or its corresponding type declarations.

Implementing some of the advices on stackoverflow did not yield any results.

After a couple of hours of search and a consultation with deep blue AI we were ready to give up. Just kidding. We found a post that explained the error.

Apparently, tsc compiler (transpiler) does not resolve/emit correctly the path aliases to the output JavaScript .js files. Translated to human language it means that transpiler has a bug that it is not going to fix any time soon.

The solution Since we did not want to write our own script that crawls through all the project files and remaps the imports, we decided to use a Javascript library that could do it for us.

Add the library to your project1:

npm i --save module-alias

Also add the following code to package.json in project1:

{
  "name": "project1",
  ...
  "_moduleAliases": {
    "@root": ".",
    "@shared-component": "../shared-component",
    ...
  },
  ...
}

And finally add require('module-alias/register') to the main file of the app:

require('module-alias/register');
...
require('ts-node').register({
  transpileOnly: true,
});
Adding a local library to a monorepo

Subscribe to The infinite monkey theorem

Get the latest posts delivered right to your inbox