mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
initial commit
This commit is contained in:
15
.editorconfig
Normal file
15
.editorconfig
Normal 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
20
.gitignore
vendored
Normal 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
46
.vscode/launch.json
vendored
Normal 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
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.check.workspaceVersion": false
|
||||||
|
}
|
33
README.md
Normal file
33
README.md
Normal 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
12
app.json
Normal 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
7
empty.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
NgProbeToken: {},
|
||||||
|
_createConditionalRootRenderer: function(rootRenderer, extraTokens, coreTokens) {
|
||||||
|
return rootRenderer;
|
||||||
|
},
|
||||||
|
__platform_browser_private__: {}
|
||||||
|
};
|
7
nodemon.json
Normal file
7
nodemon.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"watch": [
|
||||||
|
"dist",
|
||||||
|
"src/index.html"
|
||||||
|
],
|
||||||
|
"ext" : "js ts json html"
|
||||||
|
}
|
98
package.json
Normal file
98
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
13
src/+app/+about/about-routing.module.ts
Normal file
13
src/+app/+about/about-routing.module.ts
Normal 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 { }
|
14
src/+app/+about/about.component.ts
Normal file
14
src/+app/+about/about.component.ts
Normal 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)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
src/+app/+about/about.module.ts
Normal file
16
src/+app/+about/about.module.ts
Normal 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 { }
|
13
src/+app/+home/home-routing.module.ts
Normal file
13
src/+app/+home/home-routing.module.ts
Normal 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 { }
|
8
src/+app/+home/home.component.css
Normal file
8
src/+app/+home/home.component.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
blockquote {
|
||||||
|
border-left:5px #158126 solid;
|
||||||
|
background:#fff;
|
||||||
|
padding:20px 20px 20px 40px;
|
||||||
|
}
|
||||||
|
blockquote::before {
|
||||||
|
left: 1em;
|
||||||
|
}
|
6
src/+app/+home/home.component.html
Normal file
6
src/+app/+home/home.component.html
Normal 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>
|
27
src/+app/+home/home.component.ts
Normal file
27
src/+app/+home/home.component.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/+app/+home/home.module.ts
Normal file
16
src/+app/+home/home.module.ts
Normal 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 { }
|
13
src/+app/+lazy/lazy-routing.module.ts
Normal file
13
src/+app/+lazy/lazy-routing.module.ts
Normal 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 { }
|
14
src/+app/+lazy/lazy.component.ts
Normal file
14
src/+app/+lazy/lazy.component.ts
Normal 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 {
|
||||||
|
}
|
16
src/+app/+lazy/lazy.module.ts
Normal file
16
src/+app/+lazy/lazy.module.ts
Normal 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 { }
|
13
src/+app/+todo/todo-routing.module.ts
Normal file
13
src/+app/+todo/todo-routing.module.ts
Normal 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 { }
|
43
src/+app/+todo/todo.component.ts
Normal file
43
src/+app/+todo/todo.component.ts
Normal 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/+app/+todo/todo.module.ts
Normal file
16
src/+app/+todo/todo.module.ts
Normal 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 { }
|
17
src/+app/app-routing.module.ts
Normal file
17
src/+app/app-routing.module.ts
Normal 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
66
src/+app/app.component.ts
Normal 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
27
src/+app/app.module.ts
Executable 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';
|
29
src/+app/shared/api.service.ts
Normal file
29
src/+app/shared/api.service.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
88
src/+app/shared/cache.service.ts
Normal file
88
src/+app/shared/cache.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
55
src/+app/shared/model/model.service.ts
Normal file
55
src/+app/shared/model/model.service.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
52
src/+app/shared/shared.module.ts
Normal file
52
src/+app/shared/shared.module.ts
Normal 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
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
21
src/__workaround.browser.ts
Normal file
21
src/__workaround.browser.ts
Normal 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
44
src/__workaround.node.ts
Normal 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
194
src/angular2-meta.ts
Normal 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
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
106
src/backend/api.ts
Normal file
106
src/backend/api.ts
Normal 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
17
src/backend/cache.ts
Normal 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
7
src/backend/db.ts
Normal 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
97
src/browser.module.ts
Executable 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
36
src/client.aot.ts
Normal 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
34
src/client.ts
Normal 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
23
src/index.html
Normal 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
73
src/node.module.ts
Executable 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
147
src/server.aot.ts
Normal 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
17
src/server.routes.ts
Normal 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
103
src/server.ts
Normal 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
73
src/typings.d.ts
vendored
Normal 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
28
tsconfig.aot.json
Normal 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
27
tsconfig.json
Normal 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
133
webpack.config.ts
Normal 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
178
webpack.prod.config.ts
Normal 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) })
|
||||||
|
];
|
Reference in New Issue
Block a user