angular-architects / module-federation-plugin

MIT License
737 stars 203 forks source link

ngDevMode is not defined #458

Open DavidGoethCplace opened 10 months ago

DavidGoethCplace commented 10 months ago

Hi,

When using the ModuleFederationPlugin within a custom webpack 5 configuration that is set to 'development' mode, I face the problem that angular produces a 'ReferenceError: ngDevMode is not defined' error.

I have a simple solution by defining the ngDevMode manually:

config.plugins.push(
new webpack.DefinePlugin({
      ngDevMode: config.mode === 'development'
}));

But I think that should be handled by ModuleFederationPlugin directly.

Greetings David

raulmelo commented 9 months ago

I have the same problem, I can't run MFE because of ngDevMode

error ReferenceError: ngDevMode is not defined
    at 168 (platform-browser.mjs:172:50)
    at __webpack_require__ (bootstrap:19:1)
    at 8836 (home-widgets.component.html:30:48)
    at __webpack_require__ (bootstrap:19:1)
    at container-entry:3:1
    at 460.9e51155742b22477.js:1:165936
    at Generator.next (<anonymous>)
    at s (460.9e51155742b22477.js:1:546695)
    at xe (460.9e51155742b22477.js:1:546913)
    at O.invoke (polyfills.34b46c4a98362076.js:1:6572)

Package.json

 "dependencies": {
    "@angular-architects/module-federation": "17.0.1",
    "@angular-architects/module-federation-tools": "17.0.1",
    "@angular/animations": "^17.0.5",
    "@angular/cdk": "^17.0.2",
    "@angular/common": "^17.0.5",
    "@angular/compiler": "^17.0.5",
    "@angular/core": "^17.0.5",
    "@angular/elements": "^17.0.5",
    "@angular/forms": "^17.0.5",
    "@angular/material": "^17.0.2",
    "@angular/platform-browser": "^17.0.5",
    "@angular/platform-browser-dynamic": "^17.0.5",
    "@angular/router": "^17.0.5",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.0.5",
    "@angular-eslint/builder": "17.1.1",
    "@angular-eslint/eslint-plugin": "^17.1.1",
    "@angular-eslint/eslint-plugin-template": "17.1.1",
    "@angular-eslint/schematics": "17.1.1",
    "@angular-eslint/template-parser": "17.1.1",
    "@angular/cli": "~17.0.5",
    "@angular/compiler-cli": "^17.0.5",
    "@types/file-saver": "^2.0.7",
    "@types/jasmine": "~4.3.0",
    "@typescript-eslint/eslint-plugin": "6.13.1",
    "@typescript-eslint/parser": "6.13.1",
    "eslint": "^8.54.0",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "ngx-build-plus": "^17.0.0",
    "typescript": "~5.2.2"
  }

webpack.config

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");

const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, '../../tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "terms",
    publicPath: "auto",
  },
  optimization: {
    runtimeChunk: false,
  },
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    },
  },
  experiments: {
    outputModule: true,
  },
  plugins: [
    new ModuleFederationPlugin({
      library: { type: "module" },
      name: "terms",
      filename: "remoteEntry.js",
      exposes: {
        "./web-components": "./projects/terms/src/bootstrap.ts",
      },
      shared: share({
        "@angular/core": { requiredVersion: "auto" },
        "@angular/common": { requiredVersion: "auto" },
        "@angular/router": { requiredVersion: "auto" },
        rxjs: { requiredVersion: "auto" },
        ...sharedMappings.getDescriptors(),
      }),
    }),
    sharedMappings.getPlugin(),
  ],
};
raulmelo commented 9 months ago

solution: webpack.config.js

add code in plugins

 new webpack.DefinePlugin({
      ngDevMode: "undefined",
 }),

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const webpack = require("webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, '../../tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
....OTHERS,
  plugins: [

   // DISABLE ngDevMode as it is not needed in a remoteEntry
    new webpack.DefinePlugin({
      ngDevMode: "undefined",
    }),
    // END DISABLE ngDevMode as it is not needed in a remoteEntry

    new ModuleFederationPlugin({
      library: { type: "module" },
      name: "NAME",
      filename: "remoteEntry.js",
      exposes: {
        "./web-components": "./projects/NAME/src/bootstrap.ts",
      },
      ...
    }),
  ],
};
Teifun2 commented 8 months ago

Im running into the same issue.

For me it is caused by @angular/forms

providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CheckboxComponent),
      multi: true,
    },
],

the NG_VALUE_ACCESSOR is causing this issue: https://github.com/angular/angular/blob/main/packages/forms/src/directives/control_value_accessor.ts#L212

Teifun2 commented 8 months ago

Probably this is due to the missing initalization. https://github.com/angular/angular/blob/main/packages/core/src/util/ng_dev_mode.ts#L119

However this does not explain why the NgDevMode is missing with ModuleFederation

dbeachy1 commented 7 months ago

@raulmelo Thanks for the workaround info. When I do that in my webpack.config.js, the compilation blows up with:

Generating browser application bundles (phase: setup).../Users/dobeachy/ise/frontend/node_modules/webpack/lib/DefinePlugin.js:341
        compiler.hooks.compilation.tap(
                                   ^

TypeError: Cannot read properties of undefined (reading 'tap')
    at DefinePlugin.apply (/Users/dobeachy/ise/frontend/node_modules/webpack/lib/DefinePlugin.js:341:30)
    at exports.createResolver (/Users/dobeachy/ise/frontend/node_modules/enhanced-resolve/lib/ResolverFactory.js:695:11)
    at ResolverFactory._create (/Users/dobeachy/ise/frontend/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/ResolverFactory.js:131:12)
    at ResolverFactory.get (/Users/dobeachy/ise/frontend/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/ResolverFactory.js:112:28)
    at exports.resolveMatchedConfigs (/Users/dobeachy/ise/frontend/node_modules/webpack/lib/sharing/resolveMatchedConfigs.js:46:47)
    at /Users/dobeachy/ise/frontend/node_modules/webpack/lib/sharing/ConsumeSharedPlugin.js:126:21
    at Hook.eval [as call] (eval at create (/Users/dobeachy/ise/frontend/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:84:1)
    at Hook.CALL_DELEGATE [as _call] (/Users/dobeachy/ise/frontend/node_modules/tapable/lib/Hook.js:14:14)
    at Compiler.newCompilation (/Users/dobeachy/ise/frontend/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/Compiler.js:1125:30)
    at /Users/dobeachy/ise/frontend/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/Compiler.js:1170:29

My webpack.config.js has:

, plugins: [
      new webpack.DefinePlugin({
        // DISABLE ngDevMode as it is not needed in a remoteEntry
        ngDevMode: 'undefined'
      }),

I'm using "@angular-architects/module-federation": "^17.0.8",, but I also tried it with 17.0.1 and I'm getting the same error. Any ideas, anyone?

raulmelo commented 7 months ago

@dbeachy1 share your webpack.config.js please.

my code shell MFE with angular

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const mf = require('@angular-architects/module-federation/webpack');
const webpack = require('webpack'); 
const path = require('path');
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(path.join(__dirname, './tsconfig.json'), [
  /* mapped paths to share */
]);

module.exports = {
  output: {
    uniqueName: 'modulo-core',
    publicPath: 'auto',
  },
  optimization: {
    runtimeChunk: false,
  },
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    },
  },
  experiments: {
    outputModule: true,
  },
  plugins: [
    // DISABLE ngDevMode as it is not needed in a remoteEntry
    new webpack.DefinePlugin({
      ngDevMode: 'undefined',
    }),
    // END DISABLE ngDevMode as it is not needed in a remoteEntry
    new ModuleFederationPlugin({
      library: { type: 'module' },
      shared: share({
        '@angular/core': { requiredVersion: 'auto' },
        '@angular/common': { requiredVersion: 'auto' },
        '@angular/router': { requiredVersion: 'auto' },
        '@angular/common/http': { requiredVersion: 'auto' },
        rxjs: { requiredVersion: 'auto' },
        ...sharedMappings.getDescriptors(),
      }),
    }),
    sharedMappings.getPlugin(),
  ],
};

dependencies:

"@angular-architects/module-federation": "17.0.1",
"@angular-architects/module-federation-tools": "17.0.1",
"@angular/animations": "^17.1.1",
"@angular/cdk": "^17.1.0",
"@angular/common": "^17.1.1",
"@angular/compiler": "^17.1.1",
"@angular/core": "^17.1.1",
"@angular/elements": "^17.1.1",
"@angular/forms": "^17.1.1",
"@angular/material": "^17.1.0",
"@angular/platform-browser": "^17.1.1",
"@angular/platform-browser-dynamic": "^17.1.1",
"ngx-build-plus": "^17.0.0",
"typescript": "~5.3.3"
dbeachy1 commented 7 months ago

@raulmelo Thanks for info. Unfortunately I can't share my entire webpack.config.js because the code is private (the place I work), but the relevant part of my webpack.config.js is what I posted above. Here are the relevant dependencies, though:

    "@angular-architects/module-federation": "^17.0.8",
    "@angular/animations": "17.1.3",
    "@angular/cdk": "17.1.2",
    "@angular/common": "17.1.3",
    "@angular/compiler": "17.1.3",
    "@angular/core": "17.1.3",
    "@angular/forms": "17.1.3",
    "@angular/localize": "17.1.3",
    "@angular/platform-browser": "17.1.3",
    "@angular/platform-browser-dynamic": "17.1.3",
    "@angular/router": "17.1.3",

So I'm using a slightly newer version of Angular (17.1.3 vs. your 17.1.1), and a slightly newer module-federation (17.0.8 vs. your 17.0.1). I also tried setting a JavaScript global variable named ngDevMode in the MFE's index.js, but that doesn't fix the runtime bug, either. I'm debating whether to just switch to an older version of Angular 17 from before this breaking change in January...

EDIT: here's the pull request that broke this for MFEs: https://github.com/angular/angular/pull/53747

dbeachy1 commented 7 months ago

@raulmelo I have it working now, thanks for posting your code shell MFE! It turns out I was putting the new webpack.DefinePlugin block in the wrong plugins section in my webpack.config.js (we have some resolve plugins as well). Thanks again!

jsenzig-clgx commented 7 months ago

Quick note here. Updating webpack.config.js as has been suggested above to force an "undefined" value onto ngDevMode is (at least in my experience) being treated as a truthy value.

This means that I still see "Angular is running in development mode" in the browser console as I enter the MFE. It is a workaround to avoid the original issue (ngDevMode is undefined) ... but by forcing development mode to be ON.

Finally, my attempts to force a false value onto ngDevMode at this same spot do not get rid of the original undefined error. Only forcing a truthy value clears the error, counterintuitively.

For me, this issue didn't appear until we tried to upgrade to Angular 17; we didn't have this issue at Angular 16. Looks to me like a regression happened along the way that strongly suggests a fix is needed somewhere in the libraries.

scottfwalter commented 6 months ago

For me it seems to be an issue when swapping out MFE child apps. If the first child app was built with dev mode then it's not a problem. However, if first load a production mode app and then switch to a dev mode app, I see the issue.

Before switching from the production mode child to the dev mode child I enter this into the console: window.ngDevMode = {} then I don't see the problem.

It seems like Angular is not properly resetting dev mode when switching out child apps.

gsgonzalez88 commented 3 months ago

thanks @raulmelo

// DISABLE ngDevMode as it is not needed in a remoteEntry
new webpack.DefinePlugin({
  ngDevMode: 'undefined',
}),
// END DISABLE ngDevMode as it is not needed in a remoteEntry

this solved my problem!