Skip to content

feat: add micro-frontend example using @module-federation/vite#169

Merged
chrisjwalk merged 22 commits into
mainfrom
feat/mfe-example-168
Jun 7, 2026
Merged

feat: add micro-frontend example using @module-federation/vite#169
chrisjwalk merged 22 commits into
mainfrom
feat/mfe-example-168

Conversation

@chrisjwalk-bot
Copy link
Copy Markdown
Collaborator

Closes #168

Summary

Adds a working micro-frontend (MFE) demo using @module-federation/vite 1.16.2 — the only MFE solution compatible with @analogjs/platform (native federation requires Angular CLI builder, bypasses vite.config.ts).

Architecture

  • Host: apps/web-app (existing Analog app) — adds federation() as MFE host
  • Remote: apps/counter-remote (new, port 4201) — exposes ./Routes pointing to the counter feature lib

What was added

  • apps/counter-remote/ — new Vite+Angular app with MFE remote config, exposes libs/counter routes
  • apps/web-app/src/types/remotes.d.ts — TypeScript module declaration for counter-remote/Routes
  • New /mfe-counter route in web-app, loaded via import('counter-remote/Routes')
  • MFE Counter nav link in shared nav

Key implementation notes

  • federation() plugin is disabled in test mode (mode !== 'test') to avoid Vitest crashes
  • virtual:pwa-register externalized in remote build (transitively imported via @myorg/shared, tree-shaken)
  • chrome89 build target on both host and remote (required for top-level await)
  • passWithNoTests: true in counter-remote's Vitest config (remote has no spec files yet)

Known limitations

  • LayoutStore singleton: Each remote bundles its own copy of @myorg/shared — workspace libs can't be in MF shared config (only npm packages). Nav title won't update when the counter is loaded as an MFE.
  • Dev-only demo: @module-federation/vite issue #707 (async __loadShare__ wrappers) may affect production builds for components using <ng-content>. nx serve works as expected.

…#168)

- Add apps/counter-remote as MFE remote (port 4201, exposes ./Routes)
- Configure @module-federation/vite 1.16.2 on both host and remote
- Use federation() plugin disabled in test mode to avoid Vitest conflicts
- Add mfe-counter route to web-app using dynamic import('counter-remote/Routes')
- Add MFE Counter nav link in shared nav-links
- Add TypeScript module declaration for counter-remote/Routes
- Externalize virtual:pwa-register in remote build (tree-shaken, not instantiated)
- Set chrome89 build target on both host and remote for top-level await support
- Add passWithNoTests + tsconfig.spec.json to counter-remote for empty test suite
- Known limitation: LayoutStore singleton — remote bundles its own copy of
  @myorg/shared, so nav title won't update when loaded as MFE (workspace libs
  cannot be in MF shared config, only npm packages)
- Demo targets nx serve (dev mode); @module-federation/vite issue #707 (async
  __loadShare__ wrappers) may affect production builds with ng-content components

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

…ibility

@module-federation/vite crashes when server.watch is boolean false.
The @nx/vite build executor defaults watch to false, which gets merged
into the inline config before plugin config hooks run. A pre-enforce
plugin ensures server.watch is an object before the federation hook sees it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

- Add counter-remote:serve to web-app serve dependsOn
- Add continuous: true to counter-remote serve target
- Add normalize-server-watch plugin to counter-remote vite config

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

The host provides virtual:pwa-register via vite-plugin-pwa, but the
remote's dev server pre-transforms shared libs independently and can't
resolve it. A no-op virtual plugin stubs it out so the dev server
starts cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

Both host and remote were bundling separate copies of @ngrx/signals,
creating duplicate DI token identities for platform-scoped services
(Dispatcher, Events, ReducerEvents). Angular's DI detected this as
a circular dependency (NG0200) when the SignalStore-based CounterStore
was instantiated via the MFE route.

Sharing @ngrx/signals and @ngrx/signals/events as singletons ensures
a single set of class tokens across the federation boundary, fixing
the circular dependency error on navigation to /mfe-counter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

chrisjwalk-bot and others added 3 commits May 31, 2026 10:51
Prevents NG0912 component ID collision warnings by ensuring all Angular
CDK and Material sub-path packages, plus workspace libs (@myorg/counter,
@myorg/shared), are shared as singletons in both host and remote vite
configs. Previously only core Angular packages were shared, causing
CDK/Material components and workspace-lib components to be bundled
separately in each app and registered twice with Angular's component ID
registry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion)

MF virtual modules can't enumerate export * chains from TypeScript path
aliases, causing [MISSING_EXPORT] build errors. Workspace libs must
remain as separate bundles in each app.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…red singletons

Both @angular/material/divider and @angular/material/sidenav are
imported by workspace libs (@myorg/shared) which are loaded by both
the host and remote. Adding them as singletons prevents NG0912
component ID collisions for MatDivider, MatSidenav, etc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

chrisjwalk-bot and others added 3 commits May 31, 2026 20:50
- add counter-remote app exposing counter lib via module federation
- fix NG0912 collisions: import:false on CDK/Material in remote sharedDeps
- fix test OXC failures: fastCompile:mode==='test' in host analog() call
  (angular files outside tsconfig.spec.json compiled by fast-compile AOT;
   non-Angular TS stripped by OXC lang:'ts' instead of lang:'js')
- remove /feature duplicate route; /mfe-counter is the single MFE entry
- update README with MFE architecture section
- add .claude/skills/create-mfe SKILL.md documenting the full setup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure Static Web Apps allows at most one '*' per route pattern.
Replace '/counter-remote/**' with two single-wildcard patterns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump actions/checkout and actions/setup-node from v4 to v6 in
preview.yml and deploy.yml to avoid Node.js 20 deprecation warnings.
Also align actions/setup-dotnet to v5 in deploy.yml.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

- counter-remote-routes.ts: re-export counterRoutes directly from
  @myorg/counter instead of importing CounterContainer (which is not
  in the lib's barrel export), fixing NG04014 in CI integration tests
- E2E tests: update URL assertions from /feature to /mfe-counter
- playwright.config.ts: add counter-remote:serve webServer so MFE
  remote is available during E2E runs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

In preview.yml the copy step ran before web-app build, so the build
wipe wiped the counter-remote files. Fix: build both apps first, then
copy counter-remote into web-app/client/counter-remote/.

In deploy.yml counter-remote was never built or deployed at all. Add
build + copy steps and set COUNTER_REMOTE_ENTRY=/counter-remote/remoteEntry.js
so the host resolves the MFE correctly in production.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

chrisjwalk-bot and others added 2 commits May 31, 2026 22:16
- Fix step order: generate lib first, then remote app
- Add required barrel export and remotes.d.ts type declaration steps
- Clarify remote app shell files must be kept (keep main.ts, app.config.ts etc)
- Fix @module-federation/vite placement: devDependencies not dependencies
- Add checklist of vite.config.ts fields to update when cloning counter-remote
- Fix test stub: prefer re-exporting lib routes over importing components
  directly (avoids NG04014 when component is not in barrel)
- Fix CI build order: copy after BOTH builds, not before host build
  (host build wipes its output dir, destroying the copied remote files)
- Fix SWA wildcards: use /* not /** (SWA only allows one * per segment)
- Add E2E section with playwright webServer entries
- Add missing pitfalls: NG04014, remote files wiped by host build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ot level

Move CounterStore from component-scoped providers to providedIn: 'root'
so the state survives when navigating away from and back to the counter route.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

…ld output

Icons in src/assets/icons/ were not being copied to dist because Vite's
publicDir defaults to <root>/public, not src/. Moving them to
public/assets/icons/ ensures they are served at /assets/icons/*.png
in the deployed app, fixing 404s for PWA manifest icons in SWA.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

 #168)

provideAnimations() from @angular/platform-browser/animations resolves to
undefined through the @module-federation/vite loadShare mechanism, causing
"TypeError: Ut is not a function" in the deployed preview.

Since provideAnimations() is deprecated in Angular 21+ (animations are now
provided automatically by the platform), removing the explicit call eliminates
the broken loadShare dependency.

Also added an MFE integration e2e test that guards against JS load errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

chrisjwalk-bot and others added 4 commits June 7, 2026 14:58
- Removed injectDispatch, eventGroup, on, withReducer from CounterStore
- Consolidated into single signalStore with withMethods + patchState
- Removed @ngrx/signals/events and @angular/animations from counter-remote shared deps
- The root cause: @module-federation/vite generates same loadShare path for
  @ngrx/signals and @ngrx/signals/events, inlining a separate copy of
  @angular/core internals (_injectImplementation) that is never set during
  host DI, causing takeUntilDestroyed() to fail with NG0203.
- Updated counter E2E tests: all 31 passed (including MFE integration)
- Local verification: counter MFE renders with zero JS errors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…led)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…te too

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Moved pnpm.overrides from package.json to pnpm-workspace.yaml (fixes
  pnpm 11 deprecation warning & CI lockfile mismatch with --frozen-lockfile)
- Updated all 3 CI workflows to pnpm 11 to match local version
- Regenerated lockfile

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://green-water-08792290f-169.eastus2.2.azurestaticapps.net

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Note

Coverage shown for affected projects only. Per-file thresholds (80%) are enforced by vitest.

Code Coverage

Package Line Rate Branch Rate Complexity Health
src 100% 100% 0
src.lib.components.counter 100% 100% 0
src.lib.components.counter-container 100% 100% 0
src.lib.state 100% 100% 0
src 100% 100% 0
src.lib.components.forecast-table 100% 100% 0
src.lib.components.weather-forecast 100% 100% 0
src.lib.models 100% 100% 0
src.lib.services 100% 100% 0
src.lib.state 100% 100% 0
src 100% 100% 0
src.lib.home 100% 100% 0
src 100% 100% 0
src.lib.components 98% 100% 0
src.lib.state 100% 100% 0
src.lib.testing 100% 100% 0
src 100% 100% 0
src.lib.services 100% 100% 0
src.lib.state 100% 100% 0
src 100% 100% 0
src.lib.login 100% 100% 0
src.lib.state 100% 100% 0
Summary 100% (333 / 335) 100% (95 / 95) 0

@chrisjwalk chrisjwalk merged commit 7630c8d into main Jun 7, 2026
7 checks passed
@chrisjwalk chrisjwalk deleted the feat/mfe-example-168 branch June 7, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add micro-frontend example using @module-federation/vite

3 participants