angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.74k stars 11.98k forks source link

Can't retrieve exported components from Ι΅mod when using production mode #21952

Closed nohanna closed 2 years ago

nohanna commented 2 years ago

🐞 Bug report

Command (mark with an x)

Is this a regression?

This behavior also happens in Angular 11 (in production mode) and happens in Angular 12 because of default production mode. ### Description **Note**: I already posted on [stackoverflow](https://stackoverflow.com/questions/69552125/cant-federate-components-in-production-mode), but got no answer. Since it is really blocking and I'm not sure if it is a bug, I'm opening a bug report here because it is linked to the AOT compilation. I'm using Angular with [Webpack 5 Module Federation](https://webpack.js.org/concepts/module-federation/) for micro-frontends. I have 2 apps (let's call them app1 and app2) and my main app, app1, is federating components of app2. In app2, my exposed components are exported by my main module, so when I try to federate some component, I can get it from the export section of my module. Everything works fine in dev mode, I can get my exposed module, retrieve my exported components and then use the component factory resolver to create it where I want in app1. But when I build in production (I used ng serve --prod to reproduce the prod environment and test it) my exported components are gone but not the imports and providers. This is the module I get: ``` MyApp2Module: class e Ι΅fac: Ζ’ (t) Ι΅inj: imports: Array(1) 0: (12) [Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’, Ζ’] length: 1 [[Prototype]]: Array(0) providers: [Ζ’] [[Prototype]]: Object Ι΅mod: bootstrap: [] declarations: [] exports: [] id: null imports: [] schemas: null transitiveCompileScopes: null type: class e [[Prototype]]: Object ``` As you can see, in Ι΅mod, I have no exported components and no declarations and I expected to see my components here. I guess it is due to AOT compilation and the optimization since it is working in dev mode. Is AOT removing everything in Ι΅mod on purpose or it is because my components are tree-shaked since they are not directly used by my app2? I tried to use [sideEffects](https://webpack.js.org/guides/tree-shaking/) but it is not working. Is there another way to tell the compiler to not remove those components? ## πŸ”¬ Minimal Reproduction I created a reproduction [here](https://github.com/nohanna/angular-micro-frontends). Just use: - `yarn install` - `yarn build:shared` - `yarn start:first-app` (it is the "host" app, and will start on http://localhost:4200) - `yarn start:second-app` (it is the "hosted" app, and will start on http://localhost:4201) If you go to http://localhost:4200 and click in the navbar on "second app" you will see that the sidebar added 3 links which are loaded from _second-app_. Now if you do the same in production mode: - `yarn start:first-app --prod` (it is the "host" app, and will start on http://localhost:4200) - `yarn start:second-app --prod` (it is the "hosted" app, and will start on http://localhost:4201) Open the devtools, if you go to http://localhost:4200 and click in the navbar on "second app" you will see the object and the error.

πŸ”₯ Exception or Error




ERROR Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading 'Ι΅cmp')
TypeError: Cannot read properties of undefined (reading 'Ι΅cmp')
    at ye (main.js:1)
    at Qg.resolveComponentFactory (main.js:1)
    at main.js:1
    at c.invoke (polyfills.js:1)
    at Object.onInvoke (main.js:1)
    at c.invoke (polyfills.js:1)
    at u.run (polyfills.js:1)
    at polyfills.js:1
    at c.invokeTask (polyfills.js:1)
    at Object.onInvokeTask (main.js:1)
    at k (polyfills.js:1)
    at polyfills.js:1
    at c.invokeTask (polyfills.js:1)
    at Object.onInvokeTask (main.js:1)
    at c.invokeTask (polyfills.js:1)
    at u.runTask (polyfills.js:1)
    at _ (polyfills.js:1)

🌍 Your Environment




Angular CLI: 11.2.14
Node: 16.9.1
OS: linux x64

Angular: 11.2.14
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router
Ivy Workspace: Yes

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1102.14
@angular-devkit/build-angular   0.1102.14
@angular-devkit/core            11.2.14
@angular-devkit/schematics      11.2.14
@schematics/angular             11.2.14
@schematics/update              0.1102.14
ng-packagr                      11.2.4
rxjs                            6.6.7
typescript                      4.1.6
clydin commented 2 years ago

Unfortunately for this situation, customized Webpack configurations and/or usage of Webpack specific features are not officially supported by the Angular CLI. Customization of the Webpack configuration is also only possible by using third-party builders which are not maintained nor directly supported by the Angular Team. Within the Angular CLI, Webpack is considered an internal implementation detail which may or may not be used to fully build the application in all modes, now or in the future.

However, it may be useful to open an issue with the maintainers of either the https://github.com/just-jeb/angular-builders or https://github.com/manfredsteyer/ngx-build-plus projects as they may have more experience in this area.

nohanna commented 2 years ago

Thanks for the answer, I will open an issue with them.

I'm aware that this is a specific case, but do you know if there any way to explicitly tell terser to not remove classes or is it totally related to Webpack? Because I know that Angular is using "build-optimizer" so that's why I tried to use sideEffects since it is directly related to Webpack. I guess something like build-optimizer does with /*@__PURE__*/.

alan-agius4 commented 2 years ago

I think what might work is to assign a static field on the NgModule class that declares the "unused" components. While t NgModule.declarations field is removed by the AOT compiler, the custom static field isn't. This would causes the components to be flagged as used and therefore they shouldn't get removed.

const DECLARATIONS = [
      Cmp1,
      Cmp2 
 ];

@NgModule({
  declarations: DECLARATIONS,
})
export class MyModule {
  static declarations: DECLARATIONS
}

That said as @clydin mentioned customized Webpack configurations and/or using Webpack specific features are not officially supported by the Angular tooling team.

clydin commented 2 years ago

One additional note regarding why this currently works in development but not production is that the JIT mode support code is removed in production. This support code adds metadata to the Angular module definitions at runtime which is allowing the reproduction to function. However, since JIT mode support code forces all unused components (including from libraries) to be retained and the application is AOT compiled, the JIT mode support code is removed in production.

nohanna commented 2 years ago

Ok I get it, I fixed it with a static field. Thanks for your answers.

angular-automatic-lock-bot[bot] commented 2 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.