Angular: problem using code from one library in another library

114 views Asked by At

Suppose an Angular workspace with two libraries inside (e.g. projects/libA and projects/libB). In libA I'm importing code from libB

import { ComponentX } from '@myscope/libB';

ComponentX is exported in index.ts located in projects/libB/src. In tsconfig.json (in root of worspace I have)

{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "ES2022",
    "module": "es2020",
    "skipLibCheck": true,
    "allowSyntheticDefaultImports": true,
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "@myscope/libB": [
        "projects/libB/src"
      ]
    },
    "useDefineForClassFields": false
  }
}

In projects/libA/package.json I have

{
  "name": "@myscope/libA",
  "version": "0.0.1",
  "peerDependencies": {
    "@angular/common": "^15.0.0",
    "@angular/core": "^15.0.0",
    "@myscope/libB": "*"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  },
  "exports": {
    ".": {
      "sass": "./src/styles.scss"
    }
  }
}

Still while calling ng build libA I get many errors like

The file <path_to_worspace>/projects/libB/src/.../component-x.ts is outside of the configured 'rootDir'.

How can I fix that?

1

There are 1 answers

2
VonC On BEST ANSWER

I would assume you have the following project structure:

Workspace Root
│
├── tsconfig.json
│
├── projects
   │
   ├── libA
   │   ├── package.json
   │   └── ...
   │
   └── libB
       ├── src
       │   ├── index.ts (exports ComponentX)
       │   └── component-x.ts (contains ComponentX)
       └── ...

In that case, the Angular build system is checking the files being imported against the rootDir option in the tsconfig.json file. And since libB is outside of the rootDir of libA,... you get an error.

You can start with adjusting the paths option in the tsconfig.json to map @myscope/libB to projects/libB/src/public-api. Make sure libB has a public-api.ts file in its src directory that exports ComponentX.

// tsconfig.json
"paths": {
  "@myscope/libB": [
    "projects/libB/src/public-api"
  ]
}

And:

// projects/libB/src/public-api.ts
export * from './component-x/component-x.component';

Then make sure libB is built before libA using a script or, if using a compatible TypeScript version, set up project references in the tsconfig.json.

ng build libB && ng build libA

With:

// tsconfig.json
"references": [
  { "path": "./projects/libB" },
  { "path": "./projects/libA" }
]

If necessary, adjust the rootDir compiler option to encompass both libA and libB.

// tsconfig.json
"compilerOptions": {
  "rootDir": "./projects",
  ...
}

You would have:

Workspace Root
│
├── tsconfig.json (adjusted paths, references, and optionally rootDir)
│
├── projects
   │
   ├── libA
   │   ├── package.json
   │   └── ... (rest of libA files)
   │
   └── libB
       ├── src
       │   ├── public-api.ts (exports ComponentX)
       │   ├── component-x
       │   │   └── component-x.component.ts (contains ComponentX)
       │   └── ... (rest of libB files)
       └── ...

In tsconfig.json:

{
  "compilerOptions": {
    "rootDir": "./projects",  // Optional adjustment
    ...
  },
  "paths": {
    "@myscope/libB": ["projects/libB/src/public-api"]
  },
  "references": [  // If using project references
    { "path": "./projects/libB" },
    { "path": "./projects/libA" }
  ]
}

In projects/libB/src/public-api.ts:

export * from './component-x/component-x.component';

Check then if libA is able to import ComponentX from libB without encountering the 'rootDir' error, provided the build order is correctly managed (i.e., libB built before libA).


Do I understand correctly that is I add libB to peerDependencies of libA/package.json, libB will not get bundled to libA?

Yes, that is correct.

When libB is listed in the peerDependencies of libA, it signals that libB is a requirement for libA, but libB will not be bundled with libA.
Instead, it is expected that whoever is using libA will also install and provide libB. That is a common pattern for avoiding duplicate bundling of libraries, especially in scenarios where multiple libraries are expected to interact with a shared instance of another library.