module-federation / core

Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications
https://module-federation.io/
MIT License
1.39k stars 206 forks source link

Nx angular dynamic mfe got NG0203 error #2797

Closed mehrabix closed 1 month ago

mehrabix commented 1 month ago

Describe the bug

I had no issuse since i used nx mfe with angular 15, but i tried to migrate to angular 18 and got this error when navigating from shell (host) app to another mfe Error: NG0203: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext. Find more at https://angular.dev/errors/NG0203 i also saw this topic https://github.com/nrwl/nx/issues/19121 and then i tried it on my two envirements which is a windows system and debian linux, and in both cases got the same error.

Reproduction

clone this repo https://github.com/mehrabix/nx-angular-mfe

do npm i

run this command: npx nx serve shell --devRemotes="first-microfront,second-microfront"

Used Package Manager

npm

System Info

System:
    OS: Linux 6.1 Debian GNU/Linux 12 (bookworm) 12 (bookworm)
    CPU: (4) x64 Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
    Memory: 2.24 GB / 7.53 GB
    Container: Yes
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 22.5.1 - /home/linuxbrew/.linuxbrew/bin/node
    npm: 10.8.2 - /home/linuxbrew/.linuxbrew/bin/npm
    pnpm: 9.5.0 - /home/linuxbrew/.linuxbrew/bin/pnpm
  Browsers:
    Chrome: 126.0.6478.114

Validations

mehrabix commented 1 month ago

Hi,

I got some clues. Clone this repository (it's mentioned in the Angular MFE tutorial: https://nx.dev/recipes/angular/dynamic-module-federation-with-angular):

https://github.com/Coly010/nx-ng-dyn-fed

Run: npx nx serve employee --devRemotes="dashboard,todo,login" navigate between remote links The Angular version is 18.0.4, and it works perfectly. Then, run this in order to migrate to Angular 18.1.2. It will migrate successfully: nx migrate latest

After that, run: npx nx serve employee --devRemotes="dashboard,todo,login" again. navigate between remote links It will not work anymore, and you will get the NG0203 error.

Coly010 commented 1 month ago

@mehrabix

Can you open this issue on the Nx Repo? It’s not directly related to this package.

mehrabix commented 1 month ago

Hi @Coly010 I don't think it's related to Nx. as i mentioned it's working on Angular 18.0.4 and not 18.1.2 Anyway i did it. Thanks https://github.com/nrwl/nx/issues/27162

bh3605 commented 1 month ago

@Coly010 This bug most certainly is not related to Nx. I'm doing the same thing with an Angular 17 remote and receiving the same error. In my case, however, my host is a single spa MFE using systemjs externals, @angular-architects/module-federation to load this remote, and I'm trying to use this example to point the angular dependencies to the systemjs map. Now, before I tried figuring out this remote plugin stuff, I noticed the remoteEntry file can't even figure out how to share the angular dependencies so I've been trying to reverse engineer the cause of this and haven't found anything yet. This enhanced mod fed plugin is missing something the webpack version isn't.

extra-webpackconfig.js

const modfedOptions = {
    library: { type: "module" },
    filename: "remoteEntry.js",
    name: 'CarrierPigeonApp',
    exposes: {
        './Component': './src/app/app.component.ts', // this is a standalone component
    },
    shared: //{ ...deps },
    share({
        // '@angular/core': {
        //     singleton: true,
        //     strictVersion: true,
        //     requiredVersion: 'auto',
        //   }
        ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    }),
    // runtimePlugins: [require.resolve('./npm-runtime-global-plugin')],
    dts: false,
    disableManifest: true
};

module.exports = (config, options) => {
//   const singleSpaWebpackConfig = singleSpaAngularWebpack(config, options);

    config.experiments = {
        outputModule: true
    };
    config.output = {
      ...config.output,
      uniqueName: "CarrierPigeonApp",
      // depending on what imports the remote entry determines if this succeeds or not
      publicPath: "auto",
    };
    config.optimization = {
        ...config.optimization,
        runtimeChunk: false
    };
    config.plugins = [
        ...config.plugins,
        new ModuleFederationPlugin(modfedOptions)
    ];

  return config;
};

angular.json

"build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "outputPath": "dist/carrier-pigeon-app",
            "index": "src/index.html",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "scripts": [],
            "main": "src/main.ts",
            "customWebpackConfig": {
              "path": "extra-webpack.config.js",
              "libraryName": "CarrierPigeonApp",
              "libraryTarget": "umd"
            }
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "none"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true,
              "outputHashing": "none"
            }
          },
          "defaultConfiguration": "production"
     }

main.ts

import('./bootstrap')
    .catch(err => console.error(err));

bootstrap.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

package.json dependencies image

The shared resource map compiled using the enhanced mod fed plugin image

The shared resource map compiled using webpack's mod fed plugin image

As you can see, swapping out mod fed plugins leads to different shared mappings which is probably a separate bug altogether, but should give a hint that this plugin has a problem with Angular projects.

To be clear though, I receive the same error with both the enhanced mod fed plugin and webpack's mod fed plugin. My hope in figuring out the runtime plugin will lead to my remote to being able to access the same service instances my single spa mfe host uses.

bh3605 commented 1 month ago

Figured out my problem was using angular-architects' shareAll function. For some reason I needed to specify my shared dependencies the manual way and everything just worked afterwards

ScriptedAlchemy commented 1 month ago

Angular Architects might be incorrect for V2