angular-architects / module-federation-plugin

MIT License
735 stars 203 forks source link

Incompatibility between Native Federation microfrontend and alias paths in a hybrid microfrontend-library setup #697

Open lblacio-craft opened 5 days ago

lblacio-craft commented 5 days ago

For which library do you need help?

native-federation

Question

I encountered an issue while trying to use a Native Federation microfrontend project as both a microfrontend and a reusable library. The problem arises when using paths aliases in the tsconfig.json. Some aliases work fine when pointing to a standard Angular library, while others fail when pointing to a Native Federation microfrontend project.

Aliases Configuration

Here are the alias configurations I used in the tsconfig.json of my consumer project:

"paths": {
  "@shared-lib": ["../emile-ui-shared/projects/emile-ui-shared/src/public-api.ts"],
  "@core-lib": ["../emile-ui-core/public-api.ts"]
}

Behavior:

@shared-lib: This alias points to a standard Angular library (emile-ui-shared). It works perfectly. @core-lib: This alias points to a Native Federation microfrontend (emile-ui-core) that is also structured as a library. This does not work and throws an error. Error Details When I try to import from @core-lib, I encounter the following error during the build process:


ERROR TS-993004: Unable to import component AvatarComponent.
The symbol is not exported from ../emile-ui-core/public-api.ts (module '@core-lib').

src/app/ui/avatar/icon/icon.component.ts:11:13:
export class TypeIconDemoComponent {

The component is declared here:
../emile-ui-core/src/app/ui/avatar/avatar.component.ts:9:0:
@Component({

This error happens even though the component is correctly declared and exported in public-api.ts. Moreover, if I directly import the component using a relative path (e.g., ../../../../emile-ui-core/src/app/ui/avatar/avatar.component), it works fine.

Expected Behavior

The Native Federation project (emile-ui-core) should work seamlessly with alias paths when consumed as a library, just like the standard Angular library (emile-ui-shared).

Actual Behavior

The alias path pointing to the Native Federation project fails to resolve correctly, even though the symbol is exported in public-api.ts.

Steps to Reproduce

Set up a Native Federation project (emile-ui-core) that is structured to work as both a microfrontend and a reusable library. Define paths in the tsconfig.json of a consumer project:

"paths": {
  "@shared-lib": ["../emile-ui-shared/projects/emile-ui-shared/src/public-api.ts"],
  "@core-lib": ["../emile-ui-core/public-api.ts"]
}
Import a component from both aliases in the consumer project:

import { SharedComponent } from '@shared-lib';
import { CoreComponent } from '@core-lib';

Build the consumer project.

Additional Information Version Details:

Native Federation version: 18.2.2 Angular version: 18.2.12 TypeScript version: 5.4.2 The public-api.ts for @core-lib is located at ../emile-ui-core/public-api.ts and includes:

export * from './src/app/ui/avatar/avatar.component';

The standalone component (AvatarComponent) is correctly included in the fesm2022/index.d.ts when built but is not resolved through the alias.

The @shared-lib alias works perfectly because it points to a standard Angular library (emile-ui-shared).

Observed Temporary Fix

To temporarily fix the issue, I had to explicitly export the components individually in the public-api.ts file. Here is how the public-api.ts looks:

/*
 * Public API Surface of emile-ui-core-lib
 */
export * as CoreModel from './src/app/domain/view-model';
export * as CoreComponent from './src/app/ui';

/* Temporary */

export { AvatarComponent } from './src/app/ui';
export { AppPopoverComponent } from './src/app/ui';
export { LabelComponent } from './src/app/ui';

With the above structure:

Imports like CoreComponent.AvatarComponent do not work. Direct imports of AvatarComponent do work. Here is an example of the component usage that works after applying this fix:

import { CoreComponent, CoreModel } from '@core-lib';

@Component({
  selector: 'app-icon',
  standalone: true,
  imports: [CoreComponent.AvatarComponent], // This now works with the explicit export
  templateUrl: './icon.component.html',
  styleUrl: './icon.component.scss'
})
export class TypeIconDemoComponent {
  type = CoreModel.AvatarType.icon;
  size = CoreModel.Size.md;
}

Questions