piral-ng
Version:
Plugin for integrating Angular components in Piral.
171 lines (147 loc) • 5.72 kB
text/typescript
import type { ComponentContext } from 'piral-core';
import type { NgModuleRef, PlatformRef } from '@angular/core';
import * as angularBrowserDynamic from '@angular/platform-browser-dynamic';
import * as angularCore from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { contextName } from './constants';
import { CONTEXT } from './injection';
import { getNgVersion } from './utils';
import type { NgModuleFlags, NgOptions } from './types';
function getVersionHandler(versions: Record<string, () => void>) {
const major = getNgVersion();
const version = `v${major}`;
return versions[version];
}
const runningModules: Array<[any, NgModuleInt, PlatformRef]> = [];
function startNew(BootstrapModule: any, context: ComponentContext, ngOptions?: NgOptions, ngFlags?: NgModuleFlags) {
const normalCall = 'platformBrowserDynamic';
const legacyCall = 'ɵplatformCoreDynamic';
const multiplePlatforms = 'ɵALLOW_MULTIPLE_PLATFORMS';
const platformCoreDynamic =
normalCall in angularBrowserDynamic ? angularBrowserDynamic[normalCall] : angularBrowserDynamic[legacyCall];
const path = context.publicPath || '/';
const extraDefinitions =
multiplePlatforms in angularCore ? [{ provide: angularCore[multiplePlatforms], useValue: true }] : [];
const platform = platformCoreDynamic([
...extraDefinitions,
{ provide: contextName, useValue: context },
{ provide: CONTEXT, useValue: context },
{ provide: APP_BASE_HREF, useValue: path },
{ provide: 'NgFlags', useValue: ngFlags },
]);
let zoneIdentifier = '';
if ('NgZone' in angularCore) {
const zone = angularCore.NgZone;
const version = angularCore.VERSION.full;
// We need to bind the version-specific NgZone to its ID
// this will not be MF-dependent, but Angular dependent
// i.e., to allow using Zone.js with multiple versions of Angular
zoneIdentifier = `piral-ng:${version}`;
// This is a hack, since NgZone doesn't allow you to configure the property that identifies your zone.
// See:
// - https://github.com/PlaceMe-SAS/single-spa-angular-cli/issues/33
// - https://github.com/angular/angular/blob/a14dc2d7a4821a19f20a9547053a5734798f541e/packages/core/src/zone/ng_zone.ts#L144
// - https://github.com/angular/angular/blob/a14dc2d7a4821a19f20a9547053a5734798f541e/packages/core/src/zone/ng_zone.ts#L257
// @ts-ignore
zone.isInAngularZone = () => window.Zone.current._properties[zoneIdentifier] === true;
// We disable those checks as they are misleading and might cause trouble
zone.assertInAngularZone = () => {};
zone.assertNotInAngularZone = () => {};
}
return platform
.bootstrapModule(BootstrapModule, ngOptions)
.catch((err) => console.error(err))
.then((instance: NgModuleInt) => {
if (instance) {
if (zoneIdentifier) {
const zone = instance.injector.get(angularCore.NgZone);
// @ts-ignore
const z = zone?._inner ?? zone?.inner;
if (z && '_properties' in z) {
z._properties[zoneIdentifier] = true;
}
}
runningModules.push([BootstrapModule, instance, platform]);
}
return instance;
});
}
export type NgModuleInt = NgModuleRef<any> & { _destroyed: boolean };
export function teardown(BootstrapModule: any) {
const runningModuleIndex = runningModules.findIndex(([ref]) => ref === BootstrapModule);
if (runningModuleIndex !== -1) {
const [, instance] = runningModules[runningModuleIndex];
try {
instance.destroy();
runningModules.splice(runningModuleIndex, 1);
} catch (ex) {
console.warn('Could not destroy the running Angular module', ex);
}
}
}
export function startup(
BootstrapModule: any,
context: ComponentContext,
ngOptions?: NgOptions,
ngFlags?: NgModuleFlags,
): Promise<void | NgModuleInt> {
const runningModule = runningModules.find(([ref]) => ref === BootstrapModule);
if (runningModule) {
const [, instance, platform] = runningModule;
if (platform.destroyed) {
teardown(BootstrapModule);
} else {
return Promise.resolve(instance);
}
}
return startNew(BootstrapModule, context, ngOptions, ngFlags);
}
if (process.env.NODE_ENV === 'development') {
// May be used later for something useful. Right now only debugging output.
const versionHandlers = {
legacy() {
console.log('Running in legacy mode (Angular 2-8)');
},
outdated() {
console.log('Running in outdated mode (Angular 9-18)');
},
current() {
console.log('Running in current mode (Angular 19-21)');
},
next() {
console.log('Running in next mode (Angular 22)');
},
unknown() {
console.log('Running with an unknown version of Angular');
},
};
const versions = {
v2: versionHandlers.legacy,
v4: versionHandlers.legacy,
v5: versionHandlers.legacy,
v6: versionHandlers.legacy,
v7: versionHandlers.legacy,
v8: versionHandlers.legacy,
v9: versionHandlers.outdated,
v10: versionHandlers.outdated,
v11: versionHandlers.outdated,
v12: versionHandlers.outdated,
v13: versionHandlers.outdated,
v14: versionHandlers.outdated,
v15: versionHandlers.outdated,
v16: versionHandlers.outdated,
v17: versionHandlers.outdated,
v18: versionHandlers.outdated,
v19: versionHandlers.current,
v20: versionHandlers.current,
v21: versionHandlers.current,
v22: versionHandlers.next,
};
const handler = getVersionHandler(versions) || versionHandlers.unknown;
handler();
}
if (process.env.NODE_ENV === 'production') {
if ('enableProdMode' in angularCore) {
angularCore.enableProdMode();
}
}