Is there a better way to build an Angular monorepo app with libraries?
Asked Answered
W

2

6

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'.

Wilderness answered 23/7, 2019 at 19:25 Comment(1)
nx.dev may be what you are looking for.Ketchum
R
13

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"
  ]
}
Remedial answered 23/7, 2019 at 19:46 Comment(3)
Is there an easier way to improve the situation than refactoring existing projects?Wilderness
Marked it as a right solution due to its overall idea, although it's hardly assignable to my situation where I already have a set up project what would cause a refactoring which nobody wants :DWilderness
You can now do all of the above using the Angular CLI without Nrwl - workspace projects, multiple apps, multiple libs, tscongfig paths etc. I know Nrwl adds more than that, but it's also an additional build framework to manageOeuvre
C
0

Specifically regarding your concern with bundle size: I suspect Lodash is a major culprit there. Try using lodash-es instead, and importing from only packages you need. E.g.

import cloneDeep from 'lodash-es/cloneDeep';

That should reduce the amount of lodash in your bundle substantially, but it still won't be nearly as small as it could be for most use cases. I made a library specifically for this reason called micro-dash. For example it includes cloneDeep, which as the docs say adds 397 bytes to your bundle (roughly - it depends on multiple factors), whereas the lodash version adds 12,289 bytes.

Ultimately, though, to troubleshoot an oversized bundle you should see exactly how much each library is adding to it. That is the realm of source-map-explorer. Absolutely run that on your final prod bundles, and address the worst offenders first!

Conyers answered 16/9, 2020 at 1:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.