diff --git a/src/Microsoft.AspNetCore.SpaServices/README.md b/src/Microsoft.AspNetCore.SpaServices/README.md new file mode 100644 index 0000000..39d0806 --- /dev/null +++ b/src/Microsoft.AspNetCore.SpaServices/README.md @@ -0,0 +1,431 @@ +# Microsoft.AspNetCore.SpaServices + +If you're building an ASP.NET Core application, and want to use Angular 2, React, Knockout, or another single-page app (SPA) framework, this NuGet package contains useful infrastructure for you. + +This package enables: + + * [**Server-side prerendering**](#server-sideprerendering) for *universal* (a.k.a. *isomorphic*) applications, where your Angular 2 / React / etc. components are first rendered on the server, and then transferred to the client where execution continues + * [**Webpack middleware**](#webpack-dev-middleware) so that, during development, any webpack-build resources will be generated on demand, without you having to run webpack manually or compile files to disk + * [**Hot module replacement**](#webpack-hot-module-replacement) so that, during development, your code and markup changes will be sent to your browser and updated in the running application, without even needing to reload the page + * [**Routing helpers**](#routing-helper-mapspafallbackroute) for integrating server-side routing with client-side routing + +Behind the scenes, it uses the [`Microsoft.AspNetCore.NodeServices`](https://github.com/aspnet/JavaScriptServices/tree/master/src/Microsoft.AspNetCore.NodeServices) package as a fast and robust way to invoke Node.js-hosted code from ASP.NET Core at runtime. + +### Requirements + +* [Node.js](https://nodejs.org/en/) + * To test this is installed and can be found, run `node -v` on a command line + * Note: If you're deploying to an Azure web site, you don't need to do anything here - Node is already installed and available in the server environments +* [.NET Core](https://dot.net), version 1.0 RC2 or later + +### Installation into existing projects + + * Add `Microsoft.AspNetCore.SpaServices` to the dependencies list in your `project.json` file + * Run `dotnet restore` (or if you use Visual Studio, just wait a moment - it will restore dependencies automatically) + * Install supporting NPM packages for the features you'll be using: + * If you'll be using server-side prerendering, install `aspnet-prerendering`, plus `aspnet-webpack` if your SPA will be build using webpack (e.g., run `npm install --save aspnet-prerendering aspnet-webpack`) + * If you'll be using webpack dev middleware, install `aspnet-webpack` (e.g., run `npm install --save aspnet-webpack`) + + +### Creating entirely new projects + +If you're starting from scratch, you might prefer to use the `aspnetcore-spa` Yeoman generator to get a ready-to-go starting point using your choice of client-side framework. This includes `Microsoft.AspNetCore.SpaServices` along with everything configured for webpack middleware, server-side prerendering, etc. + +See: [Getting started with the aspnetcore-spa generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/) + +## Server-side prerendering + +The `SpaServices` package isn't tied to any particular client-side framework, and it doesn't force you to set up your client-side application in any one particular style. So, `SpaServices` doesn't contain hard-coded logic for rendering Angular 2 / React / etc. components. + +Instead, what `SpaServices` offers is ASP.NET Core APIs that know how to invoke a JavaScript function that you supply, passing through context information that you'll need for server-side prerendering, and then injects the resulting HTML string into your rendered page. In this document, you'll find examples of setting this up to render Angular 2 and React components. + +### 1. Enable the asp-prerender-* tag helpers + +Make sure you've installed the `Microsoft.AspNetCore.SpaServices` NuGet package and the `aspnet-prerendering` NPM package. Together these contain the server-side and client-side library code you'll need. + +Now go to your `Views/_ViewImports.cshtml` file, and add the following line: + + @addTagHelper "*, Microsoft.AspNetCore.SpaServices" + +### 2. Use asp-prerender-* in a view + +Choose a place in one of your MVC views where you want to prerender a SPA component. For example, open `Views/Home/Index.cshtml`, and add markup like the following: + +
+ +If you run your application now, and browse to whatever page renders the view you just edited, you should get an error similar to the following (assuming you're running in *Development* mode so you can see the error information): *Error: Cannot find module 'some/directory/ClientApp/boot-server'*. You've told the prerendering tag helper to execute code from a JavaScript module called `boot-server`, but haven't yet supplied any such module! + +### 3. Supplying JavaScript code to perform prerendering + +Create a JavaScript file at the path matching the `asp-prerender-module` value you specified above. In this example, that means creating a folder called `ClientApp` at the root of your project, and creating a file inside it called `boot-server.js`. Try putting the following into it: + +``` +module.exports = function(params) { + return new Promise(function (resolve, reject) { + var result = 'Current time in Node is: ' + new Date() + '
' + + 'Request path is: ' + params.location.path + '
' + + 'Absolute URL is: ' + params.absoluteUrl + '
'; + + resolve({ html: result }); + }); +}; +``` + +If you try running your app now, you should see the HTML snippet generated by your JavaScript getting injected into your page. + +As you can see, your JavaScript code receives context information (such as the URL being requested), and returns a `Promise` so that it can asynchronously supply the markup to be injected into the page. You can put whatever logic you like here, but typically you'll want to execute a component from your Angular 2 / React / etc. application. + +**Passing data from server-side to client-side code** + +If you want to pass some contextual data from your server-side code to your client-side code (for example, to avoid having to load the same data you just loaded on the server again on the client), you can supply a `globals` object alongside the initial `html`, e.g.: + + resolve({ + html: result, + globals: { + albumsList: someDataHere, + userData: someMoreDataHere + } + }); + +When the `aspnet-prerender-*` tag helper emits this result into the document, as well as injecting the `html` string, it will also emit code that populates `window.albumsList` and `window.userData` with JSON-serialized copies of the objects you passed. + +### 4. Enabling webpack build tooling + +Of course, rather than writing your `boot-server` module and your entire SPA in plain ES5 JavaScript, it's quite likely that you'll want to write your client-side code in TypeScript or at least ES2015 code. To enable this, you can either: + + * Set up some build tool such as Babel to transpile to ES5, and always remember to run this to generate plain ES5 `.js` files before you run your application + * Or, more conveniently, use [webpack](https://webpack.github.io/) along with the `asp-prerender-webpack-config` attribute so that `Microsoft.AspNetCore.SpaServices` can automatically build your boot module and the SPA code that it references. Then there's no need for `.js` files even to be written to disk - the build process is all dynamic and in memory. + +To enable webpack builds for your server-side prerendering, amend your MVC view to specify the location of your webpack configuration file using an `asp-prerender-webpack-config` attribute, e.g.: + + + +You'll also need to install the NPM module `aspnet-webpack` if you don't have it already, e.g.: + + npm install --save aspnet-webpack + +This includes webpack as well as the server-side code needed to invoke it from ASP.NET Core at runtime. + +Now, assuming you have a working webpack configuration at `webpack.config.js`, your boot module and SPA code will dynamically be built using webpack. + +#### Example: Configuring webpack to build TypeScript + +Let's say you want to write your boot module and SPA code in TypeScript. First ensure that `aspnet-webpack` is installed, along with the libraries needed for TypeScript compilation: + + npm install --save aspnet-webpack ts-loader typescript + +Next, create a file `webpack.config.js` at the root of your project, containing: + +``` +module.exports = { + resolve: { extensions: [ '', '.js', '.ts' ] }, + module: { + loaders: [ + { test: /\.ts$/, loader: 'ts-loader' } + ] + } +}; +``` + +This tells webpack that it should compile `.ts` files using TypeScript, and that when looking for modules by name (e.g., `boot-server`), it should also find files with `.js` and `.ts` extensions. + +Now you can delete `ClientApp/boot-server.js`, and in its place, create `ClientApp/boot-server.ts`, containing the TypeScript equivalent of what you had before: + +``` +export default function (params: any): Promise<{ html: string}> { + return new Promise((resolve, reject) => { + const html = ` +Current time in Node is: ${ new Date() }
+Request path is: ${ params.location.path }
+Absolute URL is: ${ params.absoluteUrl }
`; + + resolve({ html }); + }); +} +``` + +Finally, you can tell `SpaServices` to use the Webpack environment you've just set up. In your MVC view where you use `aspnet-prerender-module`, also specify `aspnet-prerender-webpack-config`: + + + +Now your `boot-server.ts` code should get executed when your ASP.NET Core page is rendered, and since it's TypeScript, it can of course reference any other TypeScript modules, which means your entire SPA can be written in TypeScript and executed on the server. + +Webpack is a broad and powerful tool and can do far more than just invoke the TypeScript compiler. To learn more, see the [webpack website](https://webpack.github.io/). + + +### 5(a). Prerendering Angular 2 components + +If you're building an Angular 2 application, you can run your components in the server inside your `boot-server.ts` file so they will be injected into the resulting web page. + +First install the NPM package `angular2-universal` - this contains infrastructure for executing Angular 2 components on the server: + +``` +npm install --save angular2-universal +``` + +Now you can use the [`angular2-universal` APIs](https://github.com/angular/universal) from your `boot-server.ts` TypeScript module to execute your Angular 2 component on the server. The code needed for this is fairly complex, but is necessary because Angular 2 supports so many different ways of being configured, and you need to provide wiring for whatever combination of DI modules you're using. + +You can find an example `boot-server.ts` that renders arbitrary Angular 2 components [here](https://github.com/aspnet/JavaScriptServices/blob/master/templates/Angular2Spa/ClientApp/boot-server.ts). If you use this with your own application, remember to reference any other DI services that your Angular 2 component depends on. + +The easiest way to get started with Angular 2 server-side rendering on ASP.NET Core is to use the [aspnetcore-spa generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/), which creates a ready-made working starting point. + +### 5(b). Prerendering React components + +React components can be executed synchronously on the server quite easily, although asynchronous execution is tricker as described below. + +#### Setting up client-side React code + +Let's say you want to write a React component in ES2015 code. You might install the NPM modules `react react-dom babel-loader babel-preset-react babel-preset-es2015`, and then prepare Webpack to build `.jsx` files by creating `webpack.config.js` in your project root, containing: + +``` +var path = require('path'); + +module.exports = { + resolve: { extensions: [ '', '.js', '.jsx' ] }, + module: { + loaders: [ + { test: /\.jsx?$/, loader: 'babel-loader' } + ] + }, + entry: { + main: ['./ClientApp/react-app.jsx'], + }, + output: { + path: path.join(__dirname, 'wwwroot', 'dist'), + filename: '[name].js' + }, +}; +``` + +You will also need a `.babelrc` file in your project root, containing: + +``` +{ + "presets": ["es2015", "react"] +} +``` + +This is enough to be able to build ES2015 `.jsx` files via Webpack. Now you could implement a simple React component, for example the following at `ClientApp/react-app.jsx`: + +``` +import * as React from 'react'; + +export class HelloMessage extends React.Component +{ + render() { + return