initial commit

This commit is contained in:
Art Lowel
2016-11-30 15:13:29 +01:00
commit a573556963
50 changed files with 2161 additions and 0 deletions

15
.editorconfig Normal file
View File

@@ -0,0 +1,15 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
/__build__
/__server_build__
/node_modules
/typings
/tsd_typings/
npm-debug.log
/dist/
.idea
*.ngfactory.ts
*.css.shim.ts
.DS_Store
webpack.records.json
/npm-debug.log.*
morgan.log

46
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,46 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/dist/server/index.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"externalConsole": false,
"sourceMaps": false,
"outDir": null
},
{
"name": "Attach",
"type": "node",
"request": "attach",
"port": 5858,
"address": "localhost",
"restart": false,
"sourceMaps": false,
"outDir": null,
"localRoot": "${workspaceRoot}",
"remoteRoot": null
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"processId": "${command.PickProcess}",
"port": 5858,
"sourceMaps": false,
"outDir": null
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.check.workspaceVersion": false
}

33
README.md Normal file
View File

@@ -0,0 +1,33 @@
# dspace-angular
> A UI for DSpace based on Angular 2 Universal.
Currently this contains the [Angular 2 Universal Starter](https://github.com/angular/universal-starter) codebase with a few tweaks.
## Links
- [The working group page on the DuraSpace Wiki](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group)
- [The project board (waffle.io)](https://waffle.io/DSpace/dspace-angular)
- [The prototype](https://github.com/DSpace-Labs/angular2-ui-prototype)
## Requirements
- [Node.js](https://nodejs.org/)
## Installation
- `npm install`
## Serve
- `npm start` to build your client app and start a web server
- `npm run build` to prepare a distributable bundle
## Watch files
- `npm run watch` to build your client app and start a web server
## Development
- run `npm start` and `npm run watch` in two separate terminals to build your client app, start a web server, and allow file changes to update in realtime
## AoT and Prod
- `npm run build:prod:ngc` to compile the ngfactory files and build prod

12
app.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "Angular 2 Universal Starter",
"description": "Angular 2 Universal starter kit by @AngularClass",
"repository": "https://github.com/angular/universal-starter",
"logo": "https://cloud.githubusercontent.com/assets/1016365/10639063/138338bc-7806-11e5-8057-d34c75f3cafc.png",
"env": {
"NPM_CONFIG_PRODUCTION": {
"description": "Install `webpack` and other development modules when deploying to allow full builds.",
"value": "false"
}
}
}

7
empty.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
NgProbeToken: {},
_createConditionalRootRenderer: function(rootRenderer, extraTokens, coreTokens) {
return rootRenderer;
},
__platform_browser_private__: {}
};

7
nodemon.json Normal file
View File

@@ -0,0 +1,7 @@
{
"watch": [
"dist",
"src/index.html"
],
"ext" : "js ts json html"
}

98
package.json Normal file
View File

@@ -0,0 +1,98 @@
{
"name": "dspace-angular",
"version": "0.0.0",
"description": "Angular 2 Universal UI for DSpace",
"repository": {
"type": "git",
"url": "https://github.com/dspace/dspace-angular.git"
},
"scripts": {
"watch": "webpack --watch",
"watch:dev": "npm run server & npm run watch",
"clean:dist": "rimraf dist",
"clean:ngc": "rimraf **/*.ngfactory.ts **/*.css.shim.ts",
"prebuild": "npm run clean:dist",
"build": "webpack --progress",
"build:prod:ngc": "npm run clean:ngc && npm run ngc && npm run clean:dist && npm run build:prod",
"build:prod:ngc:json": "npm run clean:ngc && npm run ngc && npm run clean:dist && npm run build:prod:json",
"build:prod": "webpack --config webpack.prod.config.ts",
"build:prod:json": "webpack --config webpack.prod.config.ts --json | webpack-bundle-size-analyzer",
"ngc": "ngc -p tsconfig.aot.json",
"prestart": "npm run build",
"server": "nodemon dist/server/index.js",
"debug:server": "node-nightly --inspect --debug-brk dist/server/index.js",
"start": "npm run server",
"debug:start": "npm run build && npm run debug:server",
"predebug": "npm run build",
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js",
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --config webpack.prod.config.ts",
"debug": "node --debug-brk dist/server/index.js"
},
"dependencies": {
"@angular/common": "~2.1.2",
"@angular/compiler": "~2.1.2",
"@angular/compiler-cli": "~2.1.2",
"@angular/core": "~2.1.2",
"@angular/forms": "~2.1.2",
"@angular/http": "~2.1.2",
"@angular/platform-browser": "~2.1.2",
"@angular/platform-browser-dynamic": "~2.1.2",
"@angular/platform-server": "~2.1.2",
"@angular/router": "~3.1.2",
"@angular/upgrade": "~2.1.2",
"@angularclass/bootloader": "~1.0.1",
"@angularclass/idle-preload": "~1.0.4",
"angular2-express-engine": "~2.1.0-rc.1",
"angular2-platform-node": "~2.1.0-rc.1",
"angular2-universal": "~2.1.0-rc.1",
"angular2-universal-polyfills": "~2.1.0-rc.1",
"body-parser": "^1.15.2",
"compression": "^1.6.2",
"express": "^4.14.0",
"js.clone": "0.0.3",
"methods": "~1.1.2",
"morgan": "^1.7.0",
"preboot": "~4.5.2",
"rxjs": "5.0.0-beta.12",
"webfontloader": "^1.6.26",
"zone.js": "~0.6.26"
},
"devDependencies": {
"@types/morgan": "^1.7.32",
"@types/body-parser": "0.0.29",
"@types/compression": "0.0.29",
"@types/cookie-parser": "^1.3.29",
"@types/express": "^4.0.32",
"@types/express-serve-static-core": "^4.0.33",
"@types/hammerjs": "^2.0.32",
"@types/memory-cache": "0.0.29",
"@types/mime": "0.0.28",
"@types/node": "^6.0.38",
"@types/serve-static": "^1.7.27",
"@types/webfontloader": "^1.6.27",
"@ngtools/webpack": "~1.1.7",
"accepts": "^1.3.3",
"angular2-template-loader": "^0.4.0",
"awesome-typescript-loader": "^2.2.4",
"cookie-parser": "^1.4.3",
"express-interceptor": "^1.2.0",
"iltorb": "^1.0.13",
"imports-loader": "^0.6.5",
"json-loader": "^0.5.4",
"memory-cache": "^0.1.6",
"nodemon": "^1.10.0",
"raw-loader": "^0.5.1",
"reflect-metadata": "0.1.8",
"rimraf": "^2.5.4",
"string-replace-loader": "^1.0.5",
"ts-helpers": "^1.1.2",
"ts-node": "^1.3.0",
"typescript": "2.0.2",
"v8-lazy-parse-webpack-plugin": "^0.3.0",
"webpack": "2.1.0-beta.27",
"webpack-bundle-analyzer": "1.4.1",
"webpack-dev-middleware": "^1.8.4",
"webpack-dev-server": "2.1.0-beta.11",
"webpack-merge": "~0.16.0"
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AboutComponent } from './about.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'about', component: AboutComponent }
])
]
})
export class AboutRoutingModule { }

View File

@@ -0,0 +1,14 @@
import { Component, Inject, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'about',
template: 'About component'
})
export class AboutComponent {
constructor(@Inject('req') req: any) {
// console.log('req', req)
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { AboutComponent } from './about.component';
import { AboutRoutingModule } from './about-routing.module';
@NgModule({
imports: [
SharedModule,
AboutRoutingModule
],
declarations: [
AboutComponent
]
})
export class AboutModule { }

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'home', component: HomeComponent }
])
]
})
export class HomeRoutingModule { }

View File

@@ -0,0 +1,8 @@
blockquote {
border-left:5px #158126 solid;
background:#fff;
padding:20px 20px 20px 40px;
}
blockquote::before {
left: 1em;
}

View File

@@ -0,0 +1,6 @@
<div class="home">
Home component
<strong>Async data call return value:</strong>
<pre>{{ data | json }}</pre>
<blockquote>{{ data.data }}</blockquote>
</div>

View File

@@ -0,0 +1,27 @@
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
import { ModelService } from '../shared/model/model.service';
@Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'home',
styleUrls: [ './home.component.css' ],
templateUrl: './home.component.html'
})
export class HomeComponent {
data: any = {};
constructor(public model: ModelService) {
// we need the data synchronously for the client to set the server response
// we create another method so we have more control for testing
this.universalInit();
}
universalInit() {
this.model.get('/data.json').subscribe(data => {
this.data = data;
});
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { HomeComponent } from './home.component';
import { HomeRoutingModule } from './home-routing.module';
@NgModule({
imports: [
SharedModule,
HomeRoutingModule
],
declarations: [
HomeComponent
]
})
export class HomeModule { }

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LazyComponent } from './lazy.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: LazyComponent }
])
]
})
export class LazyRoutingModule { }

View File

@@ -0,0 +1,14 @@
import { Component, Inject, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'lazy',
template: `
<p>
Lazy component
</p>
`
})
export class LazyComponent {
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { LazyComponent } from './lazy.component';
import { LazyRoutingModule } from './lazy-routing.module';
@NgModule({
imports: [
SharedModule,
LazyRoutingModule
],
declarations: [
LazyComponent
]
})
export class LazyModule { }

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TodoComponent } from './todo.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'todo', component: TodoComponent }
])
]
})
export class TodoRoutingModule { }

View File

@@ -0,0 +1,43 @@
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
import { ModelService } from '../shared/model/model.service';
@Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'todo',
styles: [`
`],
template: `
<div class="todo">
Todo component
<form #f="ngForm" (ngSubmit)="addTodo(newTodo)">
<input name="newTodo" [(ngModel)]="newTodo">
<button>Submit</button>
</form>
<ul>
<li *ngFor="let todo of todos">
{{ todo }}
</li>
</ul>
</div>
`
})
export class TodoComponent {
newTodo = '';
todos = [];
constructor(public model: ModelService) {
// we need the data synchronously for the client to set the server response
// we create another method so we have more control for testing
this.universalInit();
}
addTodo(newTodo) {
this.todos.push(newTodo);
this.newTodo = '';
}
universalInit() {
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { TodoComponent } from './todo.component';
import { TodoRoutingModule } from './todo-routing.module';
@NgModule({
imports: [
SharedModule,
TodoRoutingModule
],
declarations: [
TodoComponent
]
})
export class TodoModule { }

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
export function getLazyModule() {
return System.import('./+lazy/lazy.module' + (process.env.AOT ? '.ngfactory' : ''))
.then(mod => mod[(process.env.AOT ? 'LazyModuleNgFactory' : 'LazyModule')]);
}
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'lazy', loadChildren: getLazyModule }
])
],
})
export class AppRoutingModule { }

66
src/+app/app.component.ts Normal file
View File

@@ -0,0 +1,66 @@
import { Component, Directive, ElementRef, Renderer, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
//
/////////////////////////
// ** Example Directive
// Notice we don't touch the Element directly
@Directive({
selector: '[xLarge]'
})
export class XLargeDirective {
constructor(element: ElementRef, renderer: Renderer) {
// ** IMPORTANT **
// we must interact with the dom through -Renderer-
// for webworker/server to see the changes
renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large');
// ^^
}
}
@Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'app',
styles: [`
* { padding:0; margin:0; font-family: 'Droid Sans', sans-serif; }
#universal { text-align:center; font-weight:bold; padding:15px 0; }
nav { background:#158126; min-height:40px; border-bottom:5px #046923 solid; }
nav a { font-weight:bold; text-decoration:none; color:#fff; padding:20px; display:inline-block; }
nav a:hover { background:#00AF36; }
.hero-universal { min-height:500px; display:block; padding:20px; background: url('/assets/logo.png') no-repeat center center; }
.inner-hero { background: rgba(255, 255, 255, 0.75); border:5px #ccc solid; padding:25px; }
.router-link-active { background-color: #00AF36; }
main { padding:20px 0; }
pre { font-size:12px; }
`],
template: `
<h3 id="universal">Angular2 Universal</h3>
<nav>
<a routerLinkActive="router-link-active" routerLink="home">Home</a>
<a routerLinkActive="router-link-active" routerLink="about">About</a>
<a routerLinkActive="router-link-active" routerLink="todo">Todo</a>
<a routerLinkActive="router-link-active" routerLink="lazy">Lazy</a>
</nav>
<div class="hero-universal">
<div class="inner-hero">
<div>
<span xLarge>Universal JavaScript {{ title }}!</span>
</div>
Two-way binding: <input type="text" [value]="title" (input)="title = $event.target.value">
<br>
<br>
<strong>Router-outlet:</strong>
<main>
<router-outlet></router-outlet>
</main>
</div>
</div>
`
})
export class AppComponent {
title = 'ftw';
}

27
src/+app/app.module.ts Executable file
View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HomeModule } from './+home/home.module';
import { AboutModule } from './+about/about.module';
import { TodoModule } from './+todo/todo.module';
import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent, XLargeDirective } from './app.component';
@NgModule({
declarations: [ AppComponent, XLargeDirective ],
imports: [
SharedModule,
HomeModule,
AboutModule,
TodoModule,
AppRoutingModule
]
})
export class AppModule {
}
export { AppComponent } from './app.component';

View File

@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { CacheService } from './cache.service';
@Injectable()
export class ApiService {
constructor(public _http: Http) {
}
/**
* whatever domain/feature method name
*/
get(url: string, options?: any) {
return this._http.get(url, options)
.map(res => res.json())
.catch(err => {
console.log('Error: ', err);
return Observable.throw(err);
});
}
}

View File

@@ -0,0 +1,88 @@
import { Inject, Injectable, isDevMode } from '@angular/core';
@Injectable()
export class CacheService {
static KEY = 'CacheService';
constructor(@Inject('LRU') public _cache: Map<string, any>) {
}
/**
* check if there is a value in our store
*/
has(key: string | number): boolean {
let _key = this.normalizeKey(key);
return this._cache.has(_key);
}
/**
* store our state
*/
set(key: string | number, value: any): void {
let _key = this.normalizeKey(key);
this._cache.set(_key, value);
}
/**
* get our cached value
*/
get(key: string | number): any {
let _key = this.normalizeKey(key);
return this._cache.get(_key);
}
/**
* release memory refs
*/
clear(): void {
this._cache.clear();
}
/**
* convert to json for the client
*/
dehydrate(): any {
let json = {};
this._cache.forEach((value: any, key: string) => json[key] = value);
return json;
}
/**
* convert server json into out initial state
*/
rehydrate(json: any): void {
Object.keys(json).forEach((key: string) => {
let _key = this.normalizeKey(key);
let value = json[_key];
this._cache.set(_key, value);
});
}
/**
* allow JSON.stringify to work
*/
toJSON(): any {
return this.dehydrate();
}
/**
* convert numbers into strings
*/
normalizeKey(key: string | number): string {
if (isDevMode() && this._isInvalidValue(key)) {
throw new Error('Please provide a valid key to save in the CacheService');
}
return key + '';
}
_isInvalidValue(key): boolean {
return key === null ||
key === undefined ||
key === 0 ||
key === '' ||
typeof key === 'boolean' ||
Number.isNaN(<number>key);
}
}

View File

@@ -0,0 +1,55 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/share';
import { CacheService } from '../cache.service';
import { ApiService } from '../api.service';
export function hashCodeString(str: string): string {
let hash = 0;
if (str.length === 0) {
return hash + '';
}
for (let i = 0; i < str.length; i++) {
let char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash + '';
}
// domain/feature service
@Injectable()
export class ModelService {
// This is only one example of one Model depending on your domain
constructor(public _api: ApiService, public _cache: CacheService) {
}
/**
* whatever domain/feature method name
*/
get(url) {
// you want to return the cache if there is a response in it.
// This would cache the first response so if your API isn't idempotent
// you probably want to remove the item from the cache after you use it. LRU of 10
// you can use also hashCodeString here
let key = url;
if (this._cache.has(key)) {
return Observable.of(this._cache.get(key));
}
// you probably shouldn't .share() and you should write the correct logic
return this._api.get(url)
.do(json => {
this._cache.set(key, json);
})
.share();
}
// don't cache here since we're creating
create() {
// TODO
}
}

View File

@@ -0,0 +1,52 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ApiService } from './api.service';
import { ModelService } from './model/model.service';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
CommonModule,
RouterModule,
FormsModule,
ReactiveFormsModule
];
const PIPES = [
// put pipes here
];
const COMPONENTS = [
// put shared components here
];
const PROVIDERS = [
ModelService,
ApiService
]
@NgModule({
imports: [
...MODULES
],
declarations: [
...PIPES,
...COMPONENTS
],
exports: [
...MODULES,
...PIPES,
...COMPONENTS
]
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [
...PROVIDERS
]
};
}
}

View File

@@ -0,0 +1,21 @@
/*
* THIS IS TEMPORARY TO PATCH 2.1.1+ Core bugs
*/
/* tslint:disable */
let __compiler__ = require('@angular/compiler');
import { __platform_browser_private__ } from '@angular/platform-browser';
import { __core_private__ } from '@angular/core';
if (!__core_private__['ViewUtils']) {
__core_private__['ViewUtils'] = __core_private__['view_utils'];
}
if (__compiler__ && __compiler__.SelectorMatcher && __compiler__.CssSelector) {
(__compiler__).__compiler_private__ = {
SelectorMatcher: __compiler__.SelectorMatcher,
CssSelector: __compiler__.CssSelector
}
}

44
src/__workaround.node.ts Normal file
View File

@@ -0,0 +1,44 @@
/*
* THIS IS TEMPORARY TO PATCH 2.1.1+ Core bugs
*/
/* tslint:disable */
let __compiler__ = require('@angular/compiler');
import { __platform_browser_private__ } from '@angular/platform-browser';
import { __core_private__ } from '@angular/core';
let patch = false;
if (!__core_private__['ViewUtils']) {
patch = true;
__core_private__['ViewUtils'] = __core_private__['view_utils'];
}
if (__compiler__ && __compiler__.SelectorMatcher && __compiler__.CssSelector) {
patch = true;
(__compiler__).__compiler_private__ = {
SelectorMatcher: __compiler__.SelectorMatcher,
CssSelector: __compiler__.CssSelector
}
}
if (patch) {
var __universal__ = require('angular2-platform-node/__private_imports__');
__universal__.ViewUtils = __core_private__['view_utils'];
__universal__.CssSelector = __universal__.CssSelector || __compiler__.CssSelector;
__universal__.SelectorMatcher = __universal__.SelectorMatcher || __compiler__.SelectorMatcher;
}
// Fix Material Support
function universalMaterialSupports(eventName: string): boolean { return Boolean(this.isCustomEvent(eventName)); }
__platform_browser_private__.HammerGesturesPlugin.prototype.supports = universalMaterialSupports;
// End Fix Material Support
// Fix Universal Style
import { NodeDomRootRenderer, NodeDomRenderer } from 'angular2-universal/node';
function renderComponentFix(componentProto: any) {
return new NodeDomRenderer(this, componentProto, this._animationDriver);
}
NodeDomRootRenderer.prototype.renderComponent = renderComponentFix;
// End Fix Universal Style

194
src/angular2-meta.ts Normal file
View File

@@ -0,0 +1,194 @@
/**
* @license
* Copyright Google Inc. 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 {Injectable} from '@angular/core';
// es6-modules are used here
import {DomAdapter, getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
/**
* Represent meta element.
*
* ### Example
*
* ```ts
* { name: 'application-name', content: 'Name of my application' },
* { name: 'description', content: 'A description of the page', id: 'desc' }
* // ...
* // Twitter
* { name: 'twitter:title', content: 'Content Title' }
* // ...
* // Google+
* { itemprop: 'name', content: 'Content Title' },
* { itemprop: 'description', content: 'Content Title' }
* // ...
* // Facebook / Open Graph
* { property: 'fb:app_id', content: '123456789' },
* { property: 'og:title', content: 'Content Title' }
* ```
*
* @experimental
*/
export interface MetaDefinition {
charset?: string;
content?: string;
httpEquiv?: string;
id?: string;
itemprop?: string;
name?: string;
property?: string;
scheme?: string;
url?: string;
[prop: string]: string;
}
/**
* A service that can be used to get and add meta tags.
*
* @experimental
*/
@Injectable()
export class Meta {
private _dom: DomAdapter = getDOM();
/**
* Adds a new meta tag to the dom.
*
* ### Example
*
* ```ts
* const name: MetaDefinition = {name: 'application-name', content: 'Name of my application'};
* const desc: MetaDefinition = {name: 'description', content: 'A description of the page'};
* const tags: HTMLMetaElement[] = this.meta.addTags([name, desc]);
* ```
*
* @param tags
* @returns {HTMLMetaElement[]}
*/
addTags(...tags: Array<MetaDefinition|MetaDefinition[]>): HTMLMetaElement[] {
const presentTags = this._flattenArray(tags);
if (presentTags.length === 0) return [];
return presentTags.map((tag: MetaDefinition) => this._addInternal(tag));
}
/**
* Gets the meta tag by the given selector. Returns element or null
* if there's no such meta element.
*
* ### Example
*
* ```ts
* const meta: HTMLMetaElement = this.meta.getTag('name=description');
* const twitterMeta: HTMLMetaElement = this.meta.getTag('name="twitter:title"');
* const fbMeta: HTMLMetaElement = this.meta.getTag('property="fb:app_id"');
* ```
*
* @param selector
* @returns {HTMLMetaElement}
*/
getTag(selector: string): HTMLMetaElement {
if (!selector) return null;
return this._dom.query(`meta[${selector}]`);
}
/**
* Updates the meta tag with the given selector.
*
* * ### Example
*
* ```ts
* const meta: HTMLMetaElement = this.meta.updateTag('name=description', {name: 'description',
* content: 'New description'});
* console.log(meta.content); // 'New description'
* ```
*
* @param selector
* @param tag updated tag definition
* @returns {HTMLMetaElement}
*/
updateTag(selector: string, tag: MetaDefinition): HTMLMetaElement {
const meta: HTMLMetaElement = this.getTag(selector);
if (!meta) {
// create element if it doesn't exist
return this._addInternal(tag);
}
return this._prepareMetaElement(tag, meta);
}
/**
* Removes meta tag with the given selector from the dom.
*
* ### Example
*
* ```ts
* this.meta.removeTagBySelector('name=description');
* ```
*
* @param selector
*/
removeTagBySelector(selector: string): void {
const meta: HTMLMetaElement = this.getTag(selector);
this.removeTagElement(meta);
}
/**
* Removes given meta element from the dom.
*
* ### Example
* ```ts
* const elem: HTMLMetaElement = this.meta.getTag('name=description');
* this.meta.removeTagElement(elem);
* ```
*
* @param meta meta element
*/
removeTagElement(meta: HTMLMetaElement): void {
if (meta) {
this._removeMetaElement(meta);
}
}
private _addInternal(tag: MetaDefinition): HTMLMetaElement {
const meta: HTMLMetaElement = this._createMetaElement();
this._prepareMetaElement(tag, meta);
this._appendMetaElement(meta);
return meta;
}
private _createMetaElement(): HTMLMetaElement {
return this._dom.createElement('meta') as HTMLMetaElement;
}
private _prepareMetaElement(tag: MetaDefinition, el: HTMLMetaElement): HTMLMetaElement {
Object.keys(tag).forEach((prop: string) => this._dom.setAttribute(el, prop, tag[prop]));
return el;
}
private _appendMetaElement(meta: HTMLMetaElement): void {
const head = this._dom.getElementsByTagName(this._dom.defaultDoc(), 'head')[0];
this._dom.appendChild(head, meta);
}
private _removeMetaElement(meta: HTMLMetaElement): void {
const head = this._dom.parentElement(meta);
this._dom.removeChild(head, meta);
}
private _flattenArray(input: any[], out: any[] = []): any[] {
if (input) {
for (let i = 0; i < input.length; i++) {
const item: any = input[i];
if (Array.isArray(item)) {
this._flattenArray(item, out);
} else if (item) {
out.push(item);
}
}
}
return out;
}
}

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

106
src/backend/api.ts Normal file
View File

@@ -0,0 +1,106 @@
var util = require('util');
var {Router} = require('express');
// Our API for demos only
import {fakeDataBase} from './db';
import {fakeDemoRedisCache} from './cache';
// you would use cookies/token etc
var USER_ID = 'f9d98cf1-1b96-464e-8755-bcc2a5c09077'; // hardcoded as an example
// Our API for demos only
export function serverApi(req, res) {
let key = USER_ID + '/data.json';
let cache = fakeDemoRedisCache.get(key);
if (cache !== undefined) {
console.log('/data.json Cache Hit');
return res.json(cache);
}
console.log('/data.json Cache Miss');
fakeDataBase.get()
.then(data => {
fakeDemoRedisCache.set(key, data);
return data;
})
.then(data => res.json(data));
}
// todo API
var COUNT = 4;
var TODOS = [
{ id: 0, value: 'finish example', created_at: new Date(), completed: false },
{ id: 1, value: 'add tests', created_at: new Date(), completed: false },
{ id: 2, value: 'include development environment', created_at: new Date(), completed: false },
{ id: 3, value: 'include production environment', created_at: new Date(), completed: false }
];
export function createTodoApi() {
var router = Router()
router.route('/todos')
.get(function(req, res) {
console.log('GET');
// 70ms latency
setTimeout(function() {
res.json(TODOS);
}, 0);
})
.post(function(req, res) {
console.log('POST', util.inspect(req.body, {colors: true}));
var todo = req.body;
if (todo) {
TODOS.push({
value: todo.value,
created_at: new Date(),
completed: todo.completed,
id: COUNT++
});
return res.json(todo);
}
return res.end();
});
router.param('todo_id', function(req, res, next, todo_id) {
// ensure correct prop type
var id = Number(req.params.todo_id);
try {
var todo = TODOS[id];
req.todo_id = id;
req.todo = TODOS[id];
next();
} catch (e) {
next(new Error('failed to load todo'));
}
});
router.route('/todos/:todo_id')
.get(function(req, res) {
console.log('GET', util.inspect(req.todo, {colors: true}));
res.json(req.todo);
})
.put(function(req, res) {
console.log('PUT', util.inspect(req.body, {colors: true}));
var index = TODOS.indexOf(req.todo);
var todo = TODOS[index] = req.body;
res.json(todo);
})
.delete(function(req, res) {
console.log('DELETE', req.todo_id);
var index = TODOS.indexOf(req.todo);
TODOS.splice(index, 1);
res.json(req.todo);
});
return router;
};

17
src/backend/cache.ts Normal file
View File

@@ -0,0 +1,17 @@
var _fakeLRUcount = 0;
export const fakeDemoRedisCache = {
_cache: {},
get: (key) => {
let cache = fakeDemoRedisCache._cache[key];
_fakeLRUcount++;
if (_fakeLRUcount >= 10) {
fakeDemoRedisCache.clear();
_fakeLRUcount = 0;
}
return cache;
},
set: (key, data) => fakeDemoRedisCache._cache[key] = data,
clear: () => fakeDemoRedisCache._cache = {}
};

7
src/backend/db.ts Normal file
View File

@@ -0,0 +1,7 @@
// Our API for demos only
export const fakeDataBase = {
get() {
let res = { data: 'This fake data came from the db on the server.' };
return Promise.resolve(res);
}
};

97
src/browser.module.ts Executable file
View File

@@ -0,0 +1,97 @@
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UniversalModule, isBrowser, isNode, AUTO_PREBOOT } from 'angular2-universal/browser'; // for AoT we need to manually split universal packages
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
import { AppModule, AppComponent } from './+app/app.module';
import { SharedModule } from './+app/shared/shared.module';
import { CacheService } from './+app/shared/cache.service';
// Will be merged into @angular/platform-browser in a later release
// see https://github.com/angular/angular/pull/12322
import { Meta } from './angular2-meta';
// import * as LRU from 'modern-lru';
export function getLRU(lru?: any) {
// use LRU for node
// return lru || new LRU(10);
return lru || new Map();
}
export function getRequest() {
// the request object only lives on the server
return { cookie: document.cookie };
}
export function getResponse() {
// the response object is sent as the index.html and lives on the server
return {};
}
// TODO(gdi2290): refactor into Universal
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
@NgModule({
bootstrap: [ AppComponent ],
imports: [
// MaterialModule.forRoot() should be included first
UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included
FormsModule,
RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),
IdlePreloadModule.forRoot(),
SharedModule.forRoot(),
AppModule,
],
providers: [
{ provide: 'isBrowser', useValue: isBrowser },
{ provide: 'isNode', useValue: isNode },
{ provide: 'req', useFactory: getRequest },
{ provide: 'res', useFactory: getResponse },
{ provide: 'LRU', useFactory: getLRU, deps: [] },
CacheService,
Meta,
// { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete
]
})
export class MainModule {
constructor(public cache: CacheService) {
// TODO(gdi2290): refactor into a lifecycle hook
this.doRehydrate();
}
doRehydrate() {
let defaultValue = {};
let serverCache = this._getCacheValue(CacheService.KEY, defaultValue);
this.cache.rehydrate(serverCache);
}
_getCacheValue(key: string, defaultValue: any): any {
// browser
const win: any = window;
if (win[UNIVERSAL_KEY] && win[UNIVERSAL_KEY][key]) {
let serverCache = defaultValue;
try {
serverCache = JSON.parse(win[UNIVERSAL_KEY][key]);
if (typeof serverCache !== typeof defaultValue) {
console.log('Angular Universal: The type of data from the server is different from the default value type');
serverCache = defaultValue;
}
} catch (e) {
console.log('Angular Universal: There was a problem parsing the server data during rehydrate');
serverCache = defaultValue;
}
return serverCache;
} else {
console.log('Angular Universal: UNIVERSAL_CACHE is missing');
}
return defaultValue;
}
}

36
src/client.aot.ts Normal file
View File

@@ -0,0 +1,36 @@
// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './__workaround.browser'; // temporary until 2.1.1 things are patched in Core
// Angular 2
import { enableProdMode } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { bootloader } from '@angularclass/bootloader';
// for AoT use platformBrowser
// import { platformUniversalDynamic } from 'angular2-universal/browser';
import { load as loadWebFont } from 'webfontloader';
// enable prod for faster renders
enableProdMode();
import { MainModuleNgFactory } from './browser.module.ngfactory';
export const platformRef = platformBrowser();
// on document ready bootstrap Angular 2
export function main() {
// Load fonts async
// https://github.com/typekit/webfontloader#configuration
loadWebFont({
google: {
families: ['Droid Sans']
}
});
return platformRef.bootstrapModuleFactory(MainModuleNgFactory);
}
// support async tag or hmr
bootloader(main);

34
src/client.ts Normal file
View File

@@ -0,0 +1,34 @@
// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './__workaround.browser'; // temporary until 2.1.1 things are patched in Core
// Angular 2
import { enableProdMode } from '@angular/core';
import { platformUniversalDynamic } from 'angular2-universal/browser';
import { bootloader } from '@angularclass/bootloader';
import { load as loadWebFont } from 'webfontloader';
// enable prod for faster renders
// enableProdMode();
import { MainModule } from './browser.module';
export const platformRef = platformUniversalDynamic();
// on document ready bootstrap Angular 2
export function main() {
// Load fonts async
// https://github.com/typekit/webfontloader#configuration
loadWebFont({
google: {
families: ['Droid Sans']
}
});
return platformRef.bootstrapModule(MainModule);
}
// support async tag or hmr
bootloader(main);

23
src/index.html Normal file
View File

@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<title>DSpace</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,minimum-scale=1">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link rel="prerender" href="http://localhost:3000/lazy">
<link rel="preload" href="/assets/logo.svg">
<base href="/">
</head>
<body>
<app>
Loading DSpace ...
</app>
<script async src="/main.bundle.js"></script>
</body>
</html>

73
src/node.module.ts Executable file
View File

@@ -0,0 +1,73 @@
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UniversalModule, isBrowser, isNode } from 'angular2-universal/node'; // for AoT we need to manually split universal packages
import { AppModule, AppComponent } from './+app/app.module';
import { SharedModule } from './+app/shared/shared.module';
import { CacheService } from './+app/shared/cache.service';
// Will be merged into @angular/platform-browser in a later release
// see https://github.com/angular/angular/pull/12322
import { Meta } from './angular2-meta';
export function getLRU() {
return new Map();
}
export function getRequest() {
return Zone.current.get('req') || {};
}
export function getResponse() {
return Zone.current.get('res') || {};
}
// TODO(gdi2290): refactor into Universal
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
@NgModule({
bootstrap: [ AppComponent ],
imports: [
// MaterialModule.forRoot() should be included first
UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included
FormsModule,
RouterModule.forRoot([], { useHash: false }),
SharedModule.forRoot(),
AppModule,
],
providers: [
{ provide: 'isBrowser', useValue: isBrowser },
{ provide: 'isNode', useValue: isNode },
{ provide: 'req', useFactory: getRequest },
{ provide: 'res', useFactory: getResponse },
{ provide: 'LRU', useFactory: getLRU, deps: [] },
CacheService,
Meta,
]
})
export class MainModule {
constructor(public cache: CacheService) {
}
/**
* We need to use the arrow function here to bind the context as this is a gotcha
* in Universal for now until it's fixed
*/
universalDoDehydrate = (universalCache) => {
universalCache[CacheService.KEY] = JSON.stringify(this.cache.dehydrate());
}
/**
* Clear the cache after it's rendered
*/
universalAfterDehydrate = () => {
// comment out if LRU provided at platform level to be shared between each user
this.cache.clear();
}
}

147
src/server.aot.ts Normal file
View File

@@ -0,0 +1,147 @@
// the polyfills must be one of the first things imported in node.js.
// The only modules to be imported higher - node modules with es6-promise 3.x or other Promise polyfill dependency
// (rule of thumb: do it if you have zone.js exception that it has been overwritten)
// if you are including modules that modify Promise, such as NewRelic,, you must include them before polyfills
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './__workaround.node'; // temporary until 2.1.1 things are patched in Core
import * as fs from 'fs';
import * as path from 'path';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import * as morgan from 'morgan';
import * as mcache from 'memory-cache';
const { gzipSync } = require('zlib');
const accepts = require('accepts');
const { compressSync } = require('iltorb');
const interceptor = require('express-interceptor');
// Angular 2
import { enableProdMode } from '@angular/core';
// Angular 2 Universal
import { createEngine } from 'angular2-express-engine';
// App
import { MainModuleNgFactory } from './node.module.ngfactory';
// Routes
import { routes } from './server.routes';
// enable prod for faster renders
enableProdMode();
const app = express();
const ROOT = path.join(path.resolve(__dirname, '..'));
// Express View
app.engine('.html', createEngine({
precompile: false, // this needs to be false when using ngFactory
ngModule: MainModuleNgFactory,
providers: [
// use only if you have shared state between users
// { provide: 'LRU', useFactory: () => new LRU(10) }
// stateless providers only since it's shared
]
}));
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname);
app.set('view engine', 'html');
app.set('json spaces', 2);
app.use(cookieParser('Angular 2 Universal'));
app.use(bodyParser.json());
app.use(interceptor((req, res)=>({
// don't compress responses with this request header
isInterceptable: () => (!req.headers['x-no-compression']),
intercept: ( body, send ) => {
const encodings = new Set(accepts(req).encodings());
const bodyBuffer = new Buffer(body);
// url specific key for response cache
const key = '__response__' + req.originalUrl || req.url;
let output = bodyBuffer;
// check if cache exists
if (mcache.get(key) === null) {
// check for encoding support
if (encodings.has('br')) {
// brotli
res.setHeader('Content-Encoding', 'br');
output = compressSync(bodyBuffer);
mcache.put(key, {output, encoding: 'br'});
} else if (encodings.has('gzip')) {
// gzip
res.setHeader('Content-Encoding', 'gzip');
output = gzipSync(bodyBuffer);
mcache.put(key, {output, encoding: 'gzip'});
}
} else {
const { output, encoding } = mcache.get(key);
res.setHeader('Content-Encoding', encoding);
send(output);
}
send(output);
}
})));
const accessLogStream = fs.createWriteStream(ROOT + '/morgan.log', {flags: 'a'})
app.use(morgan('common', {
skip: (req, res) => res.statusCode < 400,
stream: accessLogStream
}));
function cacheControl(req, res, next) {
// instruct browser to revalidate in 60 seconds
res.header('Cache-Control', 'max-age=60');
next();
}
// Serve static files
app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), {maxAge: 30}));
app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), {index: false}));
//
/////////////////////////
// ** Example API
// Notice API should be in a separate process
import { serverApi, createTodoApi } from './backend/api';
// Our API for demos only
app.get('/data.json', serverApi);
app.use('/api', createTodoApi());
function ngApp(req, res) {
res.render('index', {
req,
res,
// time: true, // use this to determine what part of your app is slow only in development
preboot: false,
baseUrl: '/',
requestUrl: req.originalUrl,
originUrl: `http://localhost:${ app.get('port') }`
});
}
/**
* use universal for specific routes
*/
app.get('/', ngApp);
routes.forEach(route => {
app.get(`/${route}`, ngApp);
app.get(`/${route}/*`, ngApp);
});
app.get('*', function(req, res) {
res.setHeader('Content-Type', 'application/json');
var pojo = { status: 404, message: 'No Content' };
var json = JSON.stringify(pojo, null, 2);
res.status(404).send(json);
});
// Server
let server = app.listen(app.get('port'), () => {
console.log(`Listening on: http://localhost:${server.address().port}`);
});

17
src/server.routes.ts Normal file
View File

@@ -0,0 +1,17 @@
/**
* Server-side routes. Only the listed routes support html5pushstate.
* Has to match client side routes.
*
* Index (/) route does not have to be listed here.
*
* @example
* export const routes: string[] = [
* 'home', 'about'
* ];
**/
export const routes: string[] = [
'about',
'home',
'todo',
'lazy',
];

103
src/server.ts Normal file
View File

@@ -0,0 +1,103 @@
// the polyfills must be one of the first things imported in node.js.
// The only modules to be imported higher - node modules with es6-promise 3.x or other Promise polyfill dependency
// (rule of thumb: do it if you have zone.js exception that it has been overwritten)
// if you are including modules that modify Promise, such as NewRelic,, you must include them before polyfills
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './__workaround.node'; // temporary until 2.1.1 things are patched in Core
import * as path from 'path';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import * as morgan from 'morgan';
import * as compression from 'compression';
// Angular 2
import { enableProdMode } from '@angular/core';
// Angular 2 Universal
import { createEngine } from 'angular2-express-engine';
// App
import { MainModule } from './node.module';
// Routes
import { routes } from './server.routes';
// enable prod for faster renders
enableProdMode();
const app = express();
const ROOT = path.join(path.resolve(__dirname, '..'));
// Express View
app.engine('.html', createEngine({
ngModule: MainModule,
providers: [
// use only if you have shared state between users
// { provide: 'LRU', useFactory: () => new LRU(10) }
// stateless providers only since it's shared
]
}));
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname);
app.set('view engine', 'html');
app.set('json spaces', 2);
app.use(cookieParser('Angular 2 Universal'));
app.use(bodyParser.json());
app.use(compression());
app.use(morgan('dev'));
function cacheControl(req, res, next) {
// instruct browser to revalidate in 60 seconds
res.header('Cache-Control', 'max-age=60');
next();
}
// Serve static files
app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), {maxAge: 30}));
app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), {index: false}));
//
/////////////////////////
// ** Example API
// Notice API should be in aseparate process
import { serverApi, createTodoApi } from './backend/api';
// Our API for demos only
app.get('/data.json', serverApi);
app.use('/api', createTodoApi());
function ngApp(req, res) {
res.render('index', {
req,
res,
// time: true, // use this to determine what part of your app is slow only in development
preboot: false,
baseUrl: '/',
requestUrl: req.originalUrl,
originUrl: `http://localhost:${ app.get('port') }`
});
}
/**
* use universal for specific routes
*/
app.get('/', ngApp);
routes.forEach(route => {
app.get(`/${route}`, ngApp);
app.get(`/${route}/*`, ngApp);
});
app.get('*', function(req, res) {
res.setHeader('Content-Type', 'application/json');
var pojo = { status: 404, message: 'No Content' };
var json = JSON.stringify(pojo, null, 2);
res.status(404).send(json);
});
// Server
let server = app.listen(app.get('port'), () => {
console.log(`Listening on: http://localhost:${server.address().port}`);
});

73
src/typings.d.ts vendored Normal file
View File

@@ -0,0 +1,73 @@
/*
* Custom Type Definitions
* When including 3rd party modules you also need to include the type definition for the module
* if they don't provide one within the module. You can try to install it with typings
typings install node --save
* If you can't find the type definition in the registry we can make an ambient definition in
* this file for now. For example
declare module "my-module" {
export function doesSomething(value: string): string;
}
*
* If you're prototying and you will fix the types later you can also declare it as type any
*
declare var assert: any;
*
* If you're importing a module that uses Node.js modules which are CommonJS you need to import as
*
import * as _ from 'lodash'
* You can include your type definitions in this file until you create one for the typings registry
* see https://github.com/typings/registry
*
*/
// declare module '*'; // default type definitions for any for modules that are not found.
// caveat: if this is enabled and you do not have the proper module there may not be an error.
// suggestion: follow the pattern below with modern-lru which provides an alternative way to create an 'any' module.
// for legacy tslint etc to understand
declare module 'modern-lru' {
let x: any;
export = x;
}
declare var System: SystemJS;
interface SystemJS {
import: (path?: string) => Promise<any>;
}
// Extra variables that live on Global that will be replaced by webpack DefinePlugin
declare var ENV: string;
declare var HMR: boolean;
declare var Zone: {current: any};
interface GlobalEnvironment {
ENV;
HMR;
SystemJS: SystemJS;
System: SystemJS;
}
interface WebpackModule {
hot: {
data?: any,
idle: any,
accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void;
decline(dependencies?: string | string[]): void;
dispose(callback?: (data?: any) => void): void;
addDisposeHandler(callback?: (data?: any) => void): void;
removeDisposeHandler(callback?: (data?: any) => void): void;
check(autoApply?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
apply(options?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
status(callback?: (status?: string) => void): void | string;
removeStatusHandler(callback?: (status?: string) => void): void;
};
}
interface WebpackRequire {
context(file: string, flag?: boolean, exp?: RegExp): any;
}
// Extend typings
interface NodeRequire extends WebpackRequire {}
interface NodeModule extends WebpackModule {}
interface Global extends GlobalEnvironment {}

28
tsconfig.aot.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"sourceMap": true,
"sourceRoot": "src",
"noEmitHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": ["es6", "dom"]
},
"include": [
"./src/**/*.module.ts",
"./src/*.d.ts"
],
"angularCompilerOptions": {
"debug": false
},
"compileOnSave": false,
"buildOnSave": false,
"atom": { "rewriteTsconfig": false }
}

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"sourceMap": true,
"sourceRoot": "src",
"noEmitHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": ["es6", "dom"]
},
"exclude": [
"node_modules"
],
"angularCompilerOptions": {
"debug": false
},
"compileOnSave": false,
"buildOnSave": false,
"atom": { "rewriteTsconfig": false }
}

133
webpack.config.ts Normal file
View File

@@ -0,0 +1,133 @@
var webpack = require('webpack');
var path = require('path');
var clone = require('js.clone');
var webpackMerge = require('webpack-merge');
export var commonPlugins = [
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)src(\\|\/)linker/,
root('./src'),
{
// your Angular Async Route paths relative to this root directory
}
),
// Loader options
new webpack.LoaderOptionsPlugin({
}),
];
export var commonConfig = {
// https://webpack.github.io/docs/configuration.html#devtool
devtool: 'source-map',
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [ root('node_modules') ]
},
context: __dirname,
output: {
publicPath: '',
filename: '[name].bundle.js'
},
module: {
rules: [
// TypeScript
{ test: /\.ts$/, use: ['awesome-typescript-loader', 'angular2-template-loader'] },
{ test: /\.html$/, use: 'raw-loader' },
{ test: /\.css$/, use: 'raw-loader' },
{ test: /\.json$/, use: 'json-loader' }
],
},
plugins: [
// Use commonPlugins.
]
};
// Client.
export var clientPlugins = [
];
export var clientConfig = {
target: 'web',
entry: './src/client',
output: {
path: root('dist/client')
},
node: {
global: true,
crypto: 'empty',
__dirname: true,
__filename: true,
process: true,
Buffer: false
}
};
// Server.
export var serverPlugins = [
];
export var serverConfig = {
target: 'node',
entry: './src/server', // use the entry file of the node server if everything is ts rather than es5
output: {
filename: 'index.js',
path: root('dist/server'),
libraryTarget: 'commonjs2'
},
module: {
rules: [
{ test: /@angular(\\|\/)material/, use: "imports-loader?window=>global" }
],
},
externals: includeClientPackages(
/@angularclass|@angular|angular2-|ng2-|ng-|@ng-|angular-|@ngrx|ngrx-|@angular2|ionic|@ionic|-angular2|-ng2|-ng/
),
node: {
global: true,
crypto: true,
__dirname: true,
__filename: true,
process: true,
Buffer: true
}
};
export default [
// Client
webpackMerge(clone(commonConfig), clientConfig, { plugins: clientPlugins.concat(commonPlugins) }),
// Server
webpackMerge(clone(commonConfig), serverConfig, { plugins: serverPlugins.concat(commonPlugins) })
];
// Helpers
export function includeClientPackages(packages, localModule?: string[]) {
return function(context, request, cb) {
if (localModule instanceof RegExp && localModule.test(request)) {
return cb();
}
if (packages instanceof RegExp && packages.test(request)) {
return cb();
}
if (Array.isArray(packages) && packages.indexOf(request) !== -1) {
return cb();
}
if (!path.isAbsolute(request) && request.charAt(0) !== '.') {
return cb(null, 'commonjs ' + request);
}
return cb();
};
}
export function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [__dirname].concat(args));
}

178
webpack.prod.config.ts Normal file
View File

@@ -0,0 +1,178 @@
const webpack = require('webpack');
const path = require('path');
const clone = require('js.clone');
const webpackMerge = require('webpack-merge');
const V8LazyParseWebpackPlugin = require('v8-lazy-parse-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
import webpackConfig, { root, includeClientPackages } from './webpack.config';
// const CompressionPlugin = require('compression-webpack-plugin');
export const commonPlugins = [
new V8LazyParseWebpackPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production'),
'AOT': true
}
}),
// Loader options
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.NormalModuleReplacementPlugin(
/facade(\\|\/)async/,
root('node_modules/@angular/core/src/facade/async.js')
),
new webpack.NormalModuleReplacementPlugin(
/facade(\\|\/)collection/,
root('node_modules/@angular/core/src/facade/collection.js')
),
new webpack.NormalModuleReplacementPlugin(
/facade(\\|\/)errors/,
root('node_modules/@angular/core/src/facade/errors.js')
),
new webpack.NormalModuleReplacementPlugin(
/facade(\\|\/)lang/,
root('node_modules/@angular/core/src/facade/lang.js')
),
new webpack.NormalModuleReplacementPlugin(
/facade(\\|\/)math/,
root('node_modules/@angular/core/src/facade/math.js')
),
];
export const commonConfig = {
output: {
filename: '[name].bundle.js',
chunkFilename: '[chunkhash].js'
},
};
// Client.
export const clientPlugins = [
new BundleAnalyzerPlugin({
analyzerMode: 'disabled', // change it to `server` to view bundle stats
reportFilename: 'report.html',
generateStatsFile: true,
statsFilename: 'stats.json',
}),
// To use gzip, you can run 'npm install compression-webpack-plugin --save-dev'
// add 'var CompressionPlugin = require("compression-webpack-plugin");' on the top
// and comment out below codes
//
// new CompressionPlugin({
// asset: "[path].gz[query]",
// algorithm: "gzip",
// test: /\.js$|\.css$|\.html$/,
// threshold: 10240,
// minRatio: 0.8
// }),
new webpack.optimize.UglifyJsPlugin({
// beautify: true,
// mangle: false,
output: {
comments: false
},
compress: {
warnings: false,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true,
negate_iife: false // we need this for lazy v8
},
sourceMap: true
}),
new webpack.NormalModuleReplacementPlugin(
/@angular(\\|\/)upgrade/,
root('empty.js')
),
// problem with platformUniversalDynamic on the server/client
new webpack.NormalModuleReplacementPlugin(
/@angular(\\|\/)compiler/,
root('empty.js')
),
new webpack.NormalModuleReplacementPlugin(
/@angular(\\|\/)platform-browser-dynamic/,
root('empty.js')
),
new webpack.NormalModuleReplacementPlugin(
/dom(\\|\/)debug(\\|\/)ng_probe/,
root('empty.js')
),
new webpack.NormalModuleReplacementPlugin(
/dom(\\|\/)debug(\\|\/)by/,
root('empty.js')
),
new webpack.NormalModuleReplacementPlugin(
/src(\\|\/)debug(\\|\/)debug_node/,
root('empty.js')
),
new webpack.NormalModuleReplacementPlugin(
/src(\\|\/)debug(\\|\/)debug_renderer/,
root('empty.js')
),
// Waiting for https://github.com/ampedandwired/html-webpack-plugin/issues/446
// new webpack.optimize.AggressiveSplittingPlugin({
// minSize: 30000,
// maxSize: 250000
// }),
];
export const clientConfig = {
entry: './src/client.aot',
recordsOutputPath: root('webpack.records.json')
};
// Server.
export const serverPlugins = [
new webpack.optimize.UglifyJsPlugin({
// beautify: true,
mangle: false, // to ensure process.env still works
output: {
comments: false
},
compress: {
warnings: false,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true,
negate_iife: false // we need this for lazy v8
},
sourceMap: true
}),
];
export const serverConfig = {
entry: './src/server.aot',
output: {
filename: 'index.js',
chunkFilename: '[id].bundle.js',
crossOriginLoading: false
},
};
export default [
// Client
webpackMerge(webpackConfig[0], clone(commonConfig), clientConfig, {plugins: webpackConfig[0].plugins.concat(commonPlugins, clientPlugins) }),
// Server
webpackMerge(webpackConfig[1], clone(commonConfig), serverConfig, {plugins: webpackConfig[1].plugins.concat(commonPlugins, serverPlugins) })
];