mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): de-duplicate bootstrap code between bootstrapApplication and bootstrapModule (#57060)
This commit de-duplicates the code for bootstrapping between `bootstrapApplication` and `bootstrapModule`. A majority of the bootstrap code was identical between the two, with some minor differences that can be handled with a function overload. PR Close #57060
This commit is contained in:
committed by
Jessica Janiuk
parent
3459289ef0
commit
3da0254478
@@ -6,34 +6,19 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Subscription} from 'rxjs';
|
||||
|
||||
import {
|
||||
internalProvideZoneChangeDetection,
|
||||
PROVIDED_NG_ZONE,
|
||||
} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
|
||||
import {EnvironmentInjector} from '../di/r3_injector';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
|
||||
import {LOCALE_ID} from '../i18n/tokens';
|
||||
import {ImagePerformanceWarning} from '../image_performance_warning';
|
||||
import {Type} from '../interface/type';
|
||||
import {createOrReusePlatformInjector} from '../platform/platform';
|
||||
import {PLATFORM_DESTROY_LISTENERS} from '../platform/platform_ref';
|
||||
import {assertStandaloneComponentType} from '../render3/errors';
|
||||
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
|
||||
import {EnvironmentNgModuleRefAdapter} from '../render3/ng_module_ref';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref';
|
||||
import {
|
||||
PROVIDED_ZONELESS,
|
||||
ChangeDetectionScheduler,
|
||||
} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
|
||||
import {bootstrap} from '../platform/bootstrap';
|
||||
|
||||
/**
|
||||
* Internal create application API that implements the core application creation logic and optional
|
||||
@@ -76,67 +61,11 @@ export function internalCreateApplication(config: {
|
||||
// happens after we get the NgZone instance from the Injector.
|
||||
runEnvironmentInitializers: false,
|
||||
});
|
||||
const envInjector = adapter.injector;
|
||||
const ngZone = envInjector.get(NgZone);
|
||||
|
||||
return ngZone.run(() => {
|
||||
envInjector.resolveInjectorInitializers();
|
||||
const exceptionHandler: ErrorHandler | null = envInjector.get(ErrorHandler, null);
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
if (!exceptionHandler) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
|
||||
'No `ErrorHandler` found in the Dependency Injection tree.',
|
||||
);
|
||||
}
|
||||
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let onErrorSubscription: Subscription;
|
||||
ngZone.runOutsideAngular(() => {
|
||||
onErrorSubscription = ngZone.onError.subscribe({
|
||||
next: (error: any) => {
|
||||
exceptionHandler!.handleError(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// If the whole platform is destroyed, invoke the `destroy` method
|
||||
// for all bootstrapped applications as well.
|
||||
const destroyListener = () => envInjector.destroy();
|
||||
const onPlatformDestroyListeners = platformInjector.get(PLATFORM_DESTROY_LISTENERS);
|
||||
onPlatformDestroyListeners.add(destroyListener);
|
||||
|
||||
envInjector.onDestroy(() => {
|
||||
onErrorSubscription.unsubscribe();
|
||||
onPlatformDestroyListeners.delete(destroyListener);
|
||||
});
|
||||
|
||||
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
|
||||
const initStatus = envInjector.get(ApplicationInitStatus);
|
||||
initStatus.runInitializers();
|
||||
|
||||
return initStatus.donePromise.then(() => {
|
||||
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
|
||||
setLocaleId(localeId || DEFAULT_LOCALE_ID);
|
||||
|
||||
const appRef = envInjector.get(ApplicationRef);
|
||||
if (rootComponent !== undefined) {
|
||||
appRef.bootstrap(rootComponent);
|
||||
}
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
|
||||
imagePerformanceService.start();
|
||||
}
|
||||
return appRef;
|
||||
});
|
||||
});
|
||||
return bootstrap({
|
||||
r3Injector: adapter.injector,
|
||||
platformInjector,
|
||||
rootComponent,
|
||||
});
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Subscription} from 'rxjs';
|
||||
|
||||
import {PROVIDED_NG_ZONE} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {EnvironmentInjector, R3Injector} from '../di/r3_injector';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
|
||||
import {LOCALE_ID} from '../i18n/tokens';
|
||||
import {ImagePerformanceWarning} from '../image_performance_warning';
|
||||
import {Type} from '../interface/type';
|
||||
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
|
||||
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
|
||||
import {ApplicationInitStatus} from '../application/application_init';
|
||||
import {_callAndReportToErrorHandler, ApplicationRef, remove} from '../application/application_ref';
|
||||
import {PROVIDED_ZONELESS} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
import {Injector} from '../di';
|
||||
import {InternalNgModuleRef, NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {stringify} from '../util/stringify';
|
||||
|
||||
export interface ModuleBootstrapConfig<M> {
|
||||
moduleRef: InternalNgModuleRef<M>;
|
||||
allPlatformModules: NgModuleRef<unknown>[];
|
||||
}
|
||||
|
||||
export interface ApplicationBootstrapConfig {
|
||||
r3Injector: R3Injector;
|
||||
platformInjector: Injector;
|
||||
rootComponent: Type<unknown> | undefined;
|
||||
}
|
||||
|
||||
function isApplicationBootstrapConfig(
|
||||
config: ApplicationBootstrapConfig | ModuleBootstrapConfig<unknown>,
|
||||
): config is ApplicationBootstrapConfig {
|
||||
return !!(config as ApplicationBootstrapConfig).platformInjector;
|
||||
}
|
||||
|
||||
export function bootstrap<M>(
|
||||
moduleBootstrapConfig: ModuleBootstrapConfig<M>,
|
||||
): Promise<NgModuleRef<M>>;
|
||||
export function bootstrap(
|
||||
applicationBootstrapConfig: ApplicationBootstrapConfig,
|
||||
): Promise<ApplicationRef>;
|
||||
export function bootstrap<M>(
|
||||
config: ModuleBootstrapConfig<M> | ApplicationBootstrapConfig,
|
||||
): Promise<ApplicationRef> | Promise<NgModuleRef<M>> {
|
||||
const envInjector = isApplicationBootstrapConfig(config)
|
||||
? config.r3Injector
|
||||
: config.moduleRef.injector;
|
||||
const ngZone = envInjector.get(NgZone);
|
||||
return ngZone.run(() => {
|
||||
if (isApplicationBootstrapConfig(config)) {
|
||||
config.r3Injector.resolveInjectorInitializers();
|
||||
} else {
|
||||
config.moduleRef.resolveInjectorInitializers();
|
||||
}
|
||||
const exceptionHandler = envInjector.get(ErrorHandler, null);
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
if (exceptionHandler === null) {
|
||||
const errorMessage = isApplicationBootstrapConfig(config)
|
||||
? 'No `ErrorHandler` found in the Dependency Injection tree.'
|
||||
: 'No ErrorHandler. Is platform module (BrowserModule) included';
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
|
||||
errorMessage,
|
||||
);
|
||||
}
|
||||
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let onErrorSubscription: Subscription;
|
||||
ngZone.runOutsideAngular(() => {
|
||||
onErrorSubscription = ngZone.onError.subscribe({
|
||||
next: (error: any) => {
|
||||
exceptionHandler!.handleError(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (isApplicationBootstrapConfig(config)) {
|
||||
// If the whole platform is destroyed, invoke the `destroy` method
|
||||
// for all bootstrapped applications as well.
|
||||
const destroyListener = () => envInjector.destroy();
|
||||
const onPlatformDestroyListeners = config.platformInjector.get(PLATFORM_DESTROY_LISTENERS);
|
||||
onPlatformDestroyListeners.add(destroyListener);
|
||||
|
||||
envInjector.onDestroy(() => {
|
||||
onErrorSubscription.unsubscribe();
|
||||
onPlatformDestroyListeners.delete(destroyListener);
|
||||
});
|
||||
} else {
|
||||
config.moduleRef.onDestroy(() => {
|
||||
remove(config.allPlatformModules, config.moduleRef);
|
||||
onErrorSubscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
|
||||
const initStatus = envInjector.get(ApplicationInitStatus);
|
||||
initStatus.runInitializers();
|
||||
|
||||
return initStatus.donePromise.then(() => {
|
||||
// If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy
|
||||
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
|
||||
setLocaleId(localeId || DEFAULT_LOCALE_ID);
|
||||
|
||||
if (isApplicationBootstrapConfig(config)) {
|
||||
const appRef = envInjector.get(ApplicationRef);
|
||||
if (config.rootComponent !== undefined) {
|
||||
appRef.bootstrap(config.rootComponent);
|
||||
}
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
|
||||
imagePerformanceService.start();
|
||||
}
|
||||
return appRef;
|
||||
} else {
|
||||
moduleDoBootstrap(config.moduleRef, config.allPlatformModules);
|
||||
return config.moduleRef;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function moduleDoBootstrap(
|
||||
moduleRef: InternalNgModuleRef<any>,
|
||||
allPlatformModules: NgModuleRef<unknown>[],
|
||||
): void {
|
||||
const appRef = moduleRef.injector.get(ApplicationRef);
|
||||
if (moduleRef._bootstrapComponents.length > 0) {
|
||||
moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f));
|
||||
} else if (moduleRef.instance.ngDoBootstrap) {
|
||||
moduleRef.instance.ngDoBootstrap(appRef);
|
||||
} else {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND,
|
||||
ngDevMode &&
|
||||
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` +
|
||||
`but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
|
||||
`Please define one of these.`,
|
||||
);
|
||||
}
|
||||
allPlatformModules.push(moduleRef);
|
||||
}
|
||||
@@ -15,7 +15,8 @@ import {InjectionToken, Injector, StaticProvider} from '../di';
|
||||
import {INJECTOR_SCOPE} from '../di/scope';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
|
||||
import {PLATFORM_DESTROY_LISTENERS, PlatformRef} from './platform_ref';
|
||||
import {PlatformRef} from './platform_ref';
|
||||
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
|
||||
|
||||
let _platformInjector: Injector | null = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '../di';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const PLATFORM_DESTROY_LISTENERS = new InjectionToken<Set<VoidFunction>>(
|
||||
ngDevMode ? 'PlatformDestroyListeners' : '',
|
||||
);
|
||||
@@ -6,47 +6,27 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationInitStatus} from '../application/application_init';
|
||||
import {compileNgModuleFactory} from '../application/application_ngmodule_factory_compiler';
|
||||
import {
|
||||
_callAndReportToErrorHandler,
|
||||
ApplicationRef,
|
||||
BootstrapOptions,
|
||||
optionsReducer,
|
||||
remove,
|
||||
} from '../application/application_ref';
|
||||
import {
|
||||
getNgZoneOptions,
|
||||
internalProvideZoneChangeDetection,
|
||||
PROVIDED_NG_ZONE,
|
||||
} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {
|
||||
ChangeDetectionScheduler,
|
||||
PROVIDED_ZONELESS,
|
||||
} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
|
||||
import {Injectable, InjectionToken, Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {Injectable, Injector} from '../di';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
|
||||
import {LOCALE_ID} from '../i18n/tokens';
|
||||
import {Type} from '../interface/type';
|
||||
import {CompilerOptions} from '../linker';
|
||||
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {setLocaleId} from '../render3';
|
||||
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {createNgModuleRefWithProviders} from '../render3/ng_module_ref';
|
||||
import {stringify} from '../util/stringify';
|
||||
import {getNgZone, NgZone, NoopNgZone} from '../zone/ng_zone';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const PLATFORM_DESTROY_LISTENERS = new InjectionToken<Set<VoidFunction>>(
|
||||
ngDevMode ? 'PlatformDestroyListeners' : '',
|
||||
);
|
||||
import {getNgZone, NgZone} from '../zone/ng_zone';
|
||||
import {bootstrap} from './bootstrap';
|
||||
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
|
||||
|
||||
/**
|
||||
* The Angular platform is the entry point for Angular on a web page.
|
||||
@@ -97,52 +77,8 @@ export class PlatformRef {
|
||||
this.injector,
|
||||
allAppProviders,
|
||||
);
|
||||
const envInjector = moduleRef.injector;
|
||||
const ngZone = envInjector.get(NgZone);
|
||||
|
||||
return ngZone.run(() => {
|
||||
moduleRef.resolveInjectorInitializers();
|
||||
const exceptionHandler = envInjector.get(ErrorHandler, null);
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
if (exceptionHandler === null) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
|
||||
'No ErrorHandler. Is platform module (BrowserModule) included?',
|
||||
);
|
||||
}
|
||||
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngZone.runOutsideAngular(() => {
|
||||
const subscription = ngZone.onError.subscribe({
|
||||
next: (error: any) => {
|
||||
exceptionHandler!.handleError(error);
|
||||
},
|
||||
});
|
||||
moduleRef.onDestroy(() => {
|
||||
remove(this._modules, moduleRef);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
|
||||
const initStatus = envInjector.get(ApplicationInitStatus);
|
||||
initStatus.runInitializers();
|
||||
|
||||
return initStatus.donePromise.then(() => {
|
||||
// If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy
|
||||
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
|
||||
setLocaleId(localeId || DEFAULT_LOCALE_ID);
|
||||
this._moduleDoBootstrap(moduleRef);
|
||||
return moduleRef;
|
||||
});
|
||||
});
|
||||
});
|
||||
return bootstrap({moduleRef, allPlatformModules: this._modules});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,24 +109,6 @@ export class PlatformRef {
|
||||
);
|
||||
}
|
||||
|
||||
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
|
||||
const appRef = moduleRef.injector.get(ApplicationRef);
|
||||
if (moduleRef._bootstrapComponents.length > 0) {
|
||||
moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f));
|
||||
} else if (moduleRef.instance.ngDoBootstrap) {
|
||||
moduleRef.instance.ngDoBootstrap(appRef);
|
||||
} else {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND,
|
||||
ngDevMode &&
|
||||
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` +
|
||||
`but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
|
||||
`Please define one of these.`,
|
||||
);
|
||||
}
|
||||
this._modules.push(moduleRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to be called when the platform is destroyed.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user