Angular 8: A better way to build monorepo app with libraries

Issue

I have several apps in my Angular monorepo project. Also there are about 5 libraries I’ve written to use across the apps.

What I want to know is how to better build/architect these libraries.

The ins are follows:

  1. Libraries are for internal usage only (meaning no publishing or using in other apps but those in projects folder)
  2. Libraries have different dependencies like lodash and RxJs
  3. One library can import another inside itself

What I’ve done so far:

  1. Specified the umdModuleIds in each library’s ng-package.json.
  2. Specified peerDependencies on external libraries like lodash and RxJs
  3. Set up my app build which has prebuild with about 5 commands ng build lib-name combined via “&&”
  4. I import Lodash in next way import { cloneDeep } from 'lodash'

Now I see that my main.js chunk is much bigger than it was before extracting some services/components/functions into external libraries. Now main.js‘s size on prod build is 2.1 Mb which in my opinion is too big.

Also, I’m not sure whether it’s worth making 3 different builds of each library (UMD, FESM2015, FESM5).

I import libraries from dist folder as it recommended in docs following next form import { LibService } from 'lib'.

Solution

Nrwl tools, developed by Angular core contributors, specializes in enterprise architectures, including mono repositories.

The Nrwl nx-examples is a great resource to get started.

I started by using nx to build a new project. In the end, my project structure ended up as follows:

platform-directory/
  |
  ---apps/
  |  |
  |  ---app1/
  |  |
  |  ---app2/
  |
  ---library1/
  |  |
  |  ---src/
  |
  ---library2/
  |  |
  |  ---src/
  |
  ---angular.json
  |
  ---package.json
  |
  ---README.md
  |
  ---tsconfig.json

tsconfig.json

The top level tsconfig.json should contain the bulk of the global configuration for the apps and libraries as well as paths shortcuts if desired.

Path shortcuts may be configured as follows:

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "./",
    "declaration": false,
    "downlevelIteration": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "module": "esnext",
    "moduleResolution": "node",
    "sourceMap": true,
    "target": "es6",
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "@app1*": [
        "./apps/app1/src/app*"
      ],
      "@lib1/package1": [
        "./lib1/src/package1/public_api.ts"
      ],
      "@lib1/package2": [
        "./lib1/src/package2/public_api.ts"
      ],
     ...
}

Library Imports

In the applications, library code may be imported directly from the library sources, such as:

import { MyLibraryComponent } from '@lib1/package1'

Since you are not publishing the libraries, there is no need to build them. When you run the Angular compiler on your application code, the library code will be automatically included and optimized as needed.

IMPORTANT: Within each library, do not import files using the path shortcuts, since this causes hard-to-debug circular dependencies. For example, within lib2 it is okay to use:

import { MyLibraryComponent } from '@lib1/package1'

However, if this import were used within lib1, it would create a circular dependency.

As a side note, each app will have a tsconfig.app.json and tsconfig.spec.json such as the following:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc/apps/app1"
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

Answered By – Christopher Peisert

Answer Checked By – Gilberto Lyons (AngularFixing Admin)

Leave a Reply

Your email address will not be published.