From bbd13b99a038fc379274e35ebfea631d599c9f8d Mon Sep 17 00:00:00 2001 From: markuczy Date: Tue, 2 Jul 2024 12:21:48 +0200 Subject: [PATCH] fix: custom module bootstrap with router --- .../lib/utils/webcomponent-bootstrap.utils.ts | 112 ++++++++++++++++-- .../lib/utils/webcomponent-router.utils.ts | 6 +- package-lock.json | 6 +- 3 files changed, 103 insertions(+), 21 deletions(-) diff --git a/libs/angular-webcomponents/src/lib/utils/webcomponent-bootstrap.utils.ts b/libs/angular-webcomponents/src/lib/utils/webcomponent-bootstrap.utils.ts index e8e28ca3..56c9190b 100644 --- a/libs/angular-webcomponents/src/lib/utils/webcomponent-bootstrap.utils.ts +++ b/libs/angular-webcomponents/src/lib/utils/webcomponent-bootstrap.utils.ts @@ -1,33 +1,61 @@ import { createCustomElement } from '@angular/elements' -import { createApplication } from '@angular/platform-browser' -import { EnvironmentProviders, NgZone, PlatformRef, Provider, Type, VERSION, Version, getPlatform } from '@angular/core' +import { createApplication, platformBrowser } from '@angular/platform-browser' +import { + EnvironmentProviders, + Injector, + NgModuleRef, + NgZone, + PlatformRef, + Provider, + Type, + VERSION, + Version, + enableProdMode, + getPlatform, +} from '@angular/core' +import { Router } from '@angular/router' +import { getLocation } from '@onecx/accelerator' + +/** + * Implementation inspired by @angular-architects/module-federation-plugin https://github.com/angular-architects/module-federation-plugin/blob/main/libs/mf-tools/src/lib/web-components/bootstrap-utils.ts#L191 + */ + +export type AppType = 'shell' | 'microfrontend' + +export function bootstrapModule(module: Type, appType: AppType, production: boolean): Promise> { + return cachePlatform(production) + .bootstrapModule(module, { + ngZone: getNgZone(), + }) + .then((ref) => { + if (appType === 'shell') { + setShellZone(ref.injector) + } else if (appType === 'microfrontend') { + connectMicroFrontendRouter(ref.injector) + } + return ref + }) +} export async function bootstrapRemoteComponent( component: Type, elementName: string, + production: boolean, providers: (Provider | EnvironmentProviders)[] ): Promise { const app = await createApplication({ providers: [ - (window as any)['@angular-architects/module-federation-tools'].ngZone + getNgZone() ? { provide: NgZone, - useValue: (window as any)['@angular-architects/module-federation-tools'].ngZone, + useValue: getNgZone(), } : [], ...providers, ], }) - const platform = getPlatform() - let platformCache: Map = (window as any)['@angular-architects/module-federation-tools'] - .platformCache - if (!platformCache) { - platformCache = new Map() - ;(window as any)['@angular-architects/module-federation-tools'].platformCache = platformCache - } - const version = VERSION - platform && platformCache.set(version, platform) + cachePlatform(production) const myRemoteComponentAsWebComponent = createCustomElement(component, { injector: app.injector, @@ -35,3 +63,61 @@ export async function bootstrapRemoteComponent( customElements.define(elementName, myRemoteComponentAsWebComponent) } + +function getWindowState(): any { + const state = window as any + state['@onecx/angular-webcomponents'] = state['@onecx/angular-webcomponents'] || ({} as unknown) + return state +} + +function setShellZone(injector: Injector) { + const ngZone = injector.get(NgZone, null) + if (!ngZone) { + console.warn('No NgZone to share found') + return + } + setNgZone(ngZone) +} + +function setNgZone(ngZone: NgZone): void { + getWindowState().ngZone = ngZone +} + +function getNgZone(): NgZone { + return getWindowState().ngZone +} + +function cachePlatform(production: boolean): PlatformRef { + let platformCache: Map = getWindowState().platformCache + if (!platformCache) { + platformCache = new Map() + getWindowState().platformCache = platformCache + } + const version = VERSION + let platform: any = platformCache.get(version) + if (!platform) { + platform = getPlatform() ?? platformBrowser() + platform && platformCache.set(version, platform) + production ?? enableProdMode() + } + return platform +} + +function connectMicroFrontendRouter(injector: Injector) { + const router = injector.get(Router) + + if (!router) { + console.warn('No router to connect found') + return + } + + connectRouter(router) +} + +function connectRouter(router: Router): void { + let url = `${location.pathname.substring(getLocation().deploymentPath.length)}${location.search}` + router.navigateByUrl(url) + window.addEventListener('popstate', () => { + router.navigateByUrl(url) + }) +} diff --git a/libs/angular-webcomponents/src/lib/utils/webcomponent-router.utils.ts b/libs/angular-webcomponents/src/lib/utils/webcomponent-router.utils.ts index 39d999be..618dd209 100644 --- a/libs/angular-webcomponents/src/lib/utils/webcomponent-router.utils.ts +++ b/libs/angular-webcomponents/src/lib/utils/webcomponent-router.utils.ts @@ -1,5 +1,4 @@ import { Route, UrlMatcher, UrlSegment, UrlSegmentGroup } from '@angular/router' -import { getLocation } from '@onecx/accelerator' export function startsWith(prefix: string): UrlMatcher { return (url: UrlSegment[], UrlSegmentGroup: UrlSegmentGroup, route: Route) => { @@ -22,10 +21,7 @@ export function sliceBaseHref(route: Route, url: UrlSegment[]): UrlSegment[] { ) } - const baseHrefSegmentAmount = getLocation() - .deploymentPath.concat(mfeBaseHref) - .split('/') - .filter((value) => value).length + const baseHrefSegmentAmount = mfeBaseHref.split('/').filter((value) => value).length return url.slice(baseHrefSegmentAmount) } diff --git a/package-lock.json b/package-lock.json index dfc4b433..29363f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@onecx/onecx-portal-ui-libs", - "version": "4.36.0", + "version": "4.37.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@onecx/onecx-portal-ui-libs", - "version": "4.36.0", + "version": "4.37.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -113,7 +113,7 @@ }, "libs/portal-layout-styles": { "name": "@onecx/portal-layout-styles", - "version": "4.36.0", + "version": "4.37.1", "peerDependencies": { "tslib": "^2.5.0" }