From e250db4f261741b04ee4cbad4dec41a8908a12aa Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sun, 3 Apr 2022 22:40:57 -0700 Subject: [PATCH] refactor(core): avoid referencing `PlatformRef` in bootstrap code (#45519) This commit updates an existing bootstrap logic to avoid referencing the `PlatformRef` instance to keep track of the platform status. Instead, we use platform injector, so that the `PlatformRef`can be tree-shaken away in the bootstrap logic for Standalone Components. The motivation for this change is that retaining the `PlatformRef` class also retains NgModule-based bootstrap code, which would not be needed in case of Standalone Components. PR Close #45519 --- packages/core/src/application_ref.ts | 70 +++++++++++++------ .../animations/bundle.golden_symbols.json | 5 +- .../forms_reactive/bundle.golden_symbols.json | 5 +- .../bundle.golden_symbols.json | 5 +- .../router/bundle.golden_symbols.json | 5 +- 5 files changed, 64 insertions(+), 26 deletions(-) diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 510961e2dc4..6245aa0bd2b 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -43,7 +43,21 @@ import {scheduleMicroTask} from './util/microtask'; import {stringify} from './util/stringify'; import {NgZone, NoopNgZone} from './zone/ng_zone'; -let _platform: PlatformRef; +let _platformInjector: Injector|null = null; + +/** + * Internal token to indicate whether having multiple bootstrapped platform should be allowed (only + * one bootstrapped platform is allowed by default). This token helps to support SSR scenarios. + */ +export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken('AllowMultipleToken'); + +/** + * Internal token that allows to register extra callbacks that should be invoked during the + * `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the + * `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the + * entire class tree-shakeable. + */ +const PLATFORM_ON_DESTROY = new InjectionToken<() => void>('PlatformOnDestroy'); export function compileNgModuleFactory( injector: Injector, options: CompilerOptions, @@ -102,10 +116,6 @@ export function isBoundToModule(cf: ComponentFactory): boolean { return (cf as R3ComponentFactory).isBoundToModule; } -export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken('AllowMultipleToken'); - - - /** * A token for third-party components that can register themselves with NgProbe. * @@ -122,18 +132,18 @@ export class NgProbeToken { * @publicApi */ export function createPlatform(injector: Injector): PlatformRef { - if (_platform && !_platform.destroyed && - !_platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { + if (_platformInjector && !_platformInjector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ? 'There can be only one platform. Destroy the previous one to create a new one.' : ''; throw new RuntimeError(RuntimeErrorCode.MULTIPLE_PLATFORMS, errorMessage); } publishDefaultGlobalUtils(); - _platform = injector.get(PlatformRef); + _platformInjector = injector; + const platform = injector.get(PlatformRef); const inits = injector.get(PLATFORM_INITIALIZER, null); - if (inits) inits.forEach((init: any) => init()); - return _platform; + if (inits) inits.forEach(initFn => initFn()); + return platform; } /** @@ -155,16 +165,15 @@ export function createPlatformFactory( return (extraProviders: StaticProvider[] = []) => { let platform = getPlatform(); if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { + const platformProviders: StaticProvider[] = [ + ...providers, // + ...extraProviders, // + {provide: marker, useValue: true} + ]; if (parentPlatformFactory) { - parentPlatformFactory( - providers.concat(extraProviders).concat({provide: marker, useValue: true})); + parentPlatformFactory(platformProviders); } else { - const injectedProviders: StaticProvider[] = - providers.concat(extraProviders).concat({provide: marker, useValue: true}, { - provide: INJECTOR_SCOPE, - useValue: 'platform' - }); - createPlatform(Injector.create({providers: injectedProviders, name: desc})); + createPlatform(createPlatformInjector(platformProviders, desc)); } } return assertPlatform(marker); @@ -195,6 +204,21 @@ export function assertPlatform(requiredToken: any): PlatformRef { return platform; } +/** + * Helper function to create an instance of a platform injector (that maintains the 'platform' + * scope). + */ +export function createPlatformInjector(providers: StaticProvider[] = [], name?: string): Injector { + return Injector.create({ + name, + providers: [ + {provide: INJECTOR_SCOPE, useValue: 'platform'}, + {provide: PLATFORM_ON_DESTROY, useValue: () => _platformInjector = null}, // + ...providers + ], + }); +} + /** * Destroys the current Angular platform and all Angular applications on the page. * Destroys all modules and listeners registered with the platform. @@ -202,9 +226,7 @@ export function assertPlatform(requiredToken: any): PlatformRef { * @publicApi */ export function destroyPlatform(): void { - if (_platform && !_platform.destroyed) { - _platform.destroy(); - } + getPlatform()?.destroy(); } /** @@ -213,7 +235,7 @@ export function destroyPlatform(): void { * @publicApi */ export function getPlatform(): PlatformRef|null { - return _platform && !_platform.destroyed ? _platform : null; + return _platformInjector?.get(PlatformRef) ?? null; } /** @@ -417,6 +439,10 @@ export class PlatformRef { } this._modules.slice().forEach(module => module.destroy()); this._destroyListeners.forEach(listener => listener()); + + const destroyListener = this._injector.get(PLATFORM_ON_DESTROY, null); + destroyListener?.(); + this._destroyed = true; } diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index d013749862e..6fa02b4876c 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -353,6 +353,9 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_ON_DESTROY" + }, { "name": "PlatformRef" }, @@ -531,7 +534,7 @@ "name": "_keyMap" }, { - "name": "_platform" + "name": "_platformInjector" }, { "name": "_query" diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index eb476c3421f..32dcbb1be6a 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -356,6 +356,9 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_ON_DESTROY" + }, { "name": "PlatformRef" }, @@ -534,7 +537,7 @@ "name": "_keyMap" }, { - "name": "_platform" + "name": "_platformInjector" }, { "name": "_randomChar" diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 4ce2b9e9e1b..e6e0b373e84 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -350,6 +350,9 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_ON_DESTROY" + }, { "name": "PlatformRef" }, @@ -525,7 +528,7 @@ "name": "_keyMap" }, { - "name": "_platform" + "name": "_platformInjector" }, { "name": "_randomChar" diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 8604f381501..6304a62f6fb 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -422,6 +422,9 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_ON_DESTROY" + }, { "name": "PathLocationStrategy" }, @@ -762,7 +765,7 @@ "name": "_keyMap" }, { - "name": "_platform" + "name": "_platformInjector" }, { "name": "_randomChar"