[DURACOM-234] Switch to standalone bootstrapping API

This commit is contained in:
Giuseppe Digilio
2024-03-26 20:52:26 +01:00
parent 0c3e9b1535
commit a8c3af097d
10 changed files with 189 additions and 202 deletions

View File

@@ -48,7 +48,7 @@ import { hasNoValue, hasValue } from './src/app/shared/empty.util';
import { UIServerConfig } from './src/config/ui-server-config.interface';
import { ServerAppModule } from './src/main.server';
import bootstrap from './src/main.server';
import { buildAppConfig } from './src/config/config.server';
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
@@ -130,7 +130,7 @@ export function app() {
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', (_, options, callback) =>
ngExpressEngine({
bootstrap: ServerAppModule,
bootstrap,
providers: [
{
provide: REQUEST,
@@ -142,10 +142,10 @@ export function app() {
},
{
provide: APP_CONFIG,
useValue: environment
}
]
})(_, (options as any), callback)
useValue: environment,
},
],
})(_, (options as any), callback),
);
server.engine('ejs', ejs.renderFile);
@@ -162,7 +162,7 @@ export function app() {
server.get('/robots.txt', (req, res) => {
res.setHeader('content-type', 'text/plain');
res.render('assets/robots.txt.ejs', {
'origin': req.protocol + '://' + req.headers.host
'origin': req.protocol + '://' + req.headers.host,
});
});
@@ -177,7 +177,7 @@ export function app() {
router.use('/sitemap**', createProxyMiddleware({
target: `${environment.rest.baseUrl}/sitemaps`,
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
changeOrigin: true
changeOrigin: true,
}));
/**
@@ -186,7 +186,7 @@ export function app() {
router.use('/signposting**', createProxyMiddleware({
target: `${environment.rest.baseUrl}`,
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
changeOrigin: true
changeOrigin: true,
}));
/**
@@ -197,7 +197,7 @@ export function app() {
const RateLimit = require('express-rate-limit');
const limiter = new RateLimit({
windowMs: (environment.ui as UIServerConfig).rateLimiter.windowMs,
max: (environment.ui as UIServerConfig).rateLimiter.max
max: (environment.ui as UIServerConfig).rateLimiter.max,
});
server.use(limiter);
}
@@ -325,7 +325,7 @@ function initCache() {
botCache = new LRU( {
max: environment.cache.serverSide.botCache.max,
ttl: environment.cache.serverSide.botCache.timeToLive,
allowStale: environment.cache.serverSide.botCache.allowStale
allowStale: environment.cache.serverSide.botCache.allowStale,
});
}
@@ -337,7 +337,7 @@ function initCache() {
anonymousCache = new LRU( {
max: environment.cache.serverSide.anonymousCache.max,
ttl: environment.cache.serverSide.anonymousCache.timeToLive,
allowStale: environment.cache.serverSide.anonymousCache.allowStale
allowStale: environment.cache.serverSide.anonymousCache.allowStale,
});
}
}
@@ -415,7 +415,7 @@ function checkCacheForRequest(cacheName: string, cache: LRU<string, any>, req, r
const key = getCacheKey(req);
// Check if this page is in our cache
let cachedCopy = cache.get(key);
const cachedCopy = cache.get(key);
if (cachedCopy) {
if (environment.cache.serverSide.debug) { console.log(`CACHE HIT FOR ${key} in ${cacheName} cache`); }
@@ -529,20 +529,20 @@ function serverStarted() {
function createHttpsServer(keys) {
const listener = createServer({
key: keys.serviceKey,
cert: keys.certificate
cert: keys.certificate,
}, app).listen(environment.ui.port, environment.ui.host, () => {
serverStarted();
});
// Graceful shutdown when signalled
const terminator = createHttpTerminator({server: listener});
const terminator = createHttpTerminator({ server: listener });
process.on('SIGINT', () => {
void (async ()=> {
console.debug('Closing HTTPS server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTPS server closed');
})();
});
void (async ()=> {
console.debug('Closing HTTPS server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTPS server closed');
})();
});
}
/**
@@ -559,14 +559,14 @@ function run() {
});
// Graceful shutdown when signalled
const terminator = createHttpTerminator({server: listener});
const terminator = createHttpTerminator({ server: listener });
process.on('SIGINT', () => {
void (async () => {
console.debug('Closing HTTP server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTP server closed.');return undefined;
})();
});
void (async () => {
console.debug('Closing HTTP server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTP server closed.');return undefined;
})();
});
}
function start() {
@@ -597,7 +597,7 @@ function start() {
if (serviceKey && certificate) {
createHttpsServer({
serviceKey: serviceKey,
certificate: certificate
certificate: certificate,
});
} else {
console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.');
@@ -606,7 +606,7 @@ function start() {
createCertificate({
days: 1,
selfSigned: true
selfSigned: true,
}, (error, keys) => {
createHttpsServer(keys);
});
@@ -627,7 +627,7 @@ function healthCheck(req, res) {
})
.catch((error) => {
res.status(error.response.status).send({
error: error.message
error: error.message,
});
});
}

View File

@@ -35,6 +35,7 @@ import {
NativeWindowRef,
NativeWindowService,
} from './core/services/window.service';
import { ThemedRootComponent } from './root/themed-root.component';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowService } from './shared/host-window.service';
import { MenuService } from './shared/menu/menu.service';
@@ -84,7 +85,6 @@ describe('App component', () => {
},
}),
],
declarations: [AppComponent], // declare the test component
providers: [
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
{ provide: MetadataService, useValue: new MetadataServiceMock() },
@@ -109,7 +109,13 @@ describe('App component', () => {
// waitForAsync beforeEach
beforeEach(waitForAsync(() => {
return TestBed.configureTestingModule(getDefaultTestBedConf());
return TestBed.configureTestingModule(getDefaultTestBedConf()).overrideComponent(
AppComponent, {
remove: {
imports: [ ThemedRootComponent ],
},
},
);
}));
// synchronous beforeEach

View File

@@ -1,4 +1,5 @@
import {
AsyncPipe,
DOCUMENT,
isPlatformBrowser,
} from '@angular/common';
@@ -44,6 +45,7 @@ import {
NativeWindowService,
} from './core/services/window.service';
import { distinctNext } from './core/shared/distinct-next';
import { ThemedRootComponent } from './root/themed-root.component';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
import { CSSVariableService } from './shared/sass-helper/css-variable.service';
@@ -55,6 +57,11 @@ import { ThemeService } from './shared/theme-support/theme.service';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
ThemedRootComponent,
AsyncPipe,
],
})
export class AppComponent implements OnInit, AfterViewInit {
notificationOptions;

View File

@@ -1,15 +1,11 @@
import {
APP_BASE_HREF,
CommonModule,
DOCUMENT,
} from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import {
HTTP_INTERCEPTORS,
HttpClientModule,
} from '@angular/common/http';
import {
APP_ID,
NgModule,
ApplicationConfig,
importProvidersFrom,
} from '@angular/core';
import {
NoPreloading,
@@ -31,7 +27,6 @@ import {
StoreModule,
USER_PROVIDED_META_REDUCERS,
} from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
import { NgxMaskModule } from 'ngx-mask';
@@ -42,7 +37,6 @@ import {
import { StoreDevModules } from '../config/store/devtools';
import { environment } from '../environments/environment';
import { EagerThemesModule } from '../themes/eager-themes.module';
import { AppComponent } from './app.component';
import { appEffects } from './app.effects';
import {
appMetaReducers,
@@ -70,7 +64,6 @@ import { ClientCookieService } from './core/services/client-cookie.service';
import { ListableModule } from './core/shared/listable.module';
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
import { RootModule } from './root.module';
import { ThemedRootComponent } from './root/themed-root.component';
import { AUTH_METHOD_FOR_DECORATOR_MAP } from './shared/log-in/methods/log-in.methods-decorator';
import { METADATA_REPRESENTATION_COMPONENT_DECORATOR_MAP } from './shared/metadata-representation/metadata-representation.decorator';
import {
@@ -94,97 +87,79 @@ export function getMetaReducers(appConfig: AppConfig): MetaReducer<AppState>[] {
return appConfig.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
}
const IMPORTS = [
CommonModule,
HttpClientModule,
ScrollToModule.forRoot(),
NgbModule,
TranslateModule.forRoot(),
EffectsModule.forRoot(appEffects),
StoreModule.forRoot(appReducers, storeModuleConfig),
StoreRouterConnectingModule.forRoot(),
StoreDevModules,
EagerThemesModule,
RootModule,
ListableModule.withEntryComponents(),
];
const PROVIDERS = [
provideRouter(
APP_ROUTES,
withRouterConfig(APP_ROUTING_CONF),
withInMemoryScrolling(APP_ROUTING_SCROLL_CONF),
withEnabledBlockingInitialNavigation(),
withPreloading(NoPreloading),
),
{
provide: APP_BASE_HREF,
useFactory: getBaseHref,
deps: [DOCUMENT, APP_CONFIG],
},
{
provide: USER_PROVIDED_META_REDUCERS,
useFactory: getMetaReducers,
deps: [APP_CONFIG],
},
{
provide: RouterStateSerializer,
useClass: DSpaceRouterStateSerializer,
},
ClientCookieService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
// register LocaleInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: LocaleInterceptor,
multi: true,
},
// register XsrfInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: XsrfInterceptor,
multi: true,
},
// register LogInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: LogInterceptor,
multi: true,
},
// register the dynamic matcher used by form. MUST be provided by the app module
...DYNAMIC_MATCHER_PROVIDERS,
];
@NgModule({
declarations: [
AppComponent,
],
imports: [
...IMPORTS,
NgxMaskModule.forRoot(),
ThemedRootComponent,
],
export const commonAppConfig: ApplicationConfig = {
providers: [
...PROVIDERS,
{ provide: APP_ID, useValue: 'dspace-angular' },
importProvidersFrom(
ScrollToModule.forRoot(),
NgbModule,
// TranslateModule.forRoot(),
EffectsModule.forRoot(appEffects),
StoreModule.forRoot(appReducers, storeModuleConfig),
StoreRouterConnectingModule.forRoot(),
StoreDevModules,
EagerThemesModule,
RootModule,
ListableModule.withEntryComponents(),
NgxMaskModule.forRoot(),
),
provideRouter(
APP_ROUTES,
withRouterConfig(APP_ROUTING_CONF),
withInMemoryScrolling(APP_ROUTING_SCROLL_CONF),
withEnabledBlockingInitialNavigation(),
withPreloading(NoPreloading),
),
{
provide: APP_BASE_HREF,
useFactory: getBaseHref,
deps: [DOCUMENT, APP_CONFIG],
},
{
provide: USER_PROVIDED_META_REDUCERS,
useFactory: getMetaReducers,
deps: [APP_CONFIG],
},
{
provide: RouterStateSerializer,
useClass: DSpaceRouterStateSerializer,
},
ClientCookieService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
// register LocaleInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: LocaleInterceptor,
multi: true,
},
// register XsrfInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: XsrfInterceptor,
multi: true,
},
// register LogInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: LogInterceptor,
multi: true,
},
// register the dynamic matcher used by form. MUST be provided by the app module
...DYNAMIC_MATCHER_PROVIDERS,
provideCore(),
],
bootstrap: [AppComponent],
})
export class AppModule {
};
/* Use models object so all decorators are actually called */
modelList = models;
workflowTasks = WORKFLOW_TASK_OPTION_DECORATOR_MAP;
advancedWorfklowTasks = ADVANCED_WORKFLOW_TASK_OPTION_DECORATOR_MAP;
metadataRepresentations = METADATA_REPRESENTATION_COMPONENT_DECORATOR_MAP;
startsWithDecoratorMap = STARTS_WITH_DECORATOR_MAP;
browseByDecoratorMap = BROWSE_BY_DECORATOR_MAP;
authMethodForDecoratorMap = AUTH_METHOD_FOR_DECORATOR_MAP;
}
/* Use models object so all decorators are actually called */
const modelList = models;
const workflowTasks = WORKFLOW_TASK_OPTION_DECORATOR_MAP;
const advancedWorfklowTasks = ADVANCED_WORKFLOW_TASK_OPTION_DECORATOR_MAP;
const metadataRepresentations = METADATA_REPRESENTATION_COMPONENT_DECORATOR_MAP;
const startsWithDecoratorMap = STARTS_WITH_DECORATOR_MAP;
const browseByDecoratorMap = BROWSE_BY_DECORATOR_MAP;
const authMethodForDecoratorMap = AUTH_METHOD_FOR_DECORATOR_MAP;

View File

@@ -3,15 +3,17 @@ import 'reflect-metadata';
import 'core-js/es/reflect';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { AppConfig } from './config/app-config.interface';
import { extendEnvironmentWithAppConfig } from './config/config.util';
import { environment } from './environments/environment';
import { BrowserAppModule } from './modules/app/browser-app.module';
import { browserAppConfig } from './modules/app/browser-app.config';
const bootstrap = () => platformBrowserDynamic()
.bootstrapModule(BrowserAppModule, {});
/*const bootstrap = () => platformBrowserDynamic()
.bootstrapModule(BrowserAppModule, {});*/
const bootstrap = () => bootstrapApplication(AppComponent, browserAppConfig);
/**
* We use this to determine have been serven SSR HTML or not.
@@ -33,9 +35,9 @@ const main = () => {
// Configuration must be fetched explicitly
return fetch('assets/config.json')
.then((response) => response.json())
.then((appConfig: AppConfig) => {
.then((config: AppConfig) => {
// extend environment with app config for browser when not prerendered
extendEnvironmentWithAppConfig(environment, appConfig);
extendEnvironmentWithAppConfig(environment, config);
return bootstrap();
});
}

View File

@@ -7,14 +7,13 @@ import 'reflect-metadata';
*/
import '@angular/localize/init';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { environment } from './environments/environment';
import { AppComponent } from './app/app.component';
import { serverAppConfig } from './modules/app/server-app.config';
if (environment.production) {
enableProdMode();
}
const bootstrap = () => bootstrapApplication(AppComponent, serverAppConfig);
export { ServerAppModule } from './modules/app/server-app.module';
export { renderModule } from '@angular/platform-server';
export { ngExpressEngine } from '@nguniversal/express-engine';
export default bootstrap;

View File

@@ -1,14 +1,18 @@
import {
HttpClient,
HttpClientModule,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
APP_ID,
ApplicationConfig,
importProvidersFrom,
makeStateKey,
NgModule,
mergeApplicationConfig,
TransferState,
} from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { EffectsModule } from '@ngrx/effects';
import {
Action,
@@ -26,8 +30,7 @@ import {
Angulartics2RouterlessModule,
} from 'angulartics2';
import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module';
import { commonAppConfig } from '../../app/app.config';
import { storeModuleConfig } from '../../app/app.reducer';
import { AuthService } from '../../app/core/auth/auth.service';
import { AuthRequestService } from '../../app/core/auth/auth-request.service';
@@ -66,27 +69,26 @@ export function getRequest(transferState: TransferState): any {
return transferState.get<any>(REQ_KEY, {});
}
@NgModule({
bootstrap: [AppComponent],
imports: [
HttpClientModule,
// forRoot ensures the providers are only created once
Angulartics2RouterlessModule.forRoot(),
BrowserAnimationsModule,
StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig<CoreState, Action>),
EffectsModule.forFeature(coreEffects),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [TransferState, HttpClient],
},
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper },
useDefaultLang: true,
}),
AppModule,
],
export const browserAppConfig: ApplicationConfig = mergeApplicationConfig({
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideAnimations(),
provideClientHydration(),
importProvidersFrom(
// forRoot ensures the providers are only created once
Angulartics2RouterlessModule.forRoot(),
StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig<CoreState, Action>),
EffectsModule.forFeature(coreEffects),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [TransferState, HttpClient],
},
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper },
useDefaultLang: true,
}),
),
...BrowserInitService.providers(),
{ provide: APP_ID, useValue: 'dspace-angular' },
{
@@ -143,6 +145,4 @@ export function getRequest(transferState: TransferState): any {
useClass: ClientMathService,
},
],
})
export class BrowserAppModule {
}
}, commonAppConfig);

View File

@@ -1,12 +1,18 @@
import { XhrFactory } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import {
HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
APP_ID,
NgModule,
ApplicationConfig,
importProvidersFrom,
mergeApplicationConfig,
TransferState,
} from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ServerModule } from '@angular/platform-server';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideServerRendering } from '@angular/platform-server';
import { EffectsModule } from '@ngrx/effects';
import {
Action,
@@ -23,8 +29,7 @@ import {
Angulartics2GoogleGlobalSiteTag,
} from 'angulartics2';
import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module';
import { commonAppConfig } from '../../app/app.config';
import { storeModuleConfig } from '../../app/app.reducer';
import { AuthService } from '../../app/core/auth/auth.service';
import { AuthRequestService } from '../../app/core/auth/auth-request.service';
@@ -57,23 +62,22 @@ export function createTranslateLoader(transferState: TransferState) {
return new TranslateServerLoader(transferState, 'dist/server/assets/i18n/', '.json');
}
@NgModule({
bootstrap: [AppComponent],
imports: [
NoopAnimationsModule,
StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig<CoreState, Action>),
EffectsModule.forFeature(coreEffects),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [TransferState],
},
}),
AppModule,
ServerModule,
],
export const serverAppConfig: ApplicationConfig = mergeApplicationConfig({
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideAnimations(),
provideServerRendering(),
importProvidersFrom(
StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig<CoreState, Action>),
EffectsModule.forFeature(coreEffects),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [TransferState],
},
}),
),
...ServerInitService.providers(),
{ provide: APP_ID, useValue: 'dspace-angular' },
{
@@ -135,6 +139,4 @@ export function createTranslateLoader(transferState: TransferState) {
useClass: ServerMathService,
},
],
})
export class ServerAppModule {
}
}, commonAppConfig);

View File

@@ -11,7 +11,6 @@ import { StoreModule } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
import { AppModule } from '../../app/app.module';
import { RootModule } from '../../app/root.module';
import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component';
import { EditBitstreamPageComponent } from './app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component';
@@ -185,7 +184,6 @@ const DECLARATIONS = [
@NgModule({
imports: [
AppModule,
RootModule,
CommonModule,
DragDropModule,

View File

@@ -10,7 +10,6 @@ import { StoreModule } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
import { AppModule } from '../../app/app.module';
import { RootModule } from '../../app/root.module';
const DECLARATIONS = [
@@ -18,7 +17,6 @@ const DECLARATIONS = [
@NgModule({
imports: [
AppModule,
RootModule,
CommonModule,
DragDropModule,