Angular 17 library and webapp in monorepo: ng build works but ng serve fails with a dependency resolution problem

777 Views Asked by At

Problem

I have an Angular 17 web app in a monorepo that builds fine with ng build but fails on running ng serve with many “Failed to resolve dependency: , present in ‘optimizeDeps.include’" errors.

Background

I have set up a new monorepo from scratch using pnpm. The repo has three projects; one Angular 17 library, one Angular 17 web app and one Ionic mobile app. The web app and the mobile app both depend on the library. The library initially contained a simple text-returning function and the web and mobile apps were just skeletons that called the library and then presented the received string in their GUIs. I tested the apps using ng build, ng serve, ionic build and ionic serve and everything worked; both the web app and the mobile app built and ran in the web browser without problems.

Then I started to migrate code into the project from an older Angular 13 project. This code depends on several other packages, among them lodash, Capacitor, Firebase and Angular Material. After having upgraded the library code to the latest TypeScript version, I ran ng build without problems. The external dependencies of the library are all listed under peerDependencies and devDependencies rather than under dependencies as required by the Angular library docs. After building, the library’s node_modules folder contains links of all the packages to node_modules/.pnpm in the monorepo root folder. The monorepo dist folder contains a library subfolder with what looks like all necessary module and interface definition files for the library. I.e., the library seems to build properly with all its dependencies.

As the next step, I migrated the web app code in a similar way. The Material theme settings in theme.scss and variables.scss required updating to the latest syntax but finally I managed to get them through the build process without any errors. I added the library as well as all its peer dependencies to the project as normal dependencies with ‘pnpm add’. After building, the web app’s node_modules folder contains links of all the packages to node_modules/.pnpm in the monorepo root folder. The monorepo dist folder contains a web app subfolder with the folder ‘browser’ containing the transpiled application; a lot of js files, subfolders for assets and media, index.html and a CSS file. I am able to open the index.html file and run the web app using the Live Server of VSCode, which seems to indicate that the generated code is complete (with the exception that the Material styling doesn’t work for some reason…). If I instead try to run ng serve on the project I get these resolution errors:

Failed to resolve dependency: @angular/cdk/coercion, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/cdk/platform, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/cdk/text-field, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/fire, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/fire/app, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/fire/auth, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/button, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/card, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/checkbox, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/core, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/dialog, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/divider, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/form-field, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/icon, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/input, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/select, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/tabs, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/toolbar, present in 'optimizeDeps.include'
Failed to resolve dependency: @angular/material/tooltip, present in 'optimizeDeps.include'
Failed to resolve dependency: firebase/analytics, present in 'optimizeDeps.include'
Failed to resolve dependency: firebase/app, present in 'optimizeDeps.include'
Failed to resolve dependency: firebase/auth, present in 'optimizeDeps.include'
Failed to resolve dependency: firebase/firestore, present in 'optimizeDeps.include'
Failed to resolve dependency: firebase/storage, present in 'optimizeDeps.include'
Failed to resolve dependency: rxfire/auth, present in 'optimizeDeps.include'
Failed to resolve dependency: rxfire/firestore, present in 'optimizeDeps.include'
Failed to resolve dependency: rxfire/storage, present in 'optimizeDeps.include'

These errors refer only to dependencies within the Firebase and the Angular Material hierarchy although there are many other dependencies in the projects that don’t cause errors (e.g., lodash and Capacitor). Moreover, firebase and rxfire are not direct dependencies so I guess they must be transitive dependencies of @angular/fire. @angular/cdk, @angular/fire and @angular/material are all listed as dependencies in the web app’s package.json file so why are neither they nor their transitive dependencies found when I run ng serve? I get no error messages when I run ng build and the artefacts then generated seem to constitute a working web app, as tested with Live Server above. I have understood that Angular nowadays uses Vite and rollup for the development server but I don’t know if that would require any additional configurations? I would be grateful to anyone who can shed any light on what I should do to make this work.

P.S. In an act of desperation I tried to add @angular/cdk, @angular/fire, @angular/material, firebase and rxfire to the workspace root project of the monorepo (pnpm add -w). To my surprise this eliminated the dependency errors above (this is good I guess but why on earth did this work???) and resulted in the following log:

Application bundle generation complete. [0.435 seconds]
9:44:37 PM [vite] ✨ new dependencies optimized: firebase/app, @angular/fire, @angular/fire/app, firebase/analytics, @angular/fire/auth, rxfire/firestore, firebase/firestore, rxfire/auth, firebase/auth, rxfire/storage, firebase/storage
9:44:37 PM [vite] ✨ optimized dependencies changed. reloading
9:44:39 PM [vite] ✨ new dependencies optimized: @angular/material/dialog, @angular/material/tabs, @angular/material/icon, @angular/material/toolbar, @angular/material/button
9:44:39 PM [vite] ✨ optimized dependencies changed. reloading
9:44:41 PM [vite] ✨ new dependencies optimized: @angular/material/card, @angular/material/input, @angular/material/form-field
9:44:41 PM [vite] ✨ optimized dependencies changed. reloading

The web app now launches in Chrome but it doesn’t get past the initialization stage. In the console I get the following error: ERROR Error: NullInjectorError: No provider for FirebaseApps! As I wrote earlier, Firebase works if I serve the app through Live Server so the error must be somewhere in the way that Vite gathers the project dependencies before it launches Chrome. I am at a loss now on how to fix this so I would really appreciate some explanations, pointers and/or creative ideas on what to do next! Thanks!

P.P.S. I have also tried all the following but nothing has helped:

  • I have cleaned all .node_modules, .angular and dist folders and rebuilt using pnpm i and ng build, first on the library and then on the web app. This didn’t help.
  • I have edited angular.json and tried to switch from the new Angular ‘application’ builder to ‘browser’ and ‘browser-esbuild’ but this didn’t help either (which was not unexpected since it seems to be the ‘dev-server’ that fails during ‘serve’ rather than the builder during ‘build’).
  • I have tried to move the library’s peer dependencies into dependencies and listed them under allowedNonPeerDependencies in ng-packagr.json. This didn’t help either.
2

There are 2 best solutions below

0
DrTomato On

I finally got it to work (mostly) by downgrading to Angular 16 which uses Webpack instead of Vite. When everything else is in order I might try to go the official upgrade path from Angular 16 to Angular 17 and see if that resolves my issues. I still don't know the actual reasons why it didn't work with Angular 17.

0
liniguez On

I found a similar issue and successfully fixed it using public-hoist-pattern in the .npmrc file.

For your specific scenario, you need to add the following entries into your .npmrc file:

public-hoist-pattern[]=@angular/cdk*
public-hoist-pattern[]=@angular/fire*
public-hoist-pattern[]=@angular/material*
public-hoist-pattern[]=firebase*
public-hoist-pattern[]=rxfire*

This configuration should address the problem.