Compare commits

..

1 Commits

Author SHA1 Message Date
SteveSandersonMS
df5d27d0d8 Example of Angular 2 lazy loading to help with #465 2017-03-29 13:34:04 +01:00
130 changed files with 800 additions and 851 deletions

View File

@@ -46,7 +46,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KnockoutSpa", "templates\KnockoutSpa\KnockoutSpa.csproj", "{F60248B1-940E-43FB-BEA0-589362AA6320}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngularSpa", "templates\AngularSpa\AngularSpa.csproj", "{4D4B84C9-13F7-40CA-B05A-DC98FD6019AC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Angular2Spa", "templates\Angular2Spa\Angular2Spa.csproj", "{4D4B84C9-13F7-40CA-B05A-DC98FD6019AC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AureliaSpa", "templates\AureliaSpa\AureliaSpa.csproj", "{4D57B6E1-7141-48ED-959E-872BDD4A2F72}"
EndProject

View File

@@ -6,13 +6,7 @@ This project is part of ASP.NET Core. You can find samples, documentation and ge
## What is this?
`JavaScriptServices` is a set of client-side technologies for ASP.NET Core. It provides infrastructure that you'll find useful if you:
- Use Angular / React / Vue / Aurelia / Knockout / etc.
- Build your client-side resources using Webpack.
- Execute JavaScript on the server at runtime.
Read [Building Single Page Applications on ASP.NET Core with JavaScriptServices](https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/) for more details.
`JavaScriptServices` is a set of technologies for ASP.NET Core developers. It provides infrastructure that you'll find useful if you use Angular 2 / React / Knockout / etc. on the client, or if you build your client-side resources using Webpack, or otherwise want to execute JavaScript on the server at runtime.
This repo contains:
@@ -23,26 +17,27 @@ This repo contains:
* Hot module replacement (HMR) ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#webpack-hot-module-replacement))
* Server-side and client-side routing integration ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#routing-helper-mapspafallbackroute))
* Server-side and client-side validation integration
* "Cache priming" for Angular 2 apps
* "Lazy loading" for Knockout apps
* A Yeoman generator that creates preconfigured app starting points ([guide](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/))
* Samples and docs
It's cross-platform (Windows, Linux, or macOS) and works with .NET Core 1.0.1 or later.
Everything here is cross-platform, and works with .NET Core 1.0.1 or later on Windows, Linux, or OS X.
## Creating new applications
If you want to build a brand-new ASP.NET Core app that uses Angular / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework. It generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually!
If you want to build a brand-new ASP.NET Core app that uses Angular 2 / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework, and generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually!
To do this, install Yeoman and these generator templates:
To do this, first install Yeoman and these generator templates:
npm install -g yo generator-aspnetcore-spa
Generate your new application starting point:
Then you can generate your new application starting point:
cd some-empty-directory
yo aspnetcore-spa
Once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core SPA:
Finally, once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core Single Page Application:
dotnet run
@@ -57,11 +52,11 @@ If you have an existing ASP.NET Core application, or if you just want to use the
* Most applications developers don't need to use this directly, but you can do so if you want to implement your own functionality that involves calling Node.js code from .NET at runtime.
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.NodeServices#microsoftaspnetcorenodeservices).
* `Microsoft.AspNetCore.SpaServices`
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular 2 or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#microsoftaspnetcorespaservices).
* `Microsoft.AspNetCore.AngularServices`
* This builds on the `SpaServices` package and includes features specific to Angular. Currently, this includes validation helpers.
* The code is [here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices). You'll find a usage example for [the validation helper here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts).
* This builds on the `SpaServices` package and includes features specific to Angular 2. Currently, this includes validation helpers and a "cache priming" feature, which let you pre-evaluate ajax requests on the server so that client-side code doesn't need to make network calls once it's loaded.
* The code is [here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices), and you'll find a usage example for [the validation helper here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts), and for the [cache priming here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/Views/Home/Index.cshtml#L7-8). Full docs are to be written.
There was previously a `Microsoft.AspNetCore.ReactServices` but this is not currently needed - all applicable functionality is in `Microsoft.AspNetCore.SpaServices`, because it's sufficiently general. We might add a new `Microsoft.AspNetCore.ReactServices` package in the future if new React-specific requirements emerge.
@@ -69,32 +64,28 @@ If you want to build a helper library for some other SPA framework, you can do s
## Samples and templates
Inside this repo, [the `templates` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/templates) contains the application starting points that the `aspnetcore-spa` generator emits. You can clone this repo and run those applications directly. But it's easier to [use the Yeoman tool to run the generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
Inside this repo, [the `templates` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/templates) contains the application starting points that the `aspnetcore-spa` generator emits. If you want, you can clone this repo and run those applications directly. But it's easier to [use the Yeoman tool to run the generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
The [`samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of:
- Using the JavaScript services family of packages with Angular and React.
- A standalone `NodeServices` usage for runtime code transpilation and image processing.
Also in this repo, [the `samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of using the JavaScript services family of packages with Angular 2 and React, plus examples of standalone `NodeServices` usage for runtime code transpilation and image processing.
**To run the samples:**
* Clone this repo
* At the repo's root directory (the one containing `src`, `samples`, etc.), run `dotnet restore`
* Change directory to the sample you want to run (for example, `cd samples/angular/MusicStore`)
* Change directory to the sample you want to run (e.g., `cd samples/angular/MusicStore`)
* Restore Node dependencies by running `npm install`
* If you're trying to run the Angular "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this.
* If you're trying to run the Angular 2 "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this.
* Run the application (`dotnet run`)
* Browse to [http://localhost:5000](http://localhost:5000)
## Contributing
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great! You can run the code in this repo as follows:
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great! You can run the code in this repo just by:
* Clone the repo
* Run `dotnet restore` at the repo root dir
* Go to whatever sample or template you want to run (for example, `cd templates/AngularSpa`)
* Restore NPM dependencies (run `npm install`)
* If the sample/template you're trying to run has a file called `webpack.config.vendor.js` at its root, run `webpack --config webpack.config.vendor.js`. It it has a file called `webpack.config.js`, run `webpack` (no args). You might need to install webpack first, by running `npm install -g webpack`.
* Launch it (`dotnet run`)
* Cloning the repo
* Running `dotnet restore` at the repo root dir
* Going to whatever sample or template you want to run (e.g., `cd templates/Angular2Spa`)
* Restoring NPM dependencies (run `npm install`)
* Launching it (`dotnet run`)
If you're planning to submit a pull request, and if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.
If you're planning to submit a pull request, and if it's more than a trivial fix (e.g., for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.

View File

@@ -4,12 +4,17 @@ install:
- ps: Install-Product node 6.9.2 x64
# .NET Core SDK binaries
# Download .NET Core SDK and add to PATH
- ps: $urlCurrent = "https://download.microsoft.com/download/E/7/8/E782433E-7737-4E6C-BFBF-290A0A81C3D7/dotnet-dev-win-x64.1.0.4.zip"
- ps: $urlCurrent = "https://go.microsoft.com/fwlink/?LinkID=834991"
- ps: $urlPreview = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/rel-1.0.0/dotnet-dev-win-x64.latest.zip"
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
- ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
- ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName()
- ps: $tempFilePreview = [System.IO.Path]::GetTempFileName()
- ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
- ps: (New-Object System.Net.WebClient).DownloadFile($urlPreview, $tempFilePreview)
- ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR)
- ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFilePreview, $env:DOTNET_INSTALL_DIR + "preview")
- ps: Copy-Item "${env:DOTNET_INSTALL_DIR}preview\*" "${env:DOTNET_INSTALL_DIR}" -Force -Recurse
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
build_script:
- ps: Push-Location
@@ -26,15 +31,17 @@ artifacts:
# - ps: .\build.ps1
clone_depth: 1
test_script:
- dotnet restore
- ps: Push-Location
- cd test
- npm install selenium-standalone
- ps: Start-Process node './start-selenium.js'
- npm install
- npm test
# - dotnet restore ./src
# - npm install -g selenium-standalone
# - selenium-standalone install
# The nosys flag is needed for selenium to work on Appveyor
# - ps: Start-Process selenium-standalone 'start','--','-Djna.nosys=true'
# - ps: Push-Location
# - cd test
# - npm install
# - npm test
on_finish :
- ps: Pop-Location
# - ps: Pop-Location
# After running tests, upload results to Appveyor
- ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test\tmp\junit\*.xml))
# - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test\tmp\junit\*.xml))
deploy: off

View File

@@ -1,7 +1,6 @@
<Project>
<Import Project="dependencies.props" />
<Import Project="version.props" />
<PropertyGroup>
<Product>Microsoft ASP.NET Core</Product>
@@ -11,10 +10,9 @@
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionSuffix Condition="'$(VersionSuffix)'!='' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" />
<PackageReference Include="Internal.AspNetCore.Sdk" Version="1.0.1-*" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -6,7 +6,6 @@
<AutoMapperVersion>5.0.2</AutoMapperVersion>
<CoreFxVersion>4.3.0</CoreFxVersion>
<JsonNetVersion>10.0.1</JsonNetVersion>
<InternalAspNetCoreSdkVersion>2.0.0-*</InternalAspNetCoreSdkVersion>
<MicrosoftDataflowVersion>4.5.24</MicrosoftDataflowVersion>
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion>

View File

@@ -1,6 +0,0 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.1.2</VersionPrefix>
<VersionSuffix>preview1</VersionSuffix>
</PropertyGroup>
</Project>

View File

@@ -14,7 +14,7 @@ export class AlbumDeletePrompt {
public show(album: models.Album) {
this.album = album;
// Consider rewriting this using Angular's "Renderer" API so as to avoid direct DOM access
// Consider rewriting this using Angular 2's "Renderer" API so as to avoid direct DOM access
(<any>window).jQuery(".modal", this._elementRef.nativeElement).modal();
}
}

View File

@@ -5,7 +5,6 @@
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<IsPackable>false</IsPackable>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,8 +2,8 @@ export default function (params: any): Promise<{ html: string, globals?: any }>
return new Promise((resolve, reject) => {
// Here, you could put any logic that synchronously or asynchronously prerenders
// your SPA components. For example, see the boot-server.ts files in the AngularSpa
// and ReactReduxSpa templates for ways to prerender Angular and React components.
// your SPA components. For example, see the boot-server.ts files in the Angular2Spa
// and ReactReduxSpa templates for ways to prerender Angular 2 and React components.
//
// If you wanted, you could use a property on the 'params.data' object to specify
// which SPA component or template to render.

View File

@@ -3,8 +3,10 @@
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<Description>Helpers for building Angular applications on ASP.NET Core.</Description>
<Description>Helpers for building Angular 2 applications on ASP.NET Core.</Description>
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
<VersionPrefix>1.1.0</VersionPrefix>
<VersionSuffix>beta2</VersionSuffix>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

View File

@@ -10,7 +10,7 @@ using Newtonsoft.Json;
namespace Microsoft.AspNetCore.AngularServices
{
/// <summary>
/// Helpers for prepopulating Angular's 'http' service with data.
/// Helpers for prepopulating Angular 2's 'http' service with data.
/// </summary>
public static class PrimeCacheHelper
{

View File

@@ -5,6 +5,8 @@
<PropertyGroup>
<Description>Socket-based RPC for Microsoft.AspNetCore.NodeServices.</Description>
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
<VersionPrefix>1.1.0</VersionPrefix>
<VersionSuffix>beta2</VersionSuffix>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

View File

@@ -5,6 +5,8 @@
<PropertyGroup>
<Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description>
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
<VersionPrefix>1.1.1</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

View File

@@ -10,7 +10,7 @@ This NuGet package provides a fast and robust way to invoke Node.js code from a
It is the underlying mechanism supporting the following packages:
* [`Microsoft.AspNetCore.SpaServices`](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices) - builds on NodeServices, adding functionality commonly used in Single Page Applications, such as server-side prerendering, webpack middleware, and integration between server-side and client-side routing.
* [`Microsoft.AspNetCore.AngularServices`](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices) and [`Microsoft.AspNetCore.ReactServices`](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.ReactServices) - these build on `SpaServices`, adding helpers specific to Angular and React, such as cache priming and integrating server-side and client-side validation
* [`Microsoft.AspNetCore.AngularServices`](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices) and [`Microsoft.AspNetCore.ReactServices`](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.ReactServices) - these build on `SpaServices`, adding helpers specific to Angular 2 and React, such as cache priming and integrating server-side and client-side validation
### Requirements
@@ -32,7 +32,7 @@ For .NET Framework apps:
* `nuget install Microsoft.AspNetCore.NodeServices`
### Do you just want to build an ASP.NET Core app with Angular / React / Knockout / etc.?
### Do you just want to build an ASP.NET Core app with Angular 2 / React / Knockout / etc.?
In that case, you don't need to use NodeServices directly (or install it manually). You can either:

View File

@@ -5,6 +5,8 @@
<PropertyGroup>
<Description>Helpers for building React applications on ASP.NET Core.</Description>
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
<VersionPrefix>1.1.0</VersionPrefix>
<VersionSuffix>beta2</VersionSuffix>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

View File

@@ -5,6 +5,8 @@
<PropertyGroup>
<Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description>
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
<VersionPrefix>1.1.1</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

View File

@@ -1,10 +1,10 @@
# Microsoft.AspNetCore.SpaServices
If you're building an ASP.NET Core application, and want to use Angular, React, Knockout, or another single-page app (SPA) framework, this NuGet package contains useful infrastructure for you.
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-side-prerendering) for *universal* (a.k.a. *isomorphic*) applications, where your Angular / React / etc. components are first rendered on the server, and then transferred to the client where execution continues
* [**Server-side prerendering**](#server-side-prerendering) 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-built 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 pushed to your browser and updated in the running application automatically, without even needing to reload the page
* [**Routing helpers**](#routing-helper-mapspafallbackroute) for integrating server-side routing with client-side routing
@@ -42,9 +42,9 @@ Also, if you want to debug projects created with the aspnetcore-spa generator, s
## 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 / React / etc. components.
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 and React 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
@@ -86,7 +86,7 @@ module.exports = prerendering.createServerRenderer(function(params) {
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 / React / etc. application.
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 .NET code into JavaScript code**
@@ -211,21 +211,21 @@ Finally, run `webpack` on the command line to build `ClientApp/dist/main-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 components
### 5(a). Prerendering Angular 2 components
If you're building an Angular application, you can run your components on the server inside your `boot-server.ts` file so they will be injected into the resulting web page.
If you're building an Angular 2 application, you can run your components on 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 components inside Node.js:
First install the NPM package `angular2-universal` - this contains infrastructure for executing Angular 2 components inside Node.js:
```
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 component on the server. The code needed for this is fairly complex, but that's unavoidable because Angular supports so many different ways of being configured, and you need to provide wiring for whatever combination of DI modules you're using.
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 that's unavoidable 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 components [here](https://github.com/aspnet/JavaScriptServices/blob/dev/templates/AngularSpa/ClientApp/boot-server.ts). If you use this with your own application, you might need to edit the `serverBindings` array to reference any other DI services that your Angular component depends on.
You can find an example `boot-server.ts` that renders arbitrary Angular 2 components [here](https://github.com/aspnet/JavaScriptServices/blob/dev/templates/Angular2Spa/ClientApp/boot-server.ts). If you use this with your own application, you might need to edit the `serverBindings` array to reference any other DI services that your Angular 2 component depends on.
The easiest way to get started with Angular 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.
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
@@ -301,7 +301,7 @@ The above example is extremely simple - it doesn't use `react-router`, and it do
For an example server-side boot module that knows how to evaluate `react-router` routes and render the correct React component, see [this example](https://github.com/aspnet/JavaScriptServices/blob/dev/templates/ReactReduxSpa/ClientApp/boot-server.tsx).
Supporting asynchronous data loading involves more considerations. Unlike Angular applications that run asynchronously on the server and freely overwrite server-generated markup with client-generated markup, React strictly wants to run synchronously on the server and always produce the same markup on the server as it does on the client.
Supporting asynchronous data loading involves more considerations. Unlike Angular 2 applications that run asynchronously on the server and freely overwrite server-generated markup with client-generated markup, React strictly wants to run synchronously on the server and always produce the same markup on the server as it does on the client.
To make this work, you most likely need some way to know in advance what data your React components will need to use, load it separately from those components, and have some way of transferring information about the loaded data from server to client. If you try to implement this in a generalized way, you'll end up reinventing something like the Flux/Redux pattern.
@@ -374,19 +374,19 @@ So that's enough to build TypeScript. Here's where webpack dev middleware comes
Following on from the preceding example that builds TypeScript, you could extend your Webpack configuration further to support building LESS. There are three major approaches to doing this:
1. **If using Angular, use its native style loader to attach the styles to components**. This is extremely simple and is usually the right choice if you are using Angular. However it only applies to Angular components, not to any other part of the host page, so sometimes you might want to combine this technique with options 2 or 3 below.
1. **If using Angular 2, use its native style loader to attach the styles to components**. This is extremely simple and is usually the right choice if you are using Angular 2. However it only applies to Angular 2 components, not to any other part of the host page, so sometimes you might want to combine this technique with options 2 or 3 below.
2. **Or, use Webpack's style loader to attach the styles at runtime**. The CSS markup will be included in your JavaScript bundles and will be attached to the document dynamically. This has certain benefits during development but isn't recommended in production.
3. **Or, have each build write a standalone `.css` file to disk**. At runtime, load it using a regular `<link rel='stylesheet'>` tag. This is likely to be the approach you'll want for production use (at least for non-Angular applications, such as React applications) as it's the most robust and best-performing option.
3. **Or, have each build write a standalone `.css` file to disk**. At runtime, load it using a regular `<link rel='stylesheet'>` tag. This is likely to be the approach you'll want for production use (at least for non-Angular 2 applications, such as React applications) as it's the most robust and best-performing option.
If instead of LESS you prefer SASS or another CSS preprocessor, the exact same techniques should work, but of course you'll need to replace the `less-loader` with an equivalent Webpack loader for SASS or your chosen preprocessor.
#### Approach 1: Scoping styles to Angular components
#### Approach 1: Scoping styles to Angular 2 components
If you are using Angular, this is the easiest way to perform styling. It works with both server and client rendering, supports Hot Module Replacement, and robustly scopes styles to particular components (and optionally, their descendant elements).
If you are using Angular 2, this is the easiest way to perform styling. It works with both server and client rendering, supports Hot Module Replacement, and robustly scopes styles to particular components (and optionally, their descendant elements).
This repository's Angular template uses this technique to scope styles to components out of the box. It defines those styles as `.css` files. For example, its components reference `.css` files like this:
This repository's Angular 2 template uses this technique to scope styles to components out of the box. It defines those styles as `.css` files. For example, its components reference `.css` files like this:
```javascript
@Component({
@@ -415,7 +415,7 @@ Next, add the following loader configuration to the `loaders` array in `webpack.
{ test: /\.less/, include: /ClientApp/, loader: 'raw-loader!less-loader' }
```
Notice how this chains together with `less-loader` (which transforms `.less` syntax to plain CSS syntax), then the `raw` loader (which turn the result into a string literal). With this in place, you can reference `.less` files from your Angular components in the obvious way:
Notice how this chains together with `less-loader` (which transforms `.less` syntax to plain CSS syntax), then the `raw` loader (which turn the result into a string literal). With this in place, you can reference `.less` files from your Angular 2 components in the obvious way:
```javascript
@Component({
@@ -429,7 +429,7 @@ export class SomeComponent { ... }
#### Approach 2: Loading the styles using Webpack and JavaScript
This technique works with any client-side framework (not just Angular), and can also apply styles to the entire document rather than just individual components. It's a little simpler to set up than technique 3, plus it works flawlessly with Hot Module Replacement (HMR). The downside is that it's really only good for development time, because in production you probably don't want users to wait until JavaScript is loaded before styles are applied to the page (this would mean they'd see a 'flash of unstyled content' while the page is being loaded).
This technique works with any client-side framework (not just Angular 2), and can also apply styles to the entire document rather than just individual components. It's a little simpler to set up than technique 3, plus it works flawlessly with Hot Module Replacement (HMR). The downside is that it's really only good for development time, because in production you probably don't want users to wait until JavaScript is loaded before styles are applied to the page (this would mean they'd see a 'flash of unstyled content' while the page is being loaded).
First create a `.less` file in your project. For example, create a file at `ClientApp/styles/mystyles.less` containing:
@@ -631,7 +631,7 @@ Now if you edit any React component (e.g., in `.jsx` or `.tsx` files), the updat
Webpack has built-in HMR support for various types of module, such as styles and React components as described above. But to support HMR for other code modules, you need to add a small block of code that calls `module.hot.accept` to receive the updated module and update the running application.
This is [documented in detail on the Webpack site](https://webpack.github.io/docs/hot-module-replacement.html). Or to get a working HMR-enabled ASP.NET Core site with Angular, React, React+Redux, or Knockout, you can use the [aspnetcore-spa generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
This is [documented in detail on the Webpack site](https://webpack.github.io/docs/hot-module-replacement.html). Or to get a working HMR-enabled ASP.NET Core site with Angular 2, React, React+Redux, or Knockout, you can use the [aspnetcore-spa generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
#### Passing options to the Webpack Hot Middleware client

View File

@@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
/// </summary>
internal class ConditionalProxyMiddleware
{
private const int DefaultHttpBufferSize = 4096;
private readonly HttpClient _httpClient;
private readonly RequestDelegate _next;
private readonly ConditionalProxyMiddlewareOptions _options;
@@ -95,12 +93,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
context.Response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(context.Response.Body, DefaultHttpBufferSize, context.RequestAborted);
}
await responseMessage.Content.CopyToAsync(context.Response.Body);
return true;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "aspnet-prerendering",
"version": "2.0.5",
"version": "2.0.3",
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
"main": "index.js",
"scripts": {

View File

@@ -8,7 +8,7 @@ interface RenderToStringCallback {
interface RenderToStringResult {
html: string;
statusCode?: number;
statusCode: number;
globals?: { [key: string]: any };
}

View File

@@ -4,8 +4,3 @@ This NPM package is an internal implementation detail of the `Microsoft.AspNetCo
You should not use this package directly in your own applications, because it is not supported, and there are no
guarantees about how its APIs will change in the future.
## History
* Version 1.x amends the Webpack config to insert `react-transform` and `react-transform-hmr` entries on `babel-loader`.
* Version 2.x drops support for the Babel plugin, and instead amends the Webpack config to insert `react-hot-loader/webpack` and `react-hot-loader/patch` entries. This means it works with React Hot Loader v3.

View File

@@ -1,6 +1,6 @@
{
"name": "aspnet-webpack-react",
"version": "2.0.0",
"version": "1.0.5",
"description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
"main": "index.js",
"scripts": {
@@ -16,13 +16,22 @@
"type": "git",
"url": "https://github.com/aspnet/JavaScriptServices.git"
},
"dependencies": {
"babel-core": "^6.7.2",
"babel-loader": "^6.2.4",
"babel-plugin-react-transform": "^2.0.2",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"react": "^15.0.0",
"react-transform-hmr": "^1.0.4"
},
"devDependencies": {
"@types/webpack": "^2.2.0",
"rimraf": "^2.5.4",
"typescript": "^2.0.0",
"webpack": "^2.2.0"
"webpack": "^1.12.14"
},
"peerDependencies": {
"webpack": "^2.2.0"
"webpack": "^1.13.2 || ^2.2.0"
}
}

View File

@@ -1,66 +1,78 @@
import * as webpack from 'webpack';
type OldOrNewModule = webpack.OldModule & webpack.NewModule;
const reactHotLoaderWebpackLoader = 'react-hot-loader/webpack';
const reactHotLoaderPatch = 'react-hot-loader/patch';
const supportedTypeScriptLoaders = ['ts-loader', 'awesome-typescript-loader'];
export function addReactHotModuleReplacementConfig(webpackConfig: webpack.Configuration) {
const moduleConfig = webpackConfig.module as webpack.NewModule;
const moduleRules = moduleConfig.rules;
export function addReactHotModuleReplacementBabelTransform(webpackConfig: webpack.Configuration) {
const moduleConfig = webpackConfig.module as OldOrNewModule;
const moduleRules = moduleConfig.rules // Webpack >= 2.1.0 beta 23
|| moduleConfig.loaders; // Legacy/back-compat
if (!moduleRules) {
return; // Unknown rules list format. Might be Webpack 1.x, which is not supported.
return; // Unknown rules list format
}
// Find the rule that loads TypeScript files, and prepend 'react-hot-loader/webpack'
// to its array of loaders
for (let ruleIndex = 0; ruleIndex < moduleRules.length; ruleIndex++) {
// We only support NewUseRule (i.e., { use: ... }) because OldUseRule doesn't accept array values
const rule = moduleRules[ruleIndex] as webpack.NewUseRule;
if (!rule.use) {
continue;
moduleRules.forEach(rule => {
// Allow rules/loaders entries to be either { loader: ... } or { use: ... }
// Ignore other config formats (too many combinations to support them all)
let loaderConfig =
(rule as webpack.NewUseRule).use // Recommended config format for Webpack 2.x
|| (rule as webpack.LoaderRule).loader; // Typical config format for Webpack 1.x
if (!loaderConfig) {
return; // Not a supported rule format (e.g., an array)
}
// We're looking for the first 'use' value that's a TypeScript loader
const loadersArray = rule.use instanceof Array ? rule.use : [rule.use];
const isTypescriptLoader = supportedTypeScriptLoaders.some(typeScriptLoaderName => containsLoader(loadersArray, typeScriptLoaderName));
if (!isTypescriptLoader) {
continue;
// Allow use/loader values to be either { loader: 'name' } or 'name'
// We don't need to support other possible ways of specifying loaders (e.g., arrays),
// so skip unrecognized formats.
const loaderNameString =
(loaderConfig as (webpack.OldLoader | webpack.NewLoader)).loader
|| (loaderConfig as string);
if (!loaderNameString || (typeof loaderNameString !== 'string')) {
return; // Not a supported loader format (e.g., an array)
}
// This is the one - prefix it with the react-hot-loader loader
// (unless it's already in there somewhere)
if (!containsLoader(loadersArray, reactHotLoaderWebpackLoader)) {
loadersArray.unshift(reactHotLoaderWebpackLoader);
rule.use = loadersArray; // In case we normalised it to an array
}
break;
// Find the babel-loader entry
if (loaderNameString.match(/\bbabel-loader\b/)) {
// If the rule is of the form { use: 'name' }, then replace it
// with { use: { loader: 'name' }} so we can attach options
if ((rule as webpack.NewUseRule).use && typeof loaderConfig === 'string') {
loaderConfig = (rule as webpack.NewUseRule).use = { loader: loaderConfig };
}
// Ensure the entrypoint is prefixed with 'react-hot-loader/patch' (unless it's already in there).
// We only support entrypoints of the form { name: value } (not just 'name' or ['name'])
// because that gives us a place to prepend the new value
if (!webpackConfig.entry || typeof webpackConfig.entry === 'string' || webpackConfig.entry instanceof Array) {
throw new Error('Cannot enable React HMR because \'entry\' in Webpack config is not of the form { name: value }');
}
const entryConfig = webpackConfig.entry as webpack.Entry;
Object.getOwnPropertyNames(entryConfig).forEach(entrypointName => {
if (typeof(entryConfig[entrypointName]) === 'string') {
// Normalise to array
entryConfig[entrypointName] = [entryConfig[entrypointName] as string];
const configItemWithOptions = typeof loaderConfig === 'string'
? rule // The rule is of the form { loader: 'name' }, so put options on the rule
: loaderConfig; // The rule is of the form { use/loader: { loader: 'name' }}, so put options on the use/loader
// Ensure the config has an 'options' (or a legacy 'query')
let optionsObject =
(configItemWithOptions as webpack.NewLoader).options // Recommended config format for Webpack 2.x
|| (configItemWithOptions as webpack.OldLoaderRule).query; // Legacy
if (!optionsObject) {
// If neither options nor query was set, define a new value,
// using the legacy format ('query') for compatibility with Webpack 1.x
optionsObject = (configItemWithOptions as webpack.OldLoaderRule).query = {};
}
let entryValueArray = entryConfig[entrypointName] as string[];
if (entryValueArray.indexOf(reactHotLoaderPatch) < 0) {
entryValueArray.unshift(reactHotLoaderPatch);
// Ensure Babel plugins includes 'react-transform'
const plugins = optionsObject['plugins'] = optionsObject['plugins'] || [];
const hasReactTransform = plugins.some(p => p && p[0] === 'react-transform');
if (!hasReactTransform) {
plugins.push(['react-transform', {}]);
}
// Ensure 'react-transform' plugin is configured to use 'react-transform-hmr'
plugins.forEach(pluginConfig => {
if (pluginConfig && pluginConfig[0] === 'react-transform') {
const pluginOpts = pluginConfig[1] = pluginConfig[1] || {};
const transforms = pluginOpts.transforms = pluginOpts.transforms || [];
const hasReactTransformHmr = transforms.some(t => t.transform === 'react-transform-hmr');
if (!hasReactTransformHmr) {
transforms.push({
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'] // Important for Webpack HMR
});
}
}
});
}
function containsLoader(loadersArray: webpack.Loader[], loaderName: string) {
return loadersArray.some(loader => {
// Allow 'use' values to be either { loader: 'name' } or 'name'
// No need to support legacy webpack.OldLoader
const actualLoaderName = (loader as webpack.NewLoader).loader || (loader as string);
return actualLoaderName && new RegExp(`\\b${ loaderName }\\b`).test(actualLoaderName);
});
}

View File

@@ -1,6 +1 @@
export { addReactHotModuleReplacementConfig } from './HotModuleReplacement';
// Temporarily alias addReactHotModuleReplacementConfig as addReactHotModuleReplacementBabelTransform for backward
// compatibility with aspnet-webpack 1.x. In aspnet-webpack 2.0, we can drop the old name (and also deprecate
// some other no-longer-supported functionality, such as LoadViaWebpack).
export { addReactHotModuleReplacementConfig as addReactHotModuleReplacementBabelTransform } from './HotModuleReplacement';
export { addReactHotModuleReplacementBabelTransform } from './HotModuleReplacement';

View File

@@ -1,6 +1,6 @@
{
"name": "aspnet-webpack",
"version": "1.0.29",
"version": "1.0.28",
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
"main": "index.js",
"scripts": {

View File

@@ -23,13 +23,11 @@ interface CreateDevServerOptions {
hotModuleReplacementEndpointUrl: string;
}
type StringMap<T> = [(key: string) => T];
// These are the options configured in C# and then JSON-serialized, hence the C#-style naming
interface DevServerOptions {
HotModuleReplacement: boolean;
HotModuleReplacementServerPort: number;
HotModuleReplacementClientOptions: StringMap<string>;
HotModuleReplacementClientOptions: Object;
ReactHotModuleReplacement: boolean;
}
@@ -41,7 +39,7 @@ interface WebpackConfigFunc {
}
type WebpackConfigFileExport = WebpackConfigOrArray | WebpackConfigFunc;
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrClientOptions: StringMap<string>, hmrServerEndpoint: string) {
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrClientOptions: HotModuleReplacementClientOptions, hmrClientEndpoint: string, hmrServerEndpoint: string) {
// Build the final Webpack config based on supplied options
if (enableHotModuleReplacement) {
// For this, we only support the key/value config format, not string or string[], since
@@ -57,6 +55,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
// Augment all entry points so they support HMR (unless they already do)
Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => {
const webpackHotMiddlewareEntryPoint = 'webpack-hot-middleware/client';
hmrClientOptions.path = hmrClientEndpoint;
const webpackHotMiddlewareOptions = '?' + querystring.stringify(hmrClientOptions);
if (typeof entryPoints[entryPointName] === 'string') {
entryPoints[entryPointName] = [webpackHotMiddlewareEntryPoint + webpackHotMiddlewareOptions, entryPoints[entryPointName]];
@@ -108,8 +107,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
const compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath,
watchOptions: webpackConfig.watchOptions
publicPath: webpackConfig.output.publicPath
}));
// After each compilation completes, copy the in-memory filesystem to disk.
@@ -208,7 +206,7 @@ function pathJoinSafe(rootPath: string, filePath: string) {
function beginWebpackWatcher(webpackConfig: webpack.Configuration) {
const compiler = webpack(webpackConfig);
compiler.watch(webpackConfig.watchOptions || {}, (err, stats) => {
compiler.watch({ /* watchOptions */ }, (err, stats) => {
// The default error reporter is fine for now, but could be customized here in the future if desired
});
}
@@ -230,6 +228,7 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
const enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
const enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || {};
if (enableReactHotModuleReplacement && !enableHotModuleReplacement) {
callback('To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.', null);
return;
@@ -268,12 +267,7 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
|| `http://localhost:${listener.address().port}/__webpack_hmr`; // Fall back on absolute URL to bypass proxying
const hmrServerEndpoint = options.hotModuleReplacementEndpointUrl
|| '/__webpack_hmr'; // URL is relative to webpack dev server root
// We always overwrite the 'path' option as it needs to match what the .NET side is expecting
const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || <StringMap<string>>{};
hmrClientOptions['path'] = hmrClientEndpoint;
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientOptions, hmrServerEndpoint);
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientOptions, hmrClientEndpoint, hmrServerEndpoint);
}
});

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
</ItemGroup>

View File

@@ -1,28 +1,29 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';
export const sharedConfig: NgModule = {
@NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent,
NavMenuComponent,
CounterComponent,
FetchDataComponent,
HomeComponent
],
imports: [
UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'counter', component: CounterComponent },
{ path: 'counter', loadChildren: './components/counter/counter.module#CounterModule' },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: '**', redirectTo: 'home' }
])
]
};
})
export class AppModule {
}

View File

@@ -1,6 +1,6 @@
<h1>Counter</h1>
<p>This is a simple example of an Angular component.</p>
<p>This is a simple example of an Angular 2 component.</p>
<p>Current count: <strong>{{ currentCount }}</strong></p>

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CounterComponent } from './counter.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: CounterComponent }
])
],
exports: [ RouterModule ],
declarations: [ CounterComponent ]
})
export class CounterModule {}

View File

@@ -1,4 +1,4 @@
import { Component, Inject } from '@angular/core';
import { Component } from '@angular/core';
import { Http } from '@angular/http';
@Component({
@@ -8,8 +8,8 @@ import { Http } from '@angular/http';
export class FetchDataComponent {
public forecasts: WeatherForecast[];
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string) {
http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => {
constructor(http: Http) {
http.get('/api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result.json() as WeatherForecast[];
});
}

View File

@@ -2,15 +2,15 @@
<p>Welcome to your new single-page application, built with:</p>
<ul>
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
<li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>
<li><a href='https://angular.io/'>Angular 2</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>
<li><a href='https://webpack.github.io/'>Webpack</a> for building and bundling client-side resources</li>
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
</ul>
<p>To help you get started, we've also set up:</p>
<ul>
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
<li><strong>Server-side prerendering</strong>. For faster initial loading and improved SEO, your Angular 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.</li>
<li><strong>Server-side prerendering</strong>. 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.</li>
<li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li>
<li><strong>Hot module replacement</strong>. 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 app will be rebuilt and a new instance injected is into the page.</li>
<li><strong>Hot module replacement</strong>. 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.</li>
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li>
</ul>

View File

@@ -0,0 +1,29 @@
import 'angular2-universal-polyfills/browser';
import { enableProdMode } from '@angular/core';
import { platformUniversalDynamic } from 'angular2-universal';
import { AppModule } from './app/app.module';
import 'bootstrap';
const rootElemTagName = 'app'; // Update this if you change your root component selector
// Enable either Hot Module Reloading or production mode
if (module['hot']) {
module['hot'].accept();
module['hot'].dispose(() => {
// Before restarting the app, we create a new root element and dispose the old one
const oldRootElem = document.querySelector(rootElemTagName);
const newRootElem = document.createElement(rootElemTagName);
oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
platform.destroy();
});
} else {
enableProdMode();
}
// Boot the application, either now or when the DOM content is loaded
const platform = platformUniversalDynamic();
const bootApplication = () => { platform.bootstrapModule(AppModule); };
if (document.readyState === 'complete') {
bootApplication();
} else {
document.addEventListener('DOMContentLoaded', bootApplication);
}

View File

@@ -0,0 +1,34 @@
import 'angular2-universal-polyfills';
import 'angular2-universal-patch';
import 'zone.js';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { enableProdMode } from '@angular/core';
import { platformNodeDynamic } from 'angular2-universal';
import { AppModule } from './app/app.module';
enableProdMode();
const platform = platformNodeDynamic();
export default createServerRenderer(params => {
return new Promise<RenderResult>((resolve, reject) => {
const requestZone = Zone.current.fork({
name: 'angular-universal request',
properties: {
baseUrl: '/',
requestUrl: params.url,
originUrl: params.origin,
preboot: false,
document: '<app></app>'
},
onHandleError: (parentZone, currentZone, targetZone, error) => {
// If any error occurs while rendering the module, reject the whole operation
reject(error);
return true;
}
});
return requestZone.run<Promise<string>>(() => platform.serializeModule(AppModule)).then(html => {
resolve({ html: html });
}, reject);
});
});

View File

@@ -1,6 +1,5 @@
// Load required polyfills and testing libraries
import 'reflect-metadata';
import 'zone.js';
import 'angular2-universal-polyfills';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';

View File

View File

@@ -0,0 +1,63 @@
{
"name": "WebApplicationBasic",
"version": "0.0.0",
"scripts": {
"test": "karma start ClientApp/test/karma.conf.js"
},
"dependencies": {
"@angular/common": "^2.4.5",
"@angular/compiler": "^2.4.5",
"@angular/core": "^2.4.5",
"@angular/forms": "^2.4.5",
"@angular/http": "^2.4.5",
"@angular/platform-browser": "^2.4.5",
"@angular/platform-browser-dynamic": "^2.4.5",
"@angular/platform-server": "^2.4.5",
"@angular/router": "^3.4.5",
"@types/node": "^6.0.42",
"angular2-platform-node": "~2.0.11",
"angular2-template-loader": "^0.6.2",
"angular2-universal": "^2.1.0-rc.1",
"angular2-universal-patch": "^0.2.1",
"angular2-universal-polyfills": "^2.1.0-rc.1",
"angular2-router-loader": "^0.3.4",
"aspnet-prerendering": "^2.0.0",
"aspnet-webpack": "^1.0.17",
"awesome-typescript-loader": "^3.0.0",
"bootstrap": "^3.3.7",
"css": "^2.2.1",
"css-loader": "^0.25.0",
"es6-shim": "^0.35.1",
"event-source-polyfill": "^0.0.7",
"expose-loader": "^0.7.1",
"extract-text-webpack-plugin": "^2.0.0-rc",
"file-loader": "^0.9.0",
"html-loader": "^0.4.4",
"isomorphic-fetch": "^2.2.1",
"jquery": "^2.2.1",
"json-loader": "^0.5.4",
"preboot": "^4.5.2",
"raw-loader": "^0.5.1",
"rxjs": "^5.0.1",
"style-loader": "^0.13.1",
"to-string-loader": "^1.1.5",
"typescript": "^2.2.1",
"url-loader": "^0.5.7",
"webpack": "^2.2.0",
"webpack-hot-middleware": "^2.12.2",
"webpack-merge": "^0.14.1",
"zone.js": "^0.7.6"
},
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/jasmine": "^2.5.37",
"chai": "^3.5.0",
"jasmine-core": "^2.5.2",
"karma": "^1.3.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-webpack": "^1.8.0"
}
}

View File

@@ -16,9 +16,9 @@ module.exports = (env) => {
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader'] },
{ test: /\.ts$/, include: /ClientApp/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] },
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},

View File

@@ -16,15 +16,16 @@ module.exports = (env) => {
},
entry: {
vendor: [
'@angular/animations',
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/forms',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/platform-server',
'angular2-universal',
'angular2-universal-polyfills',
'bootstrap',
'bootstrap/dist/css/bootstrap.css',
'es6-shim',
@@ -42,7 +43,6 @@ module.exports = (env) => {
plugins: [
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable)
new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/11580
new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/14898
new webpack.IgnorePlugin(/^vertx$/) // Workaround for https://github.com/stefanpenner/es6-promise/issues/100
]
};
@@ -51,7 +51,7 @@ module.exports = (env) => {
output: { path: path.join(__dirname, 'wwwroot', 'dist') },
module: {
rules: [
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: 'css-loader' }) }
]
},
plugins: [
@@ -73,7 +73,7 @@ module.exports = (env) => {
libraryTarget: 'commonjs2',
},
module: {
rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] } ]
rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', 'css-loader'] } ]
},
entry: { vendor: ['aspnet-prerendering'] },
plugins: [

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,21 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { sharedConfig } from './app.module.shared';
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: sharedConfig.declarations,
imports: [
BrowserModule,
FormsModule,
HttpModule,
...sharedConfig.imports
],
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin }
]
})
export class AppModule {
}

View File

@@ -1,14 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { sharedConfig } from './app.module.shared';
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: sharedConfig.declarations,
imports: [
ServerModule,
...sharedConfig.imports
]
})
export class AppModule {
}

View File

@@ -1,22 +0,0 @@
import 'reflect-metadata';
import 'zone.js';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module.client';
if (module['hot']) {
module['hot'].accept();
module['hot'].dispose(() => {
// Before restarting the app, we create a new root element and dispose the old one
const oldRootElem = document.querySelector('app');
const newRootElem = document.createElement('app');
oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
modulePromise.then(appModule => appModule.destroy());
});
} else {
enableProdMode();
}
// Note: @ng-tools/webpack looks for the following expression when performing production
// builds. Don't change how this line looks, otherwise you may break tree-shaking.
const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -1,36 +0,0 @@
import 'reflect-metadata';
import 'zone.js';
import 'rxjs/add/operator/first';
import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core';
import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { AppModule } from './app/app.module.server';
enableProdMode();
export default createServerRenderer(params => {
const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);
return new Promise<RenderResult>((resolve, reject) => {
zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: state.renderToString()
});
moduleRef.destroy();
});
});
});
});
});

View File

@@ -1,60 +0,0 @@
{
"name": "WebApplicationBasic",
"version": "0.0.0",
"scripts": {
"test": "karma start ClientApp/test/karma.conf.js"
},
"dependencies": {
"@angular/animations": "4.1.2",
"@angular/common": "4.1.2",
"@angular/compiler": "4.1.2",
"@angular/core": "4.1.2",
"@angular/forms": "4.1.2",
"@angular/http": "4.1.2",
"@angular/platform-browser": "4.1.2",
"@angular/platform-browser-dynamic": "4.1.2",
"@angular/platform-server": "4.1.2",
"@angular/router": "4.1.2",
"@types/node": "7.0.18",
"angular2-template-loader": "0.6.2",
"aspnet-prerendering": "^2.0.5",
"aspnet-webpack": "^1.0.29",
"awesome-typescript-loader": "3.1.3",
"bootstrap": "3.3.7",
"css": "2.2.1",
"css-loader": "0.28.1",
"es6-shim": "0.35.3",
"event-source-polyfill": "0.0.9",
"expose-loader": "0.7.3",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "0.11.1",
"html-loader": "0.4.5",
"isomorphic-fetch": "2.2.1",
"jquery": "3.2.1",
"json-loader": "0.5.4",
"preboot": "4.5.2",
"raw-loader": "0.5.1",
"reflect-metadata": "0.1.10",
"rxjs": "5.4.0",
"style-loader": "0.17.0",
"to-string-loader": "1.1.5",
"typescript": "2.3.2",
"url-loader": "0.5.8",
"webpack": "2.5.1",
"webpack-hot-middleware": "2.18.0",
"webpack-merge": "4.1.0",
"zone.js": "0.8.10"
},
"devDependencies": {
"@types/chai": "3.5.2",
"@types/jasmine": "2.5.47",
"chai": "3.5.0",
"jasmine-core": "2.6.1",
"karma": "1.7.0",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.1.1",
"karma-cli": "1.0.1",
"karma-jasmine": "1.1.0",
"karma-webpack": "2.0.3"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
</ItemGroup>

View File

@@ -1,4 +1,4 @@
import { Aurelia, PLATFORM } from 'aurelia-framework';
import { Aurelia } from 'aurelia-framework';
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
@@ -10,21 +10,21 @@ export class App {
route: [ '', 'home' ],
name: 'home',
settings: { icon: 'home' },
moduleId: PLATFORM.moduleName('../home/home'),
moduleId: '../home/home',
nav: true,
title: 'Home'
}, {
route: 'counter',
name: 'counter',
settings: { icon: 'education' },
moduleId: PLATFORM.moduleName('../counter/counter'),
moduleId: '../counter/counter',
nav: true,
title: 'Counter'
}, {
route: 'fetch-data',
name: 'fetchdata',
settings: { icon: 'th-list' },
moduleId: PLATFORM.moduleName('../fetchdata/fetchdata'),
moduleId: '../fetchdata/fetchdata',
nav: true,
title: 'Fetch data'
}]);

View File

@@ -1,5 +1,5 @@
import 'isomorphic-fetch';
import { Aurelia, PLATFORM } from 'aurelia-framework';
import { Aurelia } from 'aurelia-framework';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap';
declare const IS_DEV_BUILD: boolean; // The value is supplied by Webpack during the build
@@ -11,5 +11,5 @@ export function configure(aurelia: Aurelia) {
aurelia.use.developmentLogging();
}
aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app/components/app/app')));
aurelia.start().then(() => aurelia.setRoot('app/components/app/app'));
}

View File

@@ -43,7 +43,7 @@ namespace WebApplicationBasic
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
HotModuleReplacement = false // Aurelia Webpack Plugin HMR currently has issues. Leave this set to false.
});
}
else

View File

@@ -2,30 +2,49 @@
"name": "WebApplicationBasic",
"version": "0.0.0",
"dependencies": {
"aurelia-bootstrapper": "^2.0.1",
"aurelia-fetch-client": "^1.0.1",
"aurelia-framework": "^1.1.0",
"aurelia-loader-webpack": "^2.0.0",
"aurelia-pal": "^1.3.0",
"aurelia-router": "^1.2.1",
"aurelia-bootstrapper-webpack": "^1.0.0",
"aurelia-event-aggregator": "^1.0.0",
"aurelia-fetch-client": "^1.0.0",
"aurelia-framework": "^1.0.0",
"aurelia-history-browser": "^1.0.0",
"aurelia-loader-webpack": "^1.0.0",
"aurelia-logging-console": "^1.0.0",
"aurelia-pal-browser": "^1.0.0",
"aurelia-polyfills": "^1.0.0",
"aurelia-route-recognizer": "^1.0.0",
"aurelia-router": "^1.0.2",
"aurelia-templating-binding": "^1.0.0",
"aurelia-templating-resources": "^1.0.0",
"aurelia-templating-router": "^1.0.0",
"bootstrap": "^3.3.7",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.2.1"
"jquery": "^2.2.1"
},
"devDependencies": {
"@types/node": "^7.0.12",
"aspnet-webpack": "^1.0.28",
"aurelia-webpack-plugin": "^2.0.0-rc.2",
"css-loader": "^0.28.0",
"extract-text-webpack-plugin": "^2.1.0",
"file-loader": "^0.11.1",
"html-loader": "^0.4.5",
"@types/node": "^6.0.45",
"aspnet-webpack": "^1.0.11",
"aurelia-webpack-plugin": "^1.1.0",
"copy-webpack-plugin": "^3.0.1",
"css": "^2.2.1",
"css-loader": "^0.25.0",
"expose-loader": "^0.7.1",
"extract-text-webpack-plugin": "2.0.0-beta.4",
"file-loader": "^0.9.0",
"html-loader": "^0.4.4",
"html-webpack-plugin": "^2.22.0",
"json-loader": "^0.5.4",
"style-loader": "^0.16.1",
"ts-loader": "^2.0.3",
"typescript": "^2.2.2",
"url-loader": "^0.5.8",
"webpack": "^2.3.3",
"webpack-hot-middleware": "^2.18.0"
"raw-loader": "^0.5.1",
"style-loader": "^0.13.1",
"to-string-loader": "^1.1.5",
"ts-loader": "^0.8.2",
"typescript": "^2.2.1",
"url-loader": "^0.5.7",
"webpack": "2.1.0-beta.25",
"webpack-hot-middleware": "^2.10.0"
},
"aurelia": {
"build": {
"includeDependencies": "aurelia-*"
}
}
}

View File

@@ -6,7 +6,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipDefaultLibCheck": true,
"lib": [ "es2015", "dom" ],
"lib": [ "es6", "dom" ],
"types": [ "node" ]
},
"exclude": [ "bin", "node_modules" ],

View File

@@ -1,28 +1,24 @@
const path = require('path');
const webpack = require('webpack');
const { AureliaPlugin } = require('aurelia-webpack-plugin');
const bundleOutputDir = './wwwroot/dist';
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var AureliaWebpackPlugin = require('aurelia-webpack-plugin');
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'app': 'aurelia-bootstrapper' },
resolve: {
extensions: ['.ts', '.js'],
modules: ['ClientApp', 'node_modules'],
},
var bundleOutputDir = './wwwroot/dist';
module.exports = {
resolve: { extensions: [ '.js', '.ts' ] },
entry: { 'app': 'aurelia-bootstrapper-webpack' }, // Note: The aurelia-webpack-plugin will add your app's modules to this bundle automatically
output: {
path: path.resolve(bundleOutputDir),
publicPath: '/dist/',
publicPath: '/dist',
filename: '[name].js'
},
module: {
rules: [
{ test: /\.ts$/i, include: /ClientApp/, use: 'ts-loader?silent=true' },
{ test: /\.html$/i, use: 'html-loader' },
{ test: /\.css$/i, use: isDevBuild ? 'css-loader' : 'css-loader?minimize' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } },
{ test: /\.html$/, loader: 'html-loader' },
{ test: /\.css$/, loaders: [ 'style-loader', 'css-loader' ] },
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' },
{ test: /\.json$/, loader: 'json-loader' }
]
},
plugins: [
@@ -31,14 +27,19 @@ module.exports = (env) => {
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
}),
new AureliaPlugin({ aureliaApp: 'boot' })
new AureliaWebpackPlugin({
root: path.resolve('./'),
src: path.resolve('./ClientApp'),
baseUrl: '/'
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin()
])
}];
}
};

View File

@@ -1,20 +1,17 @@
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS = new ExtractTextPlugin('vendor.css');
module.exports = ({ prod } = {}) => {
const isDevBuild = !prod;
return [{
stats: { modules: false },
module.exports = {
resolve: {
extensions: [ '.js' ]
},
module: {
loaders: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, loader: 'url-loader?limit=100000' },
{ test: /\.css(\?|$)/, loader: extractCSS.extract([isDevBuild ? 'css-loader' : 'css-loader?minimize']) }
{ test: /\.css(\?|$)/, loader: extractCSS.extract(['css-loader']) }
]
},
entry: {
@@ -52,5 +49,4 @@ module.exports = ({ prod } = {}) => {
].concat(isDevBuild ? [] : [
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
])
}]
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1,5 +1,4 @@
import './css/site.css';
import 'bootstrap';
import * as ko from 'knockout';
import './webpack-component-loader';
import AppRootComponent from './components/app-root/app-root';

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
</ItemGroup>

View File

@@ -19,7 +19,7 @@ module.exports = (env) => {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.html$/, use: 'raw-loader' },
{ test: /\.css$/, use: isDevBuild ? [ 'style-loader', 'css-loader' ] : ExtractTextPlugin.extract({ use: 'css-loader?minimize' }) },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader' }) },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},

View File

@@ -13,7 +13,7 @@ module.exports = (env) => {
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' },
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: 'css-loader' }) }
]
},
entry: {

View File

@@ -0,0 +1,3 @@
{
"presets": [["es2015", { "modules": false }], "react"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,41 +2,23 @@ import './css/site.css';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { browserHistory, Router } from 'react-router';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import { syncHistoryWithStore } from 'react-router-redux';
import routes from './routes';
import configureStore from './configureStore';
import { ApplicationState } from './store';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
// Create browser history to use in the Redux store
const history = createBrowserHistory();
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = (window as any).initialReduxState as ApplicationState;
const store = configureStore(history, initialState);
const store = configureStore(initialState);
const history = syncHistoryWithStore(browserHistory, store);
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing configuration
// and injects the app into a DOM element.
ReactDOM.render(
<AppContainer>
<Provider store={ store }>
<ConnectedRouter history={ history } children={ routes } />
</Provider>
</AppContainer>,
<Router history={ history } children={ routes } />
</Provider>,
document.getElementById('react-app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}

View File

@@ -1,37 +1,43 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { replace } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import { match, RouterContext } from 'react-router';
import createMemoryHistory from 'history/lib/createMemoryHistory';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { routes } from './routes';
import routes from './routes';
import configureStore from './configureStore';
export default createServerRenderer(params => {
return new Promise<RenderResult>((resolve, reject) => {
// Prepare Redux store with in-memory history, and dispatch a navigation event
// corresponding to the incoming URL
const store = configureStore(createMemoryHistory());
store.dispatch(replace(params.location));
// Prepare an instance of the application and perform an inital render that will
// cause any async tasks (e.g., data access) to begin
const routerContext: any = {};
const app = (
<Provider store={ store }>
<StaticRouter context={ routerContext } location={ params.location.path } children={ routes } />
</Provider>
);
renderToString(app);
// Match the incoming request against the list of client-side routes
const store = configureStore();
match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => {
if (error) {
throw error;
}
// If there's a redirection, just send this information back to the host application
if (routerContext.url) {
resolve({ redirectUrl: routerContext.url });
if (redirectLocation) {
resolve({ redirectUrl: redirectLocation.pathname });
return;
}
// Once any async tasks are done, we can perform the final render
// If it didn't match any route, renderProps will be undefined
if (!renderProps) {
throw new Error(`The location '${ params.url }' doesn't match any route configured in react-router.`);
}
// Build an instance of the application
const app = (
<Provider store={ store }>
<RouterContext {...renderProps} />
</Provider>
);
// Perform an initial render that will cause any async tasks (e.g., data access) to begin
renderToString(app);
// Once the tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
@@ -41,3 +47,4 @@ export default createServerRenderer(params => {
}, reject); // Also propagate any errors back into the host application
});
});
});

View File

@@ -1,16 +1,13 @@
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { ApplicationState } from '../store';
import * as CounterStore from '../store/Counter';
import * as WeatherForecasts from '../store/WeatherForecasts';
type CounterProps =
CounterStore.CounterState
& typeof CounterStore.actionCreators
& RouteComponentProps<{}>;
type CounterProps = CounterStore.CounterState & typeof CounterStore.actionCreators;
class Counter extends React.Component<CounterProps, {}> {
class Counter extends React.Component<CounterProps, void> {
public render() {
return <div>
<h1>Counter</h1>
@@ -28,4 +25,4 @@ class Counter extends React.Component<CounterProps, {}> {
export default connect(
(state: ApplicationState) => state.counter, // Selects which state properties are merged into the component's props
CounterStore.actionCreators // Selects which action creators are merged into the component's props
)(Counter) as typeof Counter;
)(Counter);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { ApplicationState } from '../store';
import * as WeatherForecastsState from '../store/WeatherForecasts';
@@ -8,18 +8,18 @@ import * as WeatherForecastsState from '../store/WeatherForecasts';
type WeatherForecastProps =
WeatherForecastsState.WeatherForecastsState // ... state we've requested from the Redux store
& typeof WeatherForecastsState.actionCreators // ... plus action creators we've requested
& RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
& { params: { startDateIndex: string } }; // ... plus incoming routing parameters
class FetchData extends React.Component<WeatherForecastProps, {}> {
class FetchData extends React.Component<WeatherForecastProps, void> {
componentWillMount() {
// This method runs when the component is first added to the page
let startDateIndex = parseInt(this.props.match.params.startDateIndex) || 0;
let startDateIndex = parseInt(this.props.params.startDateIndex) || 0;
this.props.requestWeatherForecasts(startDateIndex);
}
componentWillReceiveProps(nextProps: WeatherForecastProps) {
// This method runs when incoming props (e.g., route params) change
let startDateIndex = parseInt(nextProps.match.params.startDateIndex) || 0;
let startDateIndex = parseInt(nextProps.params.startDateIndex) || 0;
this.props.requestWeatherForecasts(startDateIndex);
}
@@ -70,4 +70,4 @@ class FetchData extends React.Component<WeatherForecastProps, {}> {
export default connect(
(state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props
WeatherForecastsState.actionCreators // Selects which action creators are merged into the component's props
)(FetchData) as typeof FetchData;
)(FetchData);

View File

@@ -1,7 +1,6 @@
import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';
export default class Home extends React.Component<RouteComponentProps<{}>, {}> {
export default class Home extends React.Component<void, void> {
public render() {
return <div>
<h1>Hello, world!</h1>

View File

@@ -1,7 +1,11 @@
import * as React from 'react';
import { NavMenu } from './NavMenu';
export class Layout extends React.Component<{}, {}> {
export interface LayoutProps {
body: React.ReactElement<any>;
}
export class Layout extends React.Component<LayoutProps, void> {
public render() {
return <div className='container-fluid'>
<div className='row'>
@@ -9,7 +13,7 @@ export class Layout extends React.Component<{}, {}> {
<NavMenu />
</div>
<div className='col-sm-9'>
{ this.props.children }
{ this.props.body }
</div>
</div>
</div>;

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { NavLink, Link } from 'react-router-dom';
import { Link } from 'react-router';
export class NavMenu extends React.Component<{}, {}> {
export class NavMenu extends React.Component<void, void> {
public render() {
return <div className='main-nav'>
<div className='navbar navbar-inverse'>
@@ -18,19 +18,19 @@ export class NavMenu extends React.Component<{}, {}> {
<div className='navbar-collapse collapse'>
<ul className='nav navbar-nav'>
<li>
<NavLink exact to={ '/' } activeClassName='active'>
<Link to={ '/' } activeClassName='active'>
<span className='glyphicon glyphicon-home'></span> Home
</NavLink>
</Link>
</li>
<li>
<NavLink to={ '/counter' } activeClassName='active'>
<Link to={ '/counter' } activeClassName='active'>
<span className='glyphicon glyphicon-education'></span> Counter
</NavLink>
</Link>
</li>
<li>
<NavLink to={ '/fetchdata' } activeClassName='active'>
<Link to={ '/fetchdata' } activeClassName='active'>
<span className='glyphicon glyphicon-th-list'></span> Fetch data
</NavLink>
</Link>
</li>
</ul>
</div>

View File

@@ -1,28 +1,26 @@
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store } from 'redux';
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as StoreModule from './store';
import { ApplicationState, reducers } from './store';
import { History } from 'history';
import { routerReducer } from 'react-router-redux';
import * as Store from './store';
export default function configureStore(history: History, initialState?: ApplicationState) {
export default function configureStore(initialState?: Store.ApplicationState) {
// Build middleware. These are functions that can process the actions before they reach the store.
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
// If devTools is installed, connect to it
const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension as () => GenericStoreEnhancer;
const createStoreWithMiddleware = compose(
applyMiddleware(thunk, routerMiddleware(history)),
applyMiddleware(thunk),
devToolsExtension ? devToolsExtension() : f => f
)(createStore);
// Combine all reducers and instantiate the app-wide store instance
const allReducers = buildRootReducer(reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;
const allReducers = buildRootReducer(Store.reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Redux.Store<Store.ApplicationState>;
// Enable Webpack hot module replacement for reducers
if (module.hot) {
module.hot.accept('./store', () => {
const nextRootReducer = require<typeof StoreModule>('./store');
const nextRootReducer = require<typeof Store>('./store');
store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
});
}
@@ -31,5 +29,5 @@ export default function configureStore(history: History, initialState?: Applicat
}
function buildRootReducer(allReducers) {
return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
return combineReducers<Store.ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
}

View File

@@ -1,12 +1,19 @@
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Router, Route, HistoryBase } from 'react-router';
import { Layout } from './components/Layout';
import Home from './components/Home';
import FetchData from './components/FetchData';
import Counter from './components/Counter';
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata/:startDateIndex?' component={ FetchData } />
</Layout>;
export default <Route component={ Layout }>
<Route path='/' components={{ body: Home }} />
<Route path='/counter' components={{ body: Counter }} />
<Route path='/fetchdata' components={{ body: FetchData }}>
<Route path='(:startDateIndex)' /> { /* Optional route segment that does not affect NavMenu highlighting */ }
</Route>
</Route>;
// Enable Hot Module Replacement (HMR)
if (module.hot) {
module.hot.accept();
}

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
</ItemGroup>

View File

@@ -2,41 +2,44 @@
"name": "WebApplicationBasic",
"version": "0.0.0",
"dependencies": {
"@types/history": "4.5.1",
"@types/react": "15.0.24",
"@types/react-dom": "15.5.0",
"@types/react-redux": "4.4.40",
"@types/react-router-dom": "4.0.4",
"@types/react-router-redux": "5.0.1",
"@types/webpack": "2.2.15",
"@types/webpack-env": "1.13.0",
"aspnet-prerendering": "^2.0.5",
"aspnet-webpack": "^1.0.29",
"aspnet-webpack-react": "^2.0.0",
"awesome-typescript-loader": "3.1.3",
"bootstrap": "3.3.7",
"css-loader": "0.28.1",
"@types/history": "^2.0.0",
"@types/react": "^0.14.29",
"@types/react-dom": "^0.14.14",
"@types/react-redux": "^4.4.29",
"@types/react-router": "^2.0.30",
"@types/react-router-redux": "4.0.30",
"@types/redux": "3.5.27",
"@types/webpack": "^2.2.0",
"@types/webpack-env": "^1.13.0",
"aspnet-prerendering": "^2.0.0",
"aspnet-webpack": "^1.0.27",
"aspnet-webpack-react": "^1.0.4",
"awesome-typescript-loader": "^3.0.0",
"babel-core": "^6.5.2",
"babel-loader": "^6.2.3",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
"bootstrap": "^3.3.6",
"css-loader": "^0.23.1",
"domain-task": "^3.0.0",
"event-source-polyfill": "0.0.9",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "0.11.1",
"history": "4.6.1",
"jquery": "3.2.1",
"json-loader": "0.5.4",
"node-noop": "1.0.0",
"react": "15.5.4",
"react-dom": "15.5.4",
"react-hot-loader": "3.0.0-beta.7",
"react-redux": "5.0.4",
"react-router-dom": "4.1.1",
"react-router-redux": "5.0.0-alpha.6",
"redux": "3.6.0",
"redux-thunk": "2.2.0",
"style-loader": "0.17.0",
"typescript": "2.3.2",
"url-loader": "0.5.8",
"webpack": "2.5.1",
"webpack-hot-middleware": "2.18.0",
"webpack-merge": "4.1.0"
"event-source-polyfill": "^0.0.7",
"extract-text-webpack-plugin": "^2.0.0-rc",
"file-loader": "^0.8.5",
"jquery": "^2.2.1",
"json-loader": "^0.5.4",
"node-noop": "^1.0.0",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-redux": "^4.4.5",
"react-router": "^2.8.1",
"react-router-redux": "^4.0.6",
"redux": "^3.6.0",
"redux-thunk": "^2.2.0",
"style-loader": "^0.13.0",
"typescript": "^2.2.1",
"url-loader": "^0.5.7",
"webpack": "^2.2.0",
"webpack-hot-middleware": "^2.12.2",
"webpack-merge": "^0.14.1"
}
}

View File

@@ -2,8 +2,8 @@
"compilerOptions": {
"baseUrl": ".",
"moduleResolution": "node",
"target": "es5",
"jsx": "react",
"target": "es6",
"jsx": "preserve",
"experimentalDecorators": true,
"sourceMap": true,
"skipDefaultLibCheck": true,

View File

@@ -17,6 +17,7 @@ module.exports = (env) => {
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: { loader: 'babel-loader', options: { cacheDirectory: true } } },
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' }
]
},
@@ -29,7 +30,7 @@ module.exports = (env) => {
entry: { 'main-client': './ClientApp/boot-client.tsx' },
module: {
rules: [
{ test: /\.css$/, use: ExtractTextPlugin.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) },
{ test: /\.css$/, use: ExtractTextPlugin.extract({ use: 'css-loader' }) },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},

View File

@@ -21,10 +21,9 @@ module.exports = (env) => {
'bootstrap/dist/css/bootstrap.css',
'domain-task',
'event-source-polyfill',
'history',
'react',
'react-dom',
'react-router-dom',
'react-router',
'react-redux',
'redux',
'redux-thunk',
@@ -50,7 +49,7 @@ module.exports = (env) => {
output: { path: path.join(__dirname, 'wwwroot', 'dist') },
module: {
rules: [
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
{ test: /\.css(\?|$)/, use: extractCSS.extract({ use: 'css-loader' }) }
]
},
plugins: [
@@ -72,7 +71,7 @@ module.exports = (env) => {
libraryTarget: 'commonjs2',
},
module: {
rules: [ { test: /\.css(\?|$)/, use: isDevBuild ? 'css-loader' : 'css-loader?minimize' } ]
rules: [ { test: /\.css(\?|$)/, use: 'css-loader' } ]
},
entry: { vendor: ['aspnet-prerendering', 'react-dom/server'] },
plugins: [

Some files were not shown because too many files have changed in this diff Show More