Merge remote-tracking branch 'remotes/origin/master' into authentication

This commit is contained in:
Giuseppe Digilio
2018-02-09 09:56:50 +01:00
46 changed files with 2492 additions and 1263 deletions

View File

@@ -29,10 +29,6 @@ module.exports = {
}, },
// Log directory // Log directory
logDirectory: '.', logDirectory: '.',
// NOTE: rehydrate or replay
// rehydrate will transfer prerender state to browser state, actions do not need to replay
// replay will transfer an array of actions to browser, actions replay automatically
prerenderStrategy: 'rehydrate',
// NOTE: will log all redux actions and transfers in console // NOTE: will log all redux actions and transfers in console
debug: false debug: false
}; };

View File

@@ -8,7 +8,7 @@
}, },
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=5.0.0" "node": ">=6.0.0"
}, },
"scripts": { "scripts": {
"global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup", "global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup",
@@ -69,135 +69,135 @@
"coverage": "http-server -c-1 -o -p 9875 ./coverage" "coverage": "http-server -c-1 -o -p 9875 ./coverage"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "4.4.5", "@angular/animations": "5.2.1",
"@angular/common": "4.4.5", "@angular/common": "5.2.1",
"@angular/core": "4.4.5", "@angular/core": "5.2.1",
"@angular/forms": "4.4.5", "@angular/forms": "5.2.1",
"@angular/http": "4.4.5", "@angular/http": "5.2.1",
"@angular/platform-browser": "4.4.5", "@angular/platform-browser": "5.2.1",
"@angular/platform-browser-dynamic": "4.4.5", "@angular/platform-browser-dynamic": "5.2.1",
"@angular/platform-server": "4.4.5", "@angular/platform-server": "5.2.1",
"@angular/router": "4.4.5", "@angular/router": "5.2.1",
"@angularclass/bootloader": "1.0.1", "@angularclass/bootloader": "1.0.1",
"@angularclass/idle-preload": "1.0.4", "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.9",
"@ng-bootstrap/ng-bootstrap": "1.0.0-beta.5", "@ngrx/effects": "4.1.1",
"@ngrx/effects": "4.0.5", "@ngrx/router-store": "4.1.1",
"@ngrx/router-store": "4.0.4", "@ngrx/store": "4.1.1",
"@ngrx/store": "4.0.3", "@nguniversal/express-engine": "5.0.0-beta.5",
"@nguniversal/express-engine": "1.0.0-beta.3", "@ngx-translate/core": "9.1.1",
"@ngx-translate/core": "8.0.0", "@ngx-translate/http-loader": "2.0.1",
"@ngx-translate/http-loader": "2.0.0", "angular-idle-preload": "2.0.4",
"body-parser": "1.18.2", "body-parser": "1.18.2",
"bootstrap": "v4.0.0-beta", "bootstrap": "4.0.0-beta",
"cerialize": "0.1.18", "cerialize": "0.1.18",
"compression": "1.7.1", "compression": "1.7.1",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"core-js": "2.5.1", "core-js": "2.5.3",
"express": "4.16.2", "express": "4.16.2",
"express-session": "1.15.6", "express-session": "1.15.6",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"http-server": "0.10.0", "http-server": "0.11.1",
"https": "1.0.0", "https": "1.0.0",
"js.clone": "0.0.3", "js.clone": "0.0.3",
"jsonschema": "1.2.0", "jsonschema": "1.2.2",
"methods": "1.1.2", "methods": "1.1.2",
"morgan": "1.9.0", "morgan": "1.9.0",
"ngx-pagination": "3.0.1", "ngx-pagination": "3.0.3",
"pem": "1.12.3", "pem": "1.12.3",
"reflect-metadata": "0.1.10", "reflect-metadata": "0.1.12",
"rxjs": "5.4.3", "rxjs": "5.5.6",
"ts-md5": "1.2.2", "ts-md5": "1.2.3",
"uuid": "^3.1.0", "uuid": "^3.2.1",
"webfontloader": "1.6.28", "webfontloader": "1.6.28",
"zone.js": "0.8.18" "zone.js": "0.8.20"
}, },
"devDependencies": { "devDependencies": {
"@angular/compiler": "4.4.5", "@angular/compiler": "^5.2.1",
"@angular/compiler-cli": "4.4.5", "@angular/compiler-cli": "^5.2.1",
"@ngrx/store-devtools": "4.0.0", "@ngrx/store-devtools": "4.1.1",
"@ngtools/webpack": "1.7.4", "@ngtools/webpack": "1.9.5",
"@types/cookie-parser": "1.4.1", "@types/cookie-parser": "1.4.1",
"@types/deep-freeze": "0.1.1", "@types/deep-freeze": "0.1.1",
"@types/express": "4.0.37", "@types/express": "4.11.0",
"@types/express-serve-static-core": "4.0.53", "@types/express-serve-static-core": "4.11.1",
"@types/hammerjs": "2.0.35", "@types/hammerjs": "2.0.35",
"@types/jasmine": "2.6.0", "@types/jasmine": "2.8.4",
"@types/memory-cache": "0.0.31", "@types/memory-cache": "0.2.0",
"@types/mime": "2.0.0", "@types/mime": "2.0.0",
"@types/node": "8.0.34", "@types/node": "^9.3.0",
"@types/serve-static": "1.7.32", "@types/serve-static": "1.13.1",
"@types/source-map": "0.5.1",
"@types/uuid": "^3.4.3", "@types/uuid": "^3.4.3",
"@types/webfontloader": "1.6.29", "@types/webfontloader": "1.6.29",
"ajv": "5.2.3", "ajv": "6.0.1",
"ajv-keywords": "2.1.0", "ajv-keywords": "3.0.0",
"angular2-template-loader": "0.6.2", "angular2-template-loader": "0.6.2",
"autoprefixer": "7.1.5", "autoprefixer": "7.2.5",
"awesome-typescript-loader": "3.2.3", "awesome-typescript-loader": "3.4.1",
"caniuse-lite": "1.0.30000746", "caniuse-lite": "1.0.30000792",
"codelyzer": "3.2.1", "codelyzer": "^4.1.0",
"compression-webpack-plugin": "1.0.1", "compression-webpack-plugin": "1.1.3",
"copy-webpack-plugin": "4.1.1", "copy-webpack-plugin": "4.3.1",
"coveralls": "3.0.0", "coveralls": "3.0.0",
"css-loader": "0.28.7", "css-loader": "0.28.9",
"deep-freeze": "0.0.1", "deep-freeze": "0.0.1",
"exports-loader": "0.6.4", "exports-loader": "0.6.4",
"html-webpack-plugin": "2.30.1", "html-webpack-plugin": "2.30.1",
"imports-loader": "0.7.1", "imports-loader": "0.7.1",
"istanbul-instrumenter-loader": "3.0.0", "istanbul-instrumenter-loader": "3.0.0",
"jasmine-core": "2.8.0", "jasmine-core": "2.9.1",
"jasmine-marbles": "0.2.0", "jasmine-marbles": "0.2.0",
"jasmine-spec-reporter": "4.2.1", "jasmine-spec-reporter": "4.2.1",
"json-loader": "0.5.7", "json-loader": "0.5.7",
"karma": "1.7.1", "karma": "2.0.0",
"karma-chrome-launcher": "2.2.0", "karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1", "karma-cli": "1.0.1",
"karma-coverage": "1.1.1", "karma-coverage": "1.1.1",
"karma-istanbul-preprocessor": "0.0.2", "karma-istanbul-preprocessor": "0.0.2",
"karma-jasmine": "1.1.0", "karma-jasmine": "1.1.1",
"karma-mocha-reporter": "2.2.4", "karma-mocha-reporter": "2.2.5",
"karma-phantomjs-launcher": "1.0.4", "karma-phantomjs-launcher": "1.0.4",
"karma-remap-coverage": "0.1.4", "karma-remap-coverage": "0.1.4",
"karma-remap-istanbul": "0.6.0", "karma-remap-istanbul": "0.6.0",
"karma-sourcemap-loader": "0.3.7", "karma-sourcemap-loader": "0.3.7",
"karma-webdriver-launcher": "1.0.5", "karma-webdriver-launcher": "1.0.5",
"karma-webpack": "2.0.5", "karma-webpack": "2.0.9",
"ngrx-store-freeze": "0.2.0", "ngrx-store-freeze": "0.2.0",
"node-sass": "4.5.3", "node-sass": "4.7.2",
"nodemon": "1.12.1", "nodemon": "1.14.11",
"npm-run-all": "4.1.1", "npm-run-all": "4.1.2",
"postcss": "6.0.13", "postcss": "6.0.16",
"postcss-apply": "0.8.0", "postcss-apply": "0.8.0",
"postcss-cli": "4.1.1", "postcss-cli": "4.1.1",
"postcss-cssnext": "3.0.2", "postcss-cssnext": "3.1.0",
"postcss-loader": "2.0.7", "postcss-loader": "2.0.10",
"postcss-responsive-type": "1.0.0", "postcss-responsive-type": "1.0.0",
"postcss-smart-import": "0.7.5", "postcss-smart-import": "0.7.6",
"protractor": "5.1.2", "protractor": "5.2.2",
"protractor-istanbul-plugin": "2.0.0", "protractor-istanbul-plugin": "2.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"resolve-url-loader": "2.1.1", "resolve-url-loader": "2.2.1",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"rollup": "0.50.0", "rollup": "0.54.1",
"rollup-plugin-commonjs": "8.2.1", "rollup-plugin-commonjs": "8.2.6",
"rollup-plugin-node-globals": "1.1.0", "rollup-plugin-node-globals": "1.1.0",
"rollup-plugin-node-resolve": "3.0.0", "rollup-plugin-node-resolve": "3.0.2",
"rollup-plugin-uglify": "2.0.1", "rollup-plugin-uglify": "3.0.0",
"sass-loader": "6.0.6", "sass-loader": "6.0.6",
"script-ext-html-webpack-plugin": "1.8.5", "script-ext-html-webpack-plugin": "1.8.8",
"source-map-loader": "0.2.2", "source-map": "0.6.1",
"source-map-loader": "0.2.3",
"string-replace-loader": "1.3.0", "string-replace-loader": "1.3.0",
"to-string-loader": "1.1.5", "to-string-loader": "1.1.5",
"ts-helpers": "1.1.2", "ts-helpers": "1.1.2",
"ts-node": "3.3.0", "ts-node": "4.1.0",
"tslint": "5.7.0", "tslint": "5.9.1",
"typedoc": "0.9.0", "typedoc": "0.9.0",
"typescript": "2.5.3", "typescript": "2.6.2",
"webpack": "3.7.1", "webpack": "^3.10.0",
"webpack-bundle-analyzer": "2.9.0", "webpack-bundle-analyzer": "2.9.2",
"webpack-dev-middleware": "1.12.0", "webpack-dev-middleware": "2.0.4",
"webpack-dev-server": "2.9.1", "webpack-dev-server": "2.11.1",
"webpack-merge": "4.1.0", "webpack-merge": "4.1.1",
"webpack-node-externals": "1.6.0" "webpack-node-externals": "1.6.0"
} }
} }

View File

@@ -1,22 +1,22 @@
import nodeResolve from 'rollup-plugin-node-resolve' import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify' import uglify from 'rollup-plugin-uglify'
export default { export default {
input: 'dist/client.js', input: 'dist/client.js',
output: { output: {
file: 'dist/client.js', file: 'dist/client.js',
format: 'iife', format: 'iife',
}, },
sourceMap: false, sourcemap: false,
plugins: [ plugins: [
nodeResolve({ nodeResolve({
jsnext: true, jsnext: true,
module: true module: true
}), }),
commonjs({ commonjs({
include: 'node_modules/rxjs/**' include: 'node_modules/rxjs/**'
}), }),
uglify() uglify()
] ]
} }

View File

@@ -23,9 +23,6 @@ import { AppComponent } from './app.component';
import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowState } from './shared/host-window.reducer';
import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowResizeAction } from './shared/host-window.actions';
import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module';
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
import { MetadataService } from './core/metadata/metadata.service'; import { MetadataService } from './core/metadata/metadata.service';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
@@ -53,8 +50,6 @@ describe('App component', () => {
useClass: MockTranslateLoader useClass: MockTranslateLoader
} }
}), }),
BrowserTransferStateModule,
BrowserTransferStoreModule
], ],
declarations: [AppComponent], // declare the test component declarations: [AppComponent], // declare the test component
providers: [ providers: [

View File

@@ -13,7 +13,6 @@ import { TranslateService } from '@ngx-translate/core';
import { GLOBAL_CONFIG, GlobalConfig } from '../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { TransferState } from '../modules/transfer-state/transfer-state';
import { MetadataService } from './core/metadata/metadata.service'; import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowState } from './shared/host-window.reducer';
@@ -32,7 +31,6 @@ export class AppComponent implements OnInit {
@Inject(GLOBAL_CONFIG) public config: GlobalConfig, @Inject(GLOBAL_CONFIG) public config: GlobalConfig,
@Inject(NativeWindowService) private _window: NativeWindowRef, @Inject(NativeWindowService) private _window: NativeWindowRef,
private translate: TranslateService, private translate: TranslateService,
private cache: TransferState,
private store: Store<HostWindowState>, private store: Store<HostWindowState>,
private metadata: MetadataService private metadata: MetadataService
) { ) {
@@ -48,16 +46,6 @@ export class AppComponent implements OnInit {
} }
} }
ngAfterViewChecked() {
this.syncCache();
}
syncCache() {
this.store.take(1).subscribe((state: HostWindowState) => {
this.cache.set('state', state);
});
}
ngOnInit() { ngOnInit() {
const env: string = this.config.production ? 'Production' : 'Development'; const env: string = this.config.production ? 'Production' : 'Development';
const color: string = this.config.production ? 'red' : 'green'; const color: string = this.config.production ? 'red' : 'green';

View File

@@ -6,13 +6,11 @@ let actionCounter = 0;
export function debugMetaReducer(reducer) { export function debugMetaReducer(reducer) {
return (state, action) => { return (state, action) => {
if (isNotEmpty(console.debug)) { actionCounter++;
actionCounter++; console.log('@ngrx action', actionCounter, action.type);
console.debug('@ngrx action', actionCounter, action.type); console.log('state', JSON.stringify(state));
console.debug('state', state); console.log('action', JSON.stringify(action));
console.debug('action', action); console.log('------------------------------------');
console.debug('------------------------------------');
}
return reducer(state, action); return reducer(state, action);
} }
} }

View File

@@ -1,31 +1,32 @@
import { NgModule } from '@angular/core'; import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects'; import { BrowserTransferStateModule } from '@angular/platform-browser';
import { StoreModule, MetaReducer, META_REDUCERS } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
import { storeFreeze } from 'ngrx-store-freeze';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { appEffects } from './app.effects'; import { EffectsModule } from '@ngrx/effects';
import { appReducers, AppState } from './app.reducer'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
import { appMetaReducers, debugMetaReducers } from './app.metareducers'; import { META_REDUCERS, MetaReducer, StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { CoreModule } from './core/core.module'; import { TranslateModule } from '@ngx-translate/core';
import { storeFreeze } from 'ngrx-store-freeze';
import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../config';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { FooterComponent } from './footer/footer.component';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { GLOBAL_CONFIG, ENV_CONFIG, GlobalConfig } from '../config'; import { appEffects } from './app.effects';
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
import { appReducers, AppState } from './app.reducer';
import { CoreModule } from './core/core.module';
import { FooterComponent } from './footer/footer.component';
import { HeaderComponent } from './header/header.component';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer'; import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';

View File

@@ -52,7 +52,7 @@ describe('CollectionGridElementComponent', () => {
if (mockCollection.shortDescription.length > 0) { if (mockCollection.shortDescription.length > 0) {
expect(descriptionText).toBeDefined(); expect(descriptionText).toBeDefined();
}else { } else {
expect(descriptionText).not.toBeDefined(); expect(descriptionText).not.toBeDefined();
} }
}); });

View File

@@ -59,7 +59,7 @@ describe('CommunityGridElementComponent', () => {
if (mockCommunity.shortDescription.length > 0) { if (mockCommunity.shortDescription.length > 0) {
expect(descriptionText).toBeDefined(); expect(descriptionText).toBeDefined();
}else { } else {
expect(descriptionText).not.toBeDefined(); expect(descriptionText).not.toBeDefined();
} }
}); });

View File

@@ -59,7 +59,7 @@ describe('ItemGridElementComponent', () => {
if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) { if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) {
expect(itemAuthorField).toBeDefined(); expect(itemAuthorField).toBeDefined();
}else { } else {
expect(itemAuthorField).toBeDefined(); expect(itemAuthorField).toBeDefined();
} }
}); });

View File

@@ -57,7 +57,7 @@ describe('CollectionSearchResultGridElementComponent', () => {
if (mockCollection.shortDescription.length > 0) { if (mockCollection.shortDescription.length > 0) {
expect(descriptionText).toBeDefined(); expect(descriptionText).toBeDefined();
}else { } else {
expect(descriptionText).not.toBeDefined(); expect(descriptionText).not.toBeDefined();
} }
}); });

View File

@@ -56,7 +56,7 @@ describe('CommunitySearchResultGridElementComponent', () => {
if (mockCommunity.shortDescription.length > 0) { if (mockCommunity.shortDescription.length > 0) {
expect(descriptionText).toBeDefined(); expect(descriptionText).toBeDefined();
}else { } else {
expect(descriptionText).not.toBeDefined(); expect(descriptionText).not.toBeDefined();
} }
}); });

View File

@@ -62,7 +62,7 @@ describe('ItemSearchResultGridElementComponent', () => {
if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) { if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) {
expect(itemAuthorField).toBeDefined(); expect(itemAuthorField).toBeDefined();
}else { } else {
expect(itemAuthorField).not.toBeDefined(); expect(itemAuthorField).not.toBeDefined();
} }
}); });
@@ -72,7 +72,7 @@ describe('ItemSearchResultGridElementComponent', () => {
if (mockItem.findMetadata('dc.date.issued').length > 0) { if (mockItem.findMetadata('dc.date.issued').length > 0) {
expect(dateField).toBeDefined(); expect(dateField).toBeDefined();
}else { } else {
expect(dateField).not.toBeDefined(); expect(dateField).not.toBeDefined();
} }
}); });

View File

@@ -1,8 +1,8 @@
import { Component, Input, Injector, ReflectiveInjector, OnInit } from '@angular/core'; import { Component, Injector, Input, OnInit } from '@angular/core';
import { ViewMode } from '../../../+search-page/search-options.model';
import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { GenericConstructor } from '../../../core/shared/generic-constructor';
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'; import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator';
import { ListableObject } from '../../object-collection/shared/listable-object.model'; import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { ViewMode } from '../../../+search-page/search-options.model';
@Component({ @Component({
selector: 'ds-wrapper-grid-element', selector: 'ds-wrapper-grid-element',
@@ -13,11 +13,14 @@ export class WrapperGridElementComponent implements OnInit {
@Input() object: ListableObject; @Input() object: ListableObject;
objectInjector: Injector; objectInjector: Injector;
constructor(private injector: Injector) {} constructor(private injector: Injector) {
}
ngOnInit(): void { ngOnInit(): void {
this.objectInjector = ReflectiveInjector.resolveAndCreate( this.objectInjector = Injector.create({
[{provide: 'objectElementProvider', useFactory: () => (this.object) }], this.injector); providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }],
parent: this.injector
});
} }

View File

@@ -1,8 +1,8 @@
import { Component, Input, Injector, ReflectiveInjector, OnInit } from '@angular/core'; import { Component, Injector, Input, OnInit } from '@angular/core';
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'
import { GenericConstructor } from '../../../core/shared/generic-constructor';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { ViewMode } from '../../../+search-page/search-options.model'; import { ViewMode } from '../../../+search-page/search-options.model';
import { GenericConstructor } from '../../../core/shared/generic-constructor';
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'
import { ListableObject } from '../../object-collection/shared/listable-object.model';
@Component({ @Component({
selector: 'ds-wrapper-list-element', selector: 'ds-wrapper-list-element',
@@ -16,8 +16,10 @@ export class WrapperListElementComponent implements OnInit {
constructor(private injector: Injector) {} constructor(private injector: Injector) {}
ngOnInit(): void { ngOnInit(): void {
this.objectInjector = ReflectiveInjector.resolveAndCreate( this.objectInjector = Injector.create({
[{provide: 'objectElementProvider', useFactory: () => (this.object) }], this.injector); providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }],
parent: this.injector
});
} }
getListElement(): string { getListElement(): string {

View File

@@ -1,6 +1,6 @@
import { OpaqueToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
export const NativeWindowService = new OpaqueToken('NativeWindowService'); export const NativeWindowService = new InjectionToken('NativeWindowService');
export class NativeWindowRef { export class NativeWindowRef {
get nativeWindow(): any { get nativeWindow(): any {

View File

@@ -10,6 +10,5 @@ export interface GlobalConfig extends Config {
cache: CacheConfig; cache: CacheConfig;
universal: UniversalConfig; universal: UniversalConfig;
logDirectory: string; logDirectory: string;
prerenderStrategy: string;
debug: boolean; debug: boolean;
} }

View File

@@ -1,31 +1,31 @@
import 'zone.js/dist/zone'; import 'zone.js/dist/zone';
import 'reflect-metadata'; import 'reflect-metadata';
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { bootloader } from '@angularclass/bootloader'; import { bootloader } from '@angularclass/bootloader';
import { load as loadWebFont } from 'webfontloader'; import { load as loadWebFont } from 'webfontloader';
import { BrowserAppModule } from './modules/app/browser-app.module'; import { BrowserAppModule } from './modules/app/browser-app.module';
import { ENV_CONFIG } from './config'; import { ENV_CONFIG } from './config';
if (ENV_CONFIG.production) { if (ENV_CONFIG.production) {
enableProdMode(); enableProdMode();
} }
export function main() { export function main() {
// Load fonts async // Load fonts async
// https://github.com/typekit/webfontloader#configuration // https://github.com/typekit/webfontloader#configuration
loadWebFont({ loadWebFont({
google: { google: {
families: ['Droid Sans'] families: ['Droid Sans']
} }
}); });
return platformBrowserDynamic().bootstrapModule(BrowserAppModule); return platformBrowserDynamic().bootstrapModule(BrowserAppModule);
} }
// support async tag or hmr // support async tag or hmr
bootloader(main); document.addEventListener('DOMContentLoaded', () => bootloader(main));

4
src/main.server.aot.ts Normal file
View File

@@ -0,0 +1,4 @@
import { startServer } from './server';
import { ServerAppModuleNgFactory } from './modules/app/server-app.module.ngfactory';
startServer(ServerAppModuleNgFactory);

View File

@@ -1,134 +1,4 @@
import 'zone.js/dist/zone-node'; import { startServer } from './server';
import 'reflect-metadata';
import 'rxjs/Rx';
import * as fs from 'fs';
import * as pem from 'pem';
import * as https from 'https';
import * as morgan from 'morgan';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as compression from 'compression';
import * as cookieParser from 'cookie-parser';
import { enableProdMode } from '@angular/core';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { ServerAppModule } from './modules/app/server-app.module'; import { ServerAppModule } from './modules/app/server-app.module';
import { ROUTES } from './routes'; startServer(ServerAppModule);
import { ENV_CONFIG } from './config';
const app = express();
const port = ENV_CONFIG.ui.port ? ENV_CONFIG.ui.port : 80;
if (ENV_CONFIG.production) {
enableProdMode();
app.use(compression());
}
app.use(morgan('dev'));
app.use(cookieParser());
app.use(bodyParser.json());
app.engine('html', ngExpressEngine({
bootstrap: ServerAppModule
}));
app.set('view engine', 'html');
app.set('views', 'src');
function cacheControl(req, res, next) {
// instruct browser to revalidate
res.header('Cache-Control', ENV_CONFIG.cache.control || 'max-age=60');
next();
}
app.use('/', cacheControl, express.static('dist', { index: false }));
// TODO: either remove or update mock backend
// app.get('/data.json', serverApi);
// app.use('/api', createMockApi());
function ngApp(req, res) {
function onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
console.warn('Error in SSR, serving for direct CSR');
res.sendFile('index.csr.html', { root: './src' });
}
if (ENV_CONFIG.universal.preboot) {
Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => {
res.render('../dist/index', {
req,
res,
preboot: ENV_CONFIG.universal.preboot,
async: ENV_CONFIG.universal.async,
time: ENV_CONFIG.universal.time,
baseUrl: ENV_CONFIG.ui.nameSpace,
originUrl: ENV_CONFIG.ui.baseUrl,
requestUrl: req.originalUrl
});
});
} else {
console.log('Universal off, serving for direct CSR');
res.sendFile('index.csr.html', { root: './src' });
}
}
ROUTES.forEach((route: string) => {
app.get(route, ngApp);
});
function serverStarted() {
console.log(`[${new Date().toTimeString()}] Listening at ${ENV_CONFIG.ui.baseUrl}`);
}
function createHttpsServer(keys) {
https.createServer({
key: keys.serviceKey,
cert: keys.certificate
}, app).listen(port, ENV_CONFIG.ui.host, () => {
serverStarted();
});
}
if (ENV_CONFIG.ui.ssl) {
let serviceKey;
try {
serviceKey = fs.readFileSync('./config/ssl/key.pem');
} catch (e) {
console.warn('Service key not found at ./config/ssl/key.pem');
}
let certificate;
try {
certificate = fs.readFileSync('./config/ssl/cert.pem');
} catch (e) {
console.warn('Certificate not found at ./config/ssl/key.pem');
}
if (serviceKey && certificate) {
createHttpsServer({
serviceKey: serviceKey,
certificate: certificate
});
} else {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
pem.createCertificate({
days: 1,
selfSigned: true
}, (error, keys) => {
createHttpsServer(keys);
});
}
} else {
app.listen(port, ENV_CONFIG.ui.host, () => {
serverStarted();
});
}

View File

@@ -1,30 +1,19 @@
import { HttpClient, HttpClientModule } from '@angular/common/http'; import { HttpClient, HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
import { EffectsModule } from '@ngrx/effects';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { IdlePreload, IdlePreloadModule } from 'angular-idle-preload';
import { AppComponent } from '../../app/app.component'; import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module'; import { AppModule } from '../../app/app.module';
import { BrowserTransferStateModule } from '../transfer-state/browser-transfer-state.module'; import { DSpaceBrowserTransferStateModule } from '../transfer-state/dspace-browser-transfer-state.module';
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
import { TransferState } from '../transfer-state/transfer-state';
import { BrowserTransferStoreEffects } from '../transfer-store/browser-transfer-store.effects';
import { BrowserTransferStoreModule } from '../transfer-store/browser-transfer-store.module';
export function init(cache: TransferState) {
return () => {
cache.initialize();
};
}
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
@@ -34,7 +23,7 @@ export function createTranslateLoader(http: HttpClient) {
bootstrap: [AppComponent], bootstrap: [AppComponent],
imports: [ imports: [
BrowserModule.withServerTransition({ BrowserModule.withServerTransition({
appId: 'ds-app-id' appId: 'dspace-angular'
}), }),
HttpClientModule, HttpClientModule,
// forRoot ensures the providers are only created once // forRoot ensures the providers are only created once
@@ -46,8 +35,7 @@ export function createTranslateLoader(http: HttpClient) {
IdlePreload IdlePreload
}), }),
BrowserAnimationsModule, BrowserAnimationsModule,
BrowserTransferStateModule, DSpaceBrowserTransferStateModule,
BrowserTransferStoreModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@@ -55,20 +43,13 @@ export function createTranslateLoader(http: HttpClient) {
deps: [HttpClient] deps: [HttpClient]
} }
}), }),
EffectsModule.forRoot([BrowserTransferStoreEffects]),
AppModule AppModule
], ],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: init,
deps: [
TransferState
]
}
]
}) })
export class BrowserAppModule { export class BrowserAppModule {
constructor(
private transferState: DSpaceTransferState,
) {
this.transferState.transfer();
}
} }

View File

@@ -1,53 +1,20 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ServerModule } from '@angular/platform-server';
import { RouterModule } from '@angular/router';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first'; import 'rxjs/add/operator/first';
import { ApplicationRef, NgModule, APP_BOOTSTRAP_LISTENER } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { Store } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { TranslateUniversalLoader } from '../translate-universal-loader';
import { ServerTransferStateModule } from '../transfer-state/server-transfer-state.module';
import { TransferState } from '../transfer-state/transfer-state';
import { ServerTransferStoreEffects } from '../transfer-store/server-transfer-store.effects';
import { ServerTransferStoreModule } from '../transfer-store/server-transfer-store.module';
import { AppState } from '../../app/app.reducer';
import { AppModule } from '../../app/app.module';
import { AppComponent } from '../../app/app.component'; import { AppComponent } from '../../app/app.component';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config'; import { AppModule } from '../../app/app.module';
import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server-transfer-state.module';
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) { import { TranslateUniversalLoader } from '../translate-universal-loader';
// authentication mechanism goes here
return () => {
appRef.isStable.filter((stable: boolean) => stable).first().subscribe(() => {
// isStable == true doesn't guarantee that all dispatched actions have been
// processed yet. So in those cases the store snapshot wouldn't be complete
// and a rehydrate would leave the app in a broken state
//
// This setTimeout without delay schedules the cache.inject() to happen ASAP
// after everything that's already scheduled, and it solves that problem.
setTimeout(() => {
cache.inject();
}, 0);
});
};
}
export function createTranslateLoader() { export function createTranslateLoader() {
return new TranslateUniversalLoader('dist/assets/i18n/', '.json'); return new TranslateUniversalLoader('dist/assets/i18n/', '.json');
@@ -57,14 +24,13 @@ export function createTranslateLoader() {
bootstrap: [AppComponent], bootstrap: [AppComponent],
imports: [ imports: [
BrowserModule.withServerTransition({ BrowserModule.withServerTransition({
appId: 'ds-app-id' appId: 'dspace-angular'
}), }),
RouterModule.forRoot([], { RouterModule.forRoot([], {
useHash: false useHash: false
}), }),
NoopAnimationsModule, NoopAnimationsModule,
ServerTransferStateModule, DSpaceServerTransferStateModule,
ServerTransferStoreModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@@ -72,25 +38,16 @@ export function createTranslateLoader() {
deps: [] deps: []
} }
}), }),
EffectsModule.forRoot([ServerTransferStoreEffects]),
ServerModule, ServerModule,
AppModule AppModule
], ],
providers: [ providers: [
{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: boot,
deps: [
TransferState,
ApplicationRef,
Store,
REQUEST,
GLOBAL_CONFIG
]
}
] ]
}) })
export class ServerAppModule { export class ServerAppModule {
constructor(
private transferState: DSpaceTransferState,
) {
this.transferState.transfer();
}
} }

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserTransferState } from './browser-transfer-state';
import { TransferState } from './transfer-state';
@NgModule({
providers: [
{ provide: TransferState, useClass: BrowserTransferState }
]
})
export class BrowserTransferStateModule {
}

View File

@@ -1,47 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { TransferState } from './transfer-state';
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
import { AppState } from '../../app/app.reducer';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
import { RouterNavigationAction } from '@ngrx/router-store';
@Injectable()
export class BrowserTransferState extends TransferState {
constructor(private store: Store<AppState>, @Inject(GLOBAL_CONFIG) private config: GlobalConfig) {
super();
}
initialize() {
// tslint:disable-next-line:no-string-literal
const cache: any = window['TRANSFER_STATE'] || {};
Object.keys(cache).forEach((key: string) => {
if (key !== 'actions') {
this.set(key, cache[key]);
}
});
if (this.config.prerenderStrategy === 'replay') {
if (cache.actions !== undefined) {
if (this.config.debug) {
console.info('Replay:', (cache.actions !== undefined && cache.actions !== null) ? cache.actions : []);
}
this.store.dispatch(new StoreAction(StoreActionTypes.REPLAY, cache.actions));
} else {
console.info('No actions occured during prerender.');
}
} else if (this.config.prerenderStrategy === 'rehydrate') {
if (this.config.debug) {
console.info('Rehydrate:', (cache.state !== undefined && cache.state !== null) ? cache.state : []);
}
this.store.dispatch(new StoreAction(StoreActionTypes.REHYDRATE, cache.state));
} else {
console.warn([this.config.prerenderStrategy, 'is not a valid prerender strategy!'].join(' '));
}
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { BrowserTransferStateModule } from '@angular/platform-browser';
import { DSpaceBrowserTransferState } from './dspace-browser-transfer-state.service';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@NgModule({
imports: [
BrowserTransferStateModule
],
providers: [
{ provide: DSpaceTransferState, useClass: DSpaceBrowserTransferState }
]
})
export class DSpaceBrowserTransferStateModule {
}

View File

@@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@Injectable()
export class DSpaceBrowserTransferState extends DSpaceTransferState {
transfer() {
const state = this.transferState.get<any>(DSpaceTransferState.NGRX_STATE, null);
this.transferState.remove(DSpaceTransferState.NGRX_STATE);
this.store.dispatch(new StoreAction(StoreActionTypes.REHYDRATE, state));
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { ServerTransferStateModule } from '@angular/platform-server';
import { DSpaceServerTransferState } from './dspace-server-transfer-state.service';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@NgModule({
imports: [
ServerTransferStateModule
],
providers: [
{ provide: DSpaceTransferState, useClass: DSpaceServerTransferState }
]
})
export class DSpaceServerTransferStateModule {
}

View File

@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { DSpaceTransferState } from './dspace-transfer-state.service';
@Injectable()
export class DSpaceServerTransferState extends DSpaceTransferState {
transfer() {
this.transferState.onSerialize(DSpaceTransferState.NGRX_STATE, () => {
let state;
this.store.take(1).subscribe((saveState: any) => {
state = saveState;
});
return state;
});
}
}

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { Store } from '@ngrx/store';
import { AppState } from '../../app/app.reducer';
@Injectable()
export abstract class DSpaceTransferState {
protected static NGRX_STATE = makeStateKey('NGRX_STATE');
constructor(
protected transferState: TransferState,
protected store: Store<AppState>
) {
}
abstract transfer(): void
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerTransferState } from './server-transfer-state';
import { TransferState } from './transfer-state';
@NgModule({
providers: [
{ provide: TransferState, useClass: ServerTransferState }
]
})
export class ServerTransferStateModule {
}

View File

@@ -1,37 +0,0 @@
import { Inject, Injectable, RendererFactory2, ViewEncapsulation } from '@angular/core';
import { INITIAL_CONFIG, PlatformState } from '@angular/platform-server';
import { TransferState } from './transfer-state';
@Injectable()
export class ServerTransferState extends TransferState {
constructor(private state: PlatformState, private rendererFactory: RendererFactory2) {
super();
}
inject() {
try {
const document: any = this.state.getDocument();
const transferStateString = JSON.stringify(this.toJson());
const renderer = this.rendererFactory.createRenderer(document, {
id: '-1',
encapsulation: ViewEncapsulation.None,
styles: [],
data: {}
});
const head = document.children[1].children[0];
if (head.name !== 'head') {
throw new Error('Please have <head> as the first element in your document');
}
const script = renderer.createElement('script');
renderer.setValue(script, `window['TRANSFER_STATE'] = ${transferStateString}`);
renderer.appendChild(head, script);
} catch (e) {
console.error(e);
}
}
}

View File

@@ -1,40 +0,0 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Store } from '@ngrx/store';
@Injectable()
export class TransferState {
protected map = new Map<string, any>();
keys() {
return this.map.keys();
}
get(key: string): any {
return this.map.get(key);
}
set(key: string, value: any): Map<string, any> {
return this.map.set(key, value);
}
toJson(): any {
const json: any = {};
Array.from(this.keys())
.forEach((key: string) => {
json[key] = this.get(key);
});
return json;
}
initialize(): void {
console.log('Initialize does nothing!');
}
inject(): void {
console.log('Inject does nothing!');
}
}

View File

@@ -1,28 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { types } from '../../app/shared/ngrx/type';
import { TransferStoreEffects } from './transfer-store.effects';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
@Injectable()
export class BrowserTransferStoreEffects extends TransferStoreEffects {
@Effect({ dispatch: false }) log = this.actions.ofType(...types()).switchMap((action: Action) => {
if (this.config.debug) {
console.info(action);
}
return Observable.of({});
});
constructor(private actions: Actions, @Inject(GLOBAL_CONFIG) public config: GlobalConfig) {
super();
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserTransferStoreEffects } from './browser-transfer-store.effects';
import { TransferStoreEffects } from './transfer-store.effects';
@NgModule({
providers: [
{ provide: TransferStoreEffects, useClass: BrowserTransferStoreEffects }
]
})
export class BrowserTransferStoreModule {
}

View File

@@ -1,36 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { types } from '../../app/shared/ngrx/type';
import { TransferStoreEffects } from './transfer-store.effects';
import { TransferState } from '../transfer-state/transfer-state';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
@Injectable()
export class ServerTransferStoreEffects extends TransferStoreEffects {
@Effect({ dispatch: false }) track = this.actions.ofType(...types()).switchMap((action: Action) => {
this.cacheAction(action);
return Observable.of({});
});
constructor(private actions: Actions, private cache: TransferState, @Inject(GLOBAL_CONFIG) public config: GlobalConfig) {
super();
this.cache.set('actions', new Array<Action>());
}
private cacheAction(action: Action): void {
if (this.config.debug) {
console.info('Cache:', action);
}
this.cache.get('actions').push(action);
}
}

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerTransferStoreEffects } from './server-transfer-store.effects';
import { TransferStoreEffects } from './transfer-store.effects';
@NgModule({
providers: [
{ provide: TransferStoreEffects, useClass: ServerTransferStoreEffects }
]
})
export class ServerTransferStoreModule {
}

View File

@@ -1,3 +0,0 @@
export abstract class TransferStoreEffects {
}

133
src/server.ts Normal file
View File

@@ -0,0 +1,133 @@
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import 'rxjs/Rx';
import * as fs from 'fs';
import * as pem from 'pem';
import * as https from 'https';
import * as morgan from 'morgan';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as compression from 'compression';
import * as cookieParser from 'cookie-parser';
import { enableProdMode, NgModuleFactory, Type } from '@angular/core';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { ROUTES } from './routes';
import { ENV_CONFIG } from './config';
export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) {
const app = express();
const port = ENV_CONFIG.ui.port ? ENV_CONFIG.ui.port : 80;
if (ENV_CONFIG.production) {
enableProdMode();
app.use(compression());
}
app.use(morgan('dev'));
app.use(cookieParser());
app.use(bodyParser.json());
app.engine('html', ngExpressEngine({
bootstrap: bootstrap
}));
app.set('view engine', 'html');
app.set('views', 'src');
function cacheControl(req, res, next) {
// instruct browser to revalidate
res.header('Cache-Control', ENV_CONFIG.cache.control || 'max-age=60');
next();
}
app.use('/', cacheControl, express.static('dist', { index: false }));
// TODO: either remove or update mock backend
// app.get('/data.json', serverApi);
// app.use('/api', createMockApi());
function ngApp(req, res) {
function onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
console.warn('Error in SSR, serving for direct CSR');
res.sendFile('index.csr.html', { root: './src' });
}
if (ENV_CONFIG.universal.preboot) {
Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => {
res.render('../dist/index', {
req,
res,
preboot: ENV_CONFIG.universal.preboot,
async: ENV_CONFIG.universal.async,
time: ENV_CONFIG.universal.time,
baseUrl: ENV_CONFIG.ui.nameSpace,
originUrl: ENV_CONFIG.ui.baseUrl,
requestUrl: req.originalUrl
});
});
} else {
console.log('Universal off, serving for direct CSR');
res.sendFile('index.csr.html', { root: './src' });
}
}
ROUTES.forEach((route: string) => {
app.get(route, ngApp);
});
function serverStarted() {
console.log(`[${new Date().toTimeString()}] Listening at ${ENV_CONFIG.ui.baseUrl}`);
}
function createHttpsServer(keys) {
https.createServer({
key: keys.serviceKey,
cert: keys.certificate
}, app).listen(port, ENV_CONFIG.ui.host, () => {
serverStarted();
});
}
if (ENV_CONFIG.ui.ssl) {
let serviceKey;
try {
serviceKey = fs.readFileSync('./config/ssl/key.pem');
} catch (e) {
console.warn('Service key not found at ./config/ssl/key.pem');
}
let certificate;
try {
certificate = fs.readFileSync('./config/ssl/cert.pem');
} catch (e) {
console.warn('Certificate not found at ./config/ssl/key.pem');
}
if (serviceKey && certificate) {
createHttpsServer({
serviceKey: serviceKey,
certificate: certificate
});
} else {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
pem.createCertificate({
days: 1,
selfSigned: true
}, (error, keys) => {
createHttpsServer(keys);
});
}
} else {
app.listen(port, ENV_CONFIG.ui.host, () => {
serverStarted();
});
}}

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.server.json",
"angularCompilerOptions": {
"entryModule": "./modules/app/server-app.module#ServerAppModule"
},
"exclude": []
}

View File

@@ -7,13 +7,13 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,
"noEmit": true,
"noEmitHelpers": true, "noEmitHelpers": true,
"importHelpers": true, "importHelpers": true,
"noImplicitAny": false, "noImplicitAny": false,
"strictNullChecks": false, "strictNullChecks": false,
"skipDefaultLibCheck": true,
"pretty": true,
"baseUrl": ".", "baseUrl": ".",
"paths": {},
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
@@ -22,15 +22,19 @@
"node" "node"
], ],
"lib": [ "lib": [
"dom",
"es6",
"es2015",
"es2016", "es2016",
"dom" "es2017"
] ]
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist", "dist",
"src/**/*.spec.ts", "src/**/*.spec.ts",
"src/**/*.e2e.ts" "src/**/*.e2e.ts",
"src/main.server.aot.ts"
], ],
"compileOnSave": false, "compileOnSave": false,
"buildOnSave": false, "buildOnSave": false,

View File

@@ -117,7 +117,6 @@
"variable-declaration": "nospace" "variable-declaration": "nospace"
} }
], ],
"typeof-compare": true,
"unified-signatures": true, "unified-signatures": true,
"variable-name": [ "variable-name": [
true, true,
@@ -146,7 +145,6 @@
"ds", "ds",
"camelCase" "camelCase"
], ],
"invoke-injectable": true,
"no-input-rename": true, "no-input-rename": true,
"no-output-rename": true, "no-output-rename": true,
"templates-use-public": false, "templates-use-public": false,

View File

@@ -1,7 +1,7 @@
const webpackMerge = require('webpack-merge'); const webpackMerge = require('webpack-merge');
const commonPartial = require('./webpack/webpack.common'); const commonPartial = require('./webpack/webpack.common');
const clientPartial = require('./webpack/webpack.client'); const clientPartial = require('./webpack/webpack.client');
const serverPartial = require('./webpack/webpack.server'); const { getServerWebpackPartial } = require('./webpack/webpack.server');
const prodPartial = require('./webpack/webpack.prod'); const prodPartial = require('./webpack/webpack.prod');
const { const {
@@ -15,6 +15,8 @@ module.exports = function(options, webpackOptions) {
console.log(`Running build for ${options.client ? 'client' : 'server'} with AoT Compilation`) console.log(`Running build for ${options.client ? 'client' : 'server'} with AoT Compilation`)
} }
let serverPartial = getServerWebpackPartial(options.aot);
let serverConfig = webpackMerge({}, commonPartial, serverPartial, { let serverConfig = webpackMerge({}, commonPartial, serverPartial, {
plugins: [ plugins: [
getAotPlugin('server', !!options.aot) getAotPlugin('server', !!options.aot)

View File

@@ -3,7 +3,7 @@ const {
} = require('./helpers'); } = require('./helpers');
const { const {
AotPlugin AngularCompilerPlugin
} = require('@ngtools/webpack'); } = require('@ngtools/webpack');
const tsconfigs = { const tsconfigs = {
@@ -11,6 +11,11 @@ const tsconfigs = {
server: root('./src/tsconfig.server.json') server: root('./src/tsconfig.server.json')
}; };
const aotTsconfigs = {
client: root('./src/tsconfig.browser.json'),
server: root('./src/tsconfig.server.aot.json')
};
/** /**
* Generates a AotPlugin for @ngtools/webpack * Generates a AotPlugin for @ngtools/webpack
* *
@@ -19,8 +24,8 @@ const tsconfigs = {
* @returns {AotPlugin} Configuration of AotPlugin * @returns {AotPlugin} Configuration of AotPlugin
*/ */
function getAotPlugin(platform, aot) { function getAotPlugin(platform, aot) {
return new AotPlugin({ return new AngularCompilerPlugin({
tsConfigPath: tsconfigs[platform], tsConfigPath: aot ? aotTsconfigs[platform] : tsconfigs[platform],
skipCodeGeneration: !aot skipCodeGeneration: !aot
}); });
} }

View File

@@ -5,15 +5,20 @@ const {
} = require('./helpers'); } = require('./helpers');
module.exports = { module.exports = {
entry: root('./src/main.server.ts'), getServerWebpackPartial: function (aot) {
output: { const entry = aot ? root('./src/main.server.aot.ts') : root('./src/main.server.ts');
filename: 'server.js' return {
}, entry: entry,
target: 'node', output: {
externals: [nodeExternals({ filename: 'server.js'
whitelist: [ },
/@angular/, target: 'node',
/@ng/, externals: [nodeExternals({
/ngx/] whitelist: [
})], /@angular/,
/@ng/,
/ngx/]
})],
}
}
}; };

View File

@@ -227,6 +227,11 @@ module.exports = function (options) {
/angular(\\|\/)core(\\|\/)@angular/, /angular(\\|\/)core(\\|\/)@angular/,
root('./src'), {} root('./src'), {}
), ),
// Workaround for https://github.com/angular/angular/issues/20357
new ContextReplacementPlugin(
/\@angular(\\|\/)core(\\|\/)esm5/,
root('./src'), {}
),
/** /**
* Plugin: DefinePlugin * Plugin: DefinePlugin

2601
yarn.lock

File diff suppressed because it is too large Load Diff