diff --git a/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js b/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js
index e448941..dd5d169 100644
--- a/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js
+++ b/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js
@@ -31,7 +31,7 @@ function loadViaTypeScript(module, filename) {
// First perform a minimal transpilation from TS code to ES2015. This is very fast (doesn't involve type checking)
// and is unlikely to need any special compiler options
var src = fs.readFileSync(filename, 'utf8');
- var compilerOptions = { jsx: ts.JsxEmit.Preserve, module: ts.ModuleKind.ES2015, target: ts.ScriptTarget.ES6 };
+ var compilerOptions = { jsx: ts.JsxEmit.Preserve, module: ts.ModuleKind.ES2015, target: ts.ScriptTarget.ES6, emitDecoratorMetadata: true };
var es6Code = ts.transpile(src, compilerOptions, 'test.tsx', /* diagnostics */ []);
// Second, process the ES2015 via Babel. We have to do this (instead of going directly from TS to ES5) because
@@ -116,6 +116,7 @@ function renderToString(callback, bootModulePath, bootModuleExport, absoluteRequ
var params = {
location: url.parse(requestPathAndQuery),
url: requestPathAndQuery,
+ absoluteUrl: absoluteRequestUrl,
domainTasks: domainTaskCompletionPromise
};
diff --git a/templates/Angular2Spa/ClientApp/boot.ts b/templates/Angular2Spa/ClientApp/boot-client.ts
similarity index 100%
rename from templates/Angular2Spa/ClientApp/boot.ts
rename to templates/Angular2Spa/ClientApp/boot-client.ts
diff --git a/templates/Angular2Spa/ClientApp/boot-server.ts b/templates/Angular2Spa/ClientApp/boot-server.ts
new file mode 100644
index 0000000..4f6c591
--- /dev/null
+++ b/templates/Angular2Spa/ClientApp/boot-server.ts
@@ -0,0 +1,32 @@
+import 'angular2-universal-preview/dist/server/universal-polyfill.js';
+import * as ngCore from 'angular2/core';
+import * as ngRouter from 'angular2/router';
+import * as ngUniversal from 'angular2-universal-preview';
+import { BASE_URL } from 'angular2-universal-preview/dist/server/src/http/node_http';
+import * as ngUniversalRender from 'angular2-universal-preview/dist/server/src/render';
+
+// TODO: Make this ugly code go away, e.g., by somehow loading via Webpack
+function loadAsString(module, filename) {
+ module.exports = require('fs').readFileSync(filename, 'utf8');
+}
+(require as any).extensions['.html'] = loadAsString;
+(require as any).extensions['.css'] = loadAsString;
+let App: any = require('./components/app/app').App;
+
+export default function (params: any): Promise<{ html: string }> {
+ return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => {
+ const serverBindings = [
+ ngRouter.ROUTER_BINDINGS,
+ ngUniversal.HTTP_PROVIDERS,
+ ngCore.provide(BASE_URL, { useValue: params.absoluteUrl }),
+ ngCore.provide(ngUniversal.REQUEST_URL, { useValue: params.url }),
+ ngCore.provide(ngRouter.APP_BASE_HREF, { useValue: '/' }),
+ ngUniversal.SERVER_LOCATION_PROVIDERS
+ ];
+
+ ngUniversalRender.renderToString(App, serverBindings).then(
+ html => resolve({ html, globals: {} }),
+ reject // Also propagate any errors back into the host application
+ );
+ });
+}
diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts
index 111b5b3..003ff01 100644
--- a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts
+++ b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts
@@ -1,5 +1,5 @@
import * as ng from 'angular2/core';
-import fetch from 'isomorphic-fetch';
+import { Http } from 'angular2/http';
@ng.Component({
selector: 'fetch-data'
@@ -10,12 +10,10 @@ import fetch from 'isomorphic-fetch';
export class FetchData {
public forecasts: WeatherForecast[];
- constructor() {
- fetch('/api/SampleData/WeatherForecasts')
- .then(response => response.json())
- .then((data: WeatherForecast[]) => {
- this.forecasts = data;
- });
+ constructor(http: Http) {
+ http.get('/api/SampleData/WeatherForecasts').subscribe(result => {
+ this.forecasts = result.json();
+ });
}
}
diff --git a/templates/Angular2Spa/ClientApp/components/home/home.html b/templates/Angular2Spa/ClientApp/components/home/home.html
index 8988e6f..4461140 100644
--- a/templates/Angular2Spa/ClientApp/components/home/home.html
+++ b/templates/Angular2Spa/ClientApp/components/home/home.html
@@ -9,6 +9,7 @@
To help you get started, we've also set up:
- Client-side navigation. For example, click Counter then Back to return here.
+ - Server-side prerendering. For faster initial loading and improved SEO, your Angular 2 app is prerendered on the server. The resulting HTML is then transferred to the browser where a client-side copy of the app takes over.
- Webpack dev middleware. In development mode, there's no need to run the
webpack build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.
- Hot module replacement. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, your Angular 2 app will be rebuilt and a new instance injected is into the page.
- Efficient production builds. In production mode, development-time features are disabled, and the
webpack build tool produces minified static CSS and JavaScript files.
diff --git a/templates/Angular2Spa/Views/Home/Index.cshtml b/templates/Angular2Spa/Views/Home/Index.cshtml
index 693b74f..82ab91b 100644
--- a/templates/Angular2Spa/Views/Home/Index.cshtml
+++ b/templates/Angular2Spa/Views/Home/Index.cshtml
@@ -2,7 +2,7 @@
ViewData["Title"] = "Home Page";
}
-Loading...
+Loading...
@section scripts {
diff --git a/templates/Angular2Spa/Views/_ViewImports.cshtml b/templates/Angular2Spa/Views/_ViewImports.cshtml
index 7252adc..3d6c9a3 100755
--- a/templates/Angular2Spa/Views/_ViewImports.cshtml
+++ b/templates/Angular2Spa/Views/_ViewImports.cshtml
@@ -1,2 +1,3 @@
@using WebApplicationBasic
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
+@addTagHelper "*, Microsoft.AspNet.SpaServices"
diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json
index d864e55..bd379d7 100644
--- a/templates/Angular2Spa/package.json
+++ b/templates/Angular2Spa/package.json
@@ -25,10 +25,15 @@
},
"dependencies": {
"angular2": "^2.0.0-beta.7",
+ "angular2-universal-preview": "^0.55.4",
"babel-core": "^6.5.2",
+ "css": "^2.2.1",
+ "domain-task": "^1.0.0",
"es6-shim": "^0.33.13",
"es7-reflect-metadata": "^1.5.5",
"isomorphic-fetch": "^2.2.1",
+ "parse5": "^1.5.1",
+ "preboot": "^1.1.3",
"reflect-metadata": "^0.1.2",
"rxjs": "^5.0.0-beta.2",
"zone.js": "^0.5.15"
diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json
index 361d496..f1782f7 100644
--- a/templates/Angular2Spa/tsconfig.json
+++ b/templates/Angular2Spa/tsconfig.json
@@ -3,7 +3,8 @@
"moduleResolution": "node",
"target": "es6",
"sourceMap": true,
- "experimentalDecorators": true
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
},
"exclude": [
"node_modules"
diff --git a/templates/Angular2Spa/tsd.json b/templates/Angular2Spa/tsd.json
index 7520fb8..79fb09e 100644
--- a/templates/Angular2Spa/tsd.json
+++ b/templates/Angular2Spa/tsd.json
@@ -7,9 +7,6 @@
"installed": {
"requirejs/require.d.ts": {
"commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7"
- },
- "isomorphic-fetch/isomorphic-fetch.d.ts": {
- "commit": "4d2d0653003a4d1df4d7c68054cce4f4ace58bdf"
}
}
}
diff --git a/templates/Angular2Spa/typings/isomorphic-fetch/isomorphic-fetch.d.ts b/templates/Angular2Spa/typings/isomorphic-fetch/isomorphic-fetch.d.ts
deleted file mode 100644
index 84e4c4b..0000000
--- a/templates/Angular2Spa/typings/isomorphic-fetch/isomorphic-fetch.d.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-// Type definitions for isomorphic-fetch
-// Project: https://github.com/matthew-andrews/isomorphic-fetch
-// Definitions by: Todd Lucas
-// Definitions: https://github.com/borisyankov/DefinitelyTyped
-
-declare enum RequestContext {
- "audio", "beacon", "cspreport", "download", "embed", "eventsource",
- "favicon", "fetch", "font", "form", "frame", "hyperlink", "iframe",
- "image", "imageset", "import", "internal", "location", "manifest",
- "object", "ping", "plugin", "prefetch", "script", "serviceworker",
- "sharedworker", "subresource", "style", "track", "video", "worker",
- "xmlhttprequest", "xslt"
-}
-declare enum RequestMode { "same-origin", "no-cors", "cors" }
-declare enum RequestCredentials { "omit", "same-origin", "include" }
-declare enum RequestCache {
- "default", "no-store", "reload", "no-cache", "force-cache",
- "only-if-cached"
-}
-declare enum ResponseType { "basic", "cors", "default", "error", "opaque" }
-
-declare type HeaderInit = Headers | Array;
-declare type BodyInit = Blob | FormData | string;
-declare type RequestInfo = Request | string;
-
-interface RequestInit {
- method?: string;
- headers?: HeaderInit | { [index: string]: string };
- body?: BodyInit;
- mode?: string | RequestMode;
- credentials?: string | RequestCredentials;
- cache?: string | RequestCache;
-}
-
-interface IHeaders {
- get(name: string): string;
- getAll(name: string): Array;
- has(name: string): boolean;
-}
-
-declare class Headers implements IHeaders {
- append(name: string, value: string): void;
- delete(name: string):void;
- get(name: string): string;
- getAll(name: string): Array;
- has(name: string): boolean;
- set(name: string, value: string): void;
-}
-
-interface IBody {
- bodyUsed: boolean;
- arrayBuffer(): Promise;
- blob(): Promise;
- formData(): Promise;
- json(): Promise;
- json(): Promise;
- text(): Promise;
-}
-
-declare class Body implements IBody {
- bodyUsed: boolean;
- arrayBuffer(): Promise;
- blob(): Promise;
- formData(): Promise;
- json(): Promise;
- json(): Promise;
- text(): Promise;
-}
-
-interface IRequest extends IBody {
- method: string;
- url: string;
- headers: Headers;
- context: string | RequestContext;
- referrer: string;
- mode: string | RequestMode;
- credentials: string | RequestCredentials;
- cache: string | RequestCache;
-}
-
-declare class Request extends Body implements IRequest {
- constructor(input: string | Request, init?: RequestInit);
- method: string;
- url: string;
- headers: Headers;
- context: string | RequestContext;
- referrer: string;
- mode: string | RequestMode;
- credentials: string | RequestCredentials;
- cache: string | RequestCache;
-}
-
-interface IResponse extends IBody {
- url: string;
- status: number;
- statusText: string;
- ok: boolean;
- headers: IHeaders;
- type: string | ResponseType;
- size: number;
- timeout: number;
- redirect(url: string, status: number): IResponse;
- error(): IResponse;
- clone(): IResponse;
-}
-
-interface IFetchStatic {
- Promise: any;
- Headers: IHeaders
- Request: IRequest;
- Response: IResponse;
- (url: string | IRequest, init?: RequestInit): Promise;
-}
-
-declare module "isomorphic-fetch" {
- export default IFetchStatic;
-}
-
-declare var fetch: IFetchStatic;
diff --git a/templates/Angular2Spa/typings/tsd.d.ts b/templates/Angular2Spa/typings/tsd.d.ts
index 707ed4f..e7d781f 100644
--- a/templates/Angular2Spa/typings/tsd.d.ts
+++ b/templates/Angular2Spa/typings/tsd.d.ts
@@ -1,3 +1,2 @@
///
-///
diff --git a/templates/Angular2Spa/webpack.config.js b/templates/Angular2Spa/webpack.config.js
index 44d5d5f..7cd64e5 100644
--- a/templates/Angular2Spa/webpack.config.js
+++ b/templates/Angular2Spa/webpack.config.js
@@ -19,7 +19,7 @@ module.exports = merge({
]
},
entry: {
- main: ['./ClientApp/boot.ts'],
+ main: ['./ClientApp/boot-client.ts'],
vendor: ['angular2/bundles/angular2-polyfills.js', 'bootstrap', 'bootstrap/dist/css/bootstrap.css', 'style-loader', 'jquery', 'angular2/core', 'angular2/common', 'angular2/http', 'angular2/router', 'angular2/platform/browser']
},
output: {