From 47ba2519236e841affabe23a33dfb6e740586e9d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 29 Feb 2016 19:29:04 +0000 Subject: [PATCH] Add server-side prerendering for Angular 2 template --- .../Content/Node/prerenderer.js | 3 +- .../ClientApp/{boot.ts => boot-client.ts} | 0 .../Angular2Spa/ClientApp/boot-server.ts | 32 +++++ .../components/fetch-data/fetch-data.ts | 12 +- .../ClientApp/components/home/home.html | 1 + templates/Angular2Spa/Views/Home/Index.cshtml | 2 +- .../Angular2Spa/Views/_ViewImports.cshtml | 1 + templates/Angular2Spa/package.json | 5 + templates/Angular2Spa/tsconfig.json | 3 +- templates/Angular2Spa/tsd.json | 3 - .../isomorphic-fetch/isomorphic-fetch.d.ts | 119 ------------------ templates/Angular2Spa/typings/tsd.d.ts | 1 - templates/Angular2Spa/webpack.config.js | 2 +- 13 files changed, 50 insertions(+), 134 deletions(-) rename templates/Angular2Spa/ClientApp/{boot.ts => boot-client.ts} (100%) create mode 100644 templates/Angular2Spa/ClientApp/boot-server.ts delete mode 100644 templates/Angular2Spa/typings/isomorphic-fetch/isomorphic-fetch.d.ts diff --git a/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js b/src/Microsoft.AspNet.SpaServices/Content/Node/prerenderer.js index e44894144e..dd5d169418 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 0000000000..4f6c591c8a --- /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 111b5b3240..003ff01481 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 8988e6f745..4461140782 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: