Compare commits

..

74 Commits

Author SHA1 Message Date
SteveSandersonMS
9c9892ab18 Add example of isomorphic cookies for #581 2017-01-16 12:54:44 +00:00
SteveSandersonMS
f8714e9653 Fix react/redux TypeScript build errors caused by breaking changes in dependencies. Fixes #529, #582, #583 2017-01-16 10:46:28 +00:00
SteveSandersonMS
9d5006107f Bumping NodeServices/SpaServices versions to 1.1.0-rc1-* for RC1 release 2017-01-13 09:02:28 +00:00
SteveSandersonMS
c3316d0caa Hopefully fix BuildQuality setting in build 2017-01-10 19:43:15 +00:00
SteveSandersonMS
38441c6598 Override the build quality determined by KoreBuild 2017-01-10 19:13:15 +00:00
SteveSandersonMS
f8f42a53b2 Increase Mocha timeout. Trying to fix intermittent test failures on AppVeyor. 2017-01-10 10:37:00 +00:00
SteveSandersonMS
a93f59c660 Add Aurelia to generator's README.md. Fixes #561. 2017-01-09 14:43:28 +00:00
SteveSandersonMS
c377e6cd28 For project.json-based projects, fix Kudu deployment by adding a .deployment file at the root 2017-01-07 00:08:06 +00:00
SteveSandersonMS
2a1435d9c1 Bump generator-aspnetcore-spa version to 0.7.3 2017-01-07 00:04:50 +00:00
SteveSandersonMS
6789dbc102 csproj-based projects don't need the _placeholder.txt files, because the publishing code correctly includes dist files even if they were not on disk before 2017-01-07 00:04:20 +00:00
SteveSandersonMS
59526ff16a Update templates' csproj files to fix first-time publishing (previously, it didn't deploy dist files if they were not already on disk) 2017-01-07 00:00:52 +00:00
SteveSandersonMS
8c456aa0fc Make Karma serve .ts files with executable MIME type, otherwise newer versions of Chrome won't run it. Fixes #499 2017-01-03 17:35:30 +00:00
Niclas Emdelius
49bece54c2 Check if response is stream or object without using reference to reflection package. 2017-01-03 13:29:45 +00:00
nicemd
0907379da8 Streamed responses can be be Object or Stream
See issue https://github.com/aspnet/JavaScriptServices/issues/518
2017-01-03 13:29:45 +00:00
SteveSandersonMS
fbb8054c2e Make error message clearer if Node isn't installed (or not found on PATH). Fixes #527 2017-01-03 10:59:26 +00:00
SteveSandersonMS
70f59fc8ec Update core package versions to 1.1.0-beta2-* so that CI will produce non-timestamped packages 2017-01-03 10:29:22 +00:00
SteveSandersonMS
e6cfe93e13 Instruct AppVeyor to regard the generator-aspnetcore-spa.tar.gz file as a build artifact 2017-01-02 12:02:58 +00:00
SteveSandersonMS
dad095b749 Bump generator-aspnetcore-spa version to 0.7.2 2017-01-02 11:59:51 +00:00
SteveSandersonMS
f31b96fe1e Yeoman package builder emits .tar.gz artifact 2017-01-02 11:59:16 +00:00
SteveSandersonMS
312885acde Add AppVeyor status to root README.md 2017-01-02 11:31:40 +00:00
SteveSandersonMS
0763f1062b Add missing trailing slash on publicPath in docs. Fixes #516. 2017-01-02 10:58:03 +00:00
SteveSandersonMS
dcb819f644 Fix port testing logic in tests by being explicit about which interface we're checking 2016-12-15 22:53:12 +00:00
SteveSandersonMS
f722dcf98d Fix PrerenderResult in Webpack sample 2016-12-15 21:45:02 +00:00
SteveSandersonMS
153c9848ca Also build samples/misc 2016-12-15 21:42:40 +00:00
SteveSandersonMS
aefa578e04 Reuse TEST_PROJECT_GLOB to build templates, since the 'test' dir doesn't contain any .NET projects 2016-12-15 21:09:44 +00:00
SteveSandersonMS
953f370336 Wait for port to be freed before continuing 2016-12-15 20:34:55 +00:00
SteveSandersonMS
200d80627c Update Appveyor test environment to Node 6.9.2 to deal with Windows-specific NPM issues 2016-12-15 20:34:46 +00:00
SteveSandersonMS
994653a277 Stop including templates in regular build (they now contain .csproj, which is not supported by that build system, and in any case they are built and end-to-end tested on Appveyor) 2016-12-15 20:32:08 +00:00
SteveSandersonMS
17b107bb42 Fix how 'current' and 'preview' SDKs are merged in Appveyor builds 2016-12-15 19:31:09 +00:00
SteveSandersonMS
67cd5f6093 In csproj projects, reference 1.1.0-preview4-final version of Razor tools 2016-12-15 18:10:43 +00:00
SteveSandersonMS
06ccad4344 Automatically install both the 'current' and 'preview' SDKs when building in Appveyor 2016-12-15 18:07:34 +00:00
SteveSandersonMS
4fd19b4293 Run tests against both csproj and project.json-style projects. Assumes relevant dotnet SDKs are installed locally. 2016-12-15 17:17:04 +00:00
SteveSandersonMS
f34eb582ad Update tests to be compatible with newer Yeoman generator 2016-12-15 16:52:02 +00:00
SteveSandersonMS
4580412f73 Don't leave behind an unused package.json.tmp file 2016-12-15 16:42:25 +00:00
SteveSandersonMS
a76b6e25d4 Add .csproj files for the templates. Some are set up for prerendering; others not. 2016-12-15 16:42:25 +00:00
SteveSandersonMS
2135d77189 generator-aspnetcore-spa support for selecting between project.json and .csproj 2016-12-15 16:42:25 +00:00
SteveSandersonMS
62b676174e generator-aspnetcore-spa now displays its own version number on startup 2016-12-15 16:41:10 +00:00
SteveSandersonMS
67c2cfd84e Add Appveyor builds and webdriver.io tests (tests cover Angular2Spa template only at present) 2016-12-15 16:38:30 +00:00
SteveSandersonMS
6decb30681 Make 'counter' components use <h1> headers to be consistent with other pages 2016-12-15 16:37:29 +00:00
SteveSandersonMS
33ed333f92 Specify SDK at repo root 2016-12-15 16:35:48 +00:00
SteveSandersonMS
6c16ba3da7 Remove template_nodemodules_placeholder.txt files - they are redundant now that we no longer need to publish node_modules to production 2016-12-15 16:35:48 +00:00
SteveSandersonMS
495c95d227 If generator fails because NPM is too old, exit with nonzero status code 2016-12-15 16:35:48 +00:00
SteveSandersonMS
4dd91a9b78 Make template package build script automate running the prepublish steps needed for the Angular2Spa template 2016-12-14 10:21:04 +00:00
SteveSandersonMS
06335eeee1 Fix building generator-aspnetcore-spa (something in a newer TypeScript version broke it) 2016-12-14 09:56:23 +00:00
SteveSandersonMS
eb98067e86 Update docs about using socket transport. Fixes #500 2016-12-13 15:59:43 +00:00
SteveSandersonMS
a25d1f4731 Publish aspnet-webpack 1.0.26 2016-12-13 15:48:44 +00:00
SteveSandersonMS
4c3834361e Stop WebpackDevMiddleware littering the 'dist' directories with blah.host-update.js.map files (they never need to be written to disk) 2016-12-13 15:47:43 +00:00
SteveSandersonMS
4cc61d79e2 Add json-loader to add webpack.config.js files. Helps with #507. 2016-12-13 12:57:06 +00:00
SteveSandersonMS
1c4682e50d Fix all the ConditionalProxyMiddleware errors that happened if you ctrl+c on a "dotnet run" (not "dotnet watch run") since beta-000002. 2016-12-13 11:32:32 +00:00
Sébastien Ros
6545e11bf2 Fix filename in sample
Mismatch between code sample and described js filename.
2016-12-08 10:04:50 +00:00
SteveSandersonMS
8d1cb3a537 Remove angular2-aspnet NPM package source as none of its functionality is still in use (and hasn't been since beta versions of Angular 2) 2016-12-08 10:02:37 +00:00
SteveSandersonMS
8705a4b353 Remove redux-typed from source tree. It's no longer in use and doesn't need to be maintained. Source can be re-added in the future if needed. 2016-12-07 19:02:53 +00:00
SteveSandersonMS
7c685c1354 Add AssemblyInfo.cs to Microsoft.AspNetCore.NodeServices.Sockets package 2016-12-07 18:09:11 +00:00
SteveSandersonMS
633969c7b4 Update Dockerfiles for .NET Core 1.1.0. Fixes #489 2016-12-07 11:15:40 +00:00
SteveSandersonMS
9c1c1b4023 Publish aspnet-prerendering 2.0.2 2016-12-01 17:31:32 +00:00
SteveSandersonMS
0a116ba2a1 Prerendering logic supplies PathBase (formatted as baseUrl) to boot logic 2016-12-01 17:24:24 +00:00
SteveSandersonMS
2b2465ad2e Update prerendering docs to account for aspnet-prerendering 2.0.0 and the new createServerRenderer API. Fixes #479 2016-12-01 14:41:48 +00:00
SteveSandersonMS
dc130adc91 Update aspnet-prerendering to reference baseUrl in domain-task's main module, not domain-task/fetch. Goal is to avoid Webpack having to bundle isomorphic-fetch on server unless you're actually using it. 2016-12-01 14:32:54 +00:00
SteveSandersonMS
b8913d29dd Update domain-task to export baseUrl from 'main' instead of only 'fetch'. Goal is to avoid Webpack detecting dependency on isomorphic-fetch unless you're actually using it. 2016-12-01 14:31:27 +00:00
SteveSandersonMS
5e669d6e7a Publish Yeoman generators 0.7.1 2016-11-30 17:54:46 +00:00
SteveSandersonMS
5d02728159 Publish Yeoman generators version 0.7.0. From here on, the templates produce ASP.NET Core 1.1.0 projects. 2016-11-30 17:09:24 +00:00
SteveSandersonMS
78632617f8 Publish aspnet-webpack 1.0.25 2016-11-30 16:53:39 +00:00
SteveSandersonMS
e2f8031bb8 For HMR, proxy all requests including /__webpack_hmr. Fixes #271. 2016-11-30 16:53:16 +00:00
SteveSandersonMS
2cffab14f6 StringAsTempFile cleans up in a wider range of circumstances (not relying on finalizer running). Helps with #7 but still doesn't cover all cases. 2016-11-30 15:04:08 +00:00
SteveSandersonMS
9001c191c1 Make Angular and React templates reference SpaServices directly. The AngularServices/ReactServices packages might be used in the future, but don't presently contain any extra usable functionality. 2016-11-30 12:19:05 +00:00
SteveSandersonMS
832da2a451 Split out 'socket' hosting model into a separate optional NuGet package, since most developers won't need it 2016-11-30 12:18:57 +00:00
SteveSandersonMS
ebf5a18344 Eliminate the NodeHostingModel enum because it isn't extensible. Instead, use extension methods on NodeServicesOptions to configure a NodeInstanceFactory. 2016-11-30 11:29:57 +00:00
SteveSandersonMS
d865e1f28b Specify latest 'current' SDK in all template global.json files 2016-11-30 11:29:57 +00:00
SteveSandersonMS
62c0680193 Make 'fetchdata' components in templates compatible with latest TypeScript compiler 2016-11-29 19:11:03 +00:00
SteveSandersonMS
a8d3eed32c Detect legacy aspnet-prerendering mode earlier to fix #470 2016-11-29 18:37:43 +00:00
SteveSandersonMS
5d14f11b1b Update all the project templates to .NET Core 1.1.0 2016-11-29 16:36:05 +00:00
SteveSandersonMS
196c0dd32e Update other samples to 1.1.0 dependencies 2016-11-29 16:36:05 +00:00
SteveSandersonMS
31d8eb69a7 Updated all 'misc' samples to 1.1.0 dependencies (and to TypeScript 2 for the Webpack sample) 2016-11-29 16:36:05 +00:00
SteveSandersonMS
4cbbf58600 Update core packages to version 1.1.0-*, upgrading .NET Core dependencies to match 1.1.0 release 2016-11-29 16:35:59 +00:00
160 changed files with 2030 additions and 1078 deletions

View File

@@ -1,5 +1,7 @@
# JavaScriptServices # JavaScriptServices
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/gprilrckx116vc9m/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/javascriptservices/branch/dev)
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
## What is this? ## What is this?

View File

@@ -1,7 +1,44 @@
init: init:
- git config --global core.autocrlf true - git config --global core.autocrlf true
install:
- ps: Install-Product node 6.9.2 x64
# .NET Core SDK binaries
# Download .NET Core SDK and add to PATH
- 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: build_script:
- build.cmd verify - ps: Push-Location
- cd templates/package-builder
- npm install
- npm run build
- ps: Pop-Location
artifacts:
- path: templates\package-builder\dist\artifacts\generator-aspnetcore-spa.tar.gz
name: generator-aspnetcore-spa
# - build.cmd verify
clone_depth: 1 clone_depth: 1
test: off test_script:
- 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
# 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))
deploy: off deploy: off

View File

@@ -1,3 +1,4 @@
{ {
"projects": ["src"] "projects": ["src"],
"sdk": { "version": "1.0.0-preview2-1-003177" }
} }

View File

@@ -2,7 +2,13 @@
var VERSION='0.1' var VERSION='0.1'
var FULL_VERSION='0.1' var FULL_VERSION='0.1'
var AUTHORS='Microsoft Open Technologies, Inc.' var AUTHORS='Microsoft Open Technologies, Inc.'
var SAMPLES_PROJECT_GLOB='templates/*/project.json' var TEST_PROJECT_GLOB='templates/*/project.json'
var SAMPLES_PROJECT_GLOB='samples/misc/*/project.json'
@{
// The build quality values are set in each of the project.json files
BuildQuality = "";
}
use-standard-lifecycle use-standard-lifecycle
k-standard-goals k-standard-goals

View File

@@ -2,6 +2,7 @@ versionSuffix=$1
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
projects=( projects=(
./src/Microsoft.AspNetCore.NodeServices ./src/Microsoft.AspNetCore.NodeServices
./src/Microsoft.AspNetCore.NodeServices.Sockets
./src/Microsoft.AspNetCore.SpaServices ./src/Microsoft.AspNetCore.SpaServices
./src/Microsoft.AspNetCore.AngularServices ./src/Microsoft.AspNetCore.AngularServices
./src/Microsoft.AspNetCore.ReactServices ./src/Microsoft.AspNetCore.ReactServices

View File

@@ -15,28 +15,28 @@
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.1", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.AngularServices": "1.0.0-*", "Microsoft.AspNetCore.AngularServices": "1.1.0-*",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0", "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Razor.Tools": { "Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.EntityFrameworkCore.SQLite": "1.0.0", "Microsoft.EntityFrameworkCore.SQLite": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"AutoMapper": "5.0.2" "AutoMapper": "5.0.2"
}, },
@@ -47,7 +47,7 @@
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.1": {
"imports": [ "imports": [
"dotnet5.6", "dotnet5.6",
"portable-net45+win8" "portable-net45+win8"

View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.NodeServices.Sockets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApplication namespace ConsoleApplication
@@ -16,7 +17,10 @@ namespace ConsoleApplication
// Set up the DI system // Set up the DI system
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddNodeServices(options => { services.AddNodeServices(options => {
options.HostingModel = NodeServicesOptions.DefaultNodeHostingModel; // To compare with Socket hosting, uncomment the following line
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
//options.UseSocketHosting();
options.ProjectPath = Directory.GetCurrentDirectory(); options.ProjectPath = Directory.GetCurrentDirectory();
options.WatchFileExtensions = new string[] {}; // Don't watch anything options.WatchFileExtensions = new string[] {}; // Don't watch anything
}); });

View File

@@ -5,11 +5,12 @@
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.NodeServices": "1.0.0-*", "Microsoft.AspNetCore.NodeServices": "1.1.0-*",
"Microsoft.Extensions.DependencyInjection": "1.0.0" "Microsoft.AspNetCore.NodeServices.Sockets": "1.1.0-*",
"Microsoft.Extensions.DependencyInjection": "1.1.0"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.0": {

View File

@@ -12,29 +12,22 @@
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", "Microsoft.AspNetCore.Mvc.TagHelpers": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.AspNetCore.NodeServices": "1.1.0-*"
"Microsoft.AspNetCore.NodeServices": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.0": {}
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
}, },
"publishExclude": [ "publishExclude": [
"node_modules", "node_modules",

View File

@@ -1,6 +1,9 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
yarn.lock
wwwroot/dist
# User-specific files # User-specific files
*.suo *.suo
*.user *.user

View File

@@ -38,7 +38,8 @@ namespace Webpack.ActionResults
request.GetEncodedUrl(), request.GetEncodedUrl(),
request.Path + request.QueryString.Value, request.Path + request.QueryString.Value,
_dataToSupply, _dataToSupply,
/* timeoutMilliseconds */ 30000 /* timeoutMilliseconds */ 30000,
/* requestPathBase */ "/"
); );
response.ContentType = "text/html"; response.ContentType = "text/html";

View File

@@ -11,9 +11,10 @@
"webpack-hot-middleware": "^2.7.1" "webpack-hot-middleware": "^2.7.1"
}, },
"dependencies": { "dependencies": {
"aspnet-webpack": "^1.0.3",
"aspnet-prerendering": "^1.0.4", "aspnet-prerendering": "^1.0.4",
"aspnet-webpack": "^1.0.3",
"ts-loader": "^0.8.1", "ts-loader": "^0.8.1",
"typescript": "^1.7.5" "typescript": "^2.0.0",
"webpack": "^1.13.3"
} }
} }

View File

@@ -12,29 +12,22 @@
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", "Microsoft.AspNetCore.Mvc.TagHelpers": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.AspNetCore.SpaServices": "1.1.0-*"
"Microsoft.AspNetCore.SpaServices": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.0": {}
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
}, },
"publishOptions": { "publishOptions": {
"exclude": [ "exclude": [

View File

@@ -2,9 +2,10 @@
"compilerOptions": { "compilerOptions": {
"moduleResolution": "node", "moduleResolution": "node",
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es5",
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true "sourceMap": true,
"lib": ["es6", "dom"]
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules"

View File

@@ -10,6 +10,6 @@ module.exports = {
}, },
plugins: [ plugins: [
extractLESS, extractLESS,
new webpack.optimize.UglifyJsPlugin({ minimize: true }) new webpack.optimize.UglifyJsPlugin({ minimize: true, compressor: { warnings: false } })
] ]
}; };

View File

@@ -1,4 +1,4 @@
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer } from 'redux'; import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store as ReduxStore } from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { routerReducer } from 'react-router-redux'; import { routerReducer } from 'react-router-redux';
import * as Store from './store'; import * as Store from './store';
@@ -16,7 +16,7 @@ export default function configureStore(initialState?: Store.ApplicationState) {
// Combine all reducers and instantiate the app-wide store instance // Combine all reducers and instantiate the app-wide store instance
const allReducers = buildRootReducer(Store.reducers); const allReducers = buildRootReducer(Store.reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Redux.Store<Store.ApplicationState>; const store = createStoreWithMiddleware(allReducers, initialState) as ReduxStore<Store.ApplicationState>;
// Enable Webpack hot module replacement for reducers // Enable Webpack hot module replacement for reducers
if (module.hot) { if (module.hot) {

View File

@@ -2,6 +2,7 @@
"name": "music-store", "name": "music-store",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@types/history": "^2.0.0",
"@types/react": "^0.14.29", "@types/react": "^0.14.29",
"@types/react-bootstrap": "^0.0.35", "@types/react-bootstrap": "^0.0.35",
"@types/react-dom": "^0.14.14", "@types/react-dom": "^0.14.14",
@@ -9,7 +10,6 @@
"@types/react-router": "^2.0.30", "@types/react-router": "^2.0.30",
"@types/react-router-bootstrap": "^0.0.27", "@types/react-router-bootstrap": "^0.0.27",
"@types/react-router-redux": "^4.0.30", "@types/react-router-redux": "^4.0.30",
"@types/redux": "3.5.27",
"@types/redux-thunk": "^2.1.28", "@types/redux-thunk": "^2.1.28",
"@types/source-map": "^0.1.28", "@types/source-map": "^0.1.28",
"@types/uglify-js": "^2.0.27", "@types/uglify-js": "^2.0.27",
@@ -42,7 +42,7 @@
"redux-typed": "^2.0.0", "redux-typed": "^2.0.0",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"ts-loader": "^0.8.1", "ts-loader": "^0.8.1",
"typescript": "2.0.3", "typescript": "^2.0.3",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.13.2", "webpack": "^1.13.2",
"webpack-hot-middleware": "^2.12.2", "webpack-hot-middleware": "^2.12.2",

View File

@@ -1,28 +1,28 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.1", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.ReactServices": "1.1.0-*",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0", "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Razor.Tools": { "Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.EntityFrameworkCore.SQLite": "1.0.0", "Microsoft.EntityFrameworkCore.SQLite": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"AutoMapper": "5.0.2" "AutoMapper": "5.0.2"
}, },
@@ -34,10 +34,7 @@
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.0": {
"imports": [ "imports": []
"dotnet5.6",
"portable-net45+win8"
]
} }
}, },

View File

@@ -1,11 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".",
"moduleResolution": "node", "moduleResolution": "node",
"target": "es6", "target": "es6",
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"types": [ "webpack-env", "whatwg-fetch" ] "types": [ "webpack-env", "whatwg-fetch" ],
"paths": {
// Fix "Duplicate identifier" errors caused by multiple dependencies fetching their own copies of type definitions.
// We tell TypeScript which type definitions module to treat as the canonical one (instead of combining all of them).
"history": ["./node_modules/@types/history/index"],
"redux": ["./node_modules/@types/redux/index"],
"react": ["./node_modules/@types/react/index"]
}
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules"

View File

@@ -10,7 +10,7 @@ module.exports = {
module: { module: {
loaders: [ loaders: [
{ test: /\.ts(x?)$/, include: /ReactApp/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.ts(x?)$/, include: /ReactApp/, exclude: /node_modules/, loader: 'babel-loader' },
{ test: /\.ts(x?)$/, include: /ReactApp/, exclude: /node_modules/, loader: 'ts-loader' }, { test: /\.ts(x?)$/, include: /ReactApp/, exclude: /node_modules/, loader: 'ts-loader?silent' },
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }, { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') },
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' } { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }
] ]

View File

@@ -9,29 +9,17 @@
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.ReactServices": "1.1.0-*",
"Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0"
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.AspNetCore.ReactServices": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.0": {}
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
}, },
"publishOptions": { "publishOptions": {
"exclude": [ "exclude": [

View File

@@ -1,3 +0,0 @@
/node_modules/
/dist/
/bundles/

View File

@@ -1,4 +0,0 @@
/src/
/tsconfig.json
/build.js
/typings/

View File

@@ -1,33 +0,0 @@
// -------------
// No need to invoke this directly. To run a build, execute:
// npm run prepublish
// -------------
var Builder = require('systemjs-builder');
var builder = new Builder('./');
builder.config({
defaultJSExtensions: true,
paths: {
'angular2-aspnet': 'dist/Exports',
'angular2-aspnet/*': 'dist/*'
},
meta: {
'angular2/*': { build: false },
'rxjs/*': { build: false }
}
});
var entryPoint = 'dist/Exports';
var tasks = [
builder.bundle(entryPoint, './bundles/angular2-aspnet.js'),
builder.bundle(entryPoint, './bundles/angular2-aspnet.min.js', { minify: true })
];
Promise.all(tasks)
.then(function() {
console.log('Build complete');
})
.catch(function(err) {
console.error('Build error');
console.error(err);
});

View File

@@ -1,32 +0,0 @@
{
"name": "angular2-aspnet",
"version": "0.0.6",
"description": "Helpers for Angular 2 apps built on ASP.NET",
"main": "./dist/Exports",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "rimraf *.d.ts dist/*.d.ts && tsc && node build.js"
},
"typings": "dist/Exports",
"author": "Microsoft",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/aspnet/JavaScriptServices/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/aspnet/JavaScriptServices.git"
},
"dependencies": {
"angular2": "2.0.0-beta.13",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.2"
},
"devDependencies": {
"es6-shim": "^0.35.0",
"rimraf": "^2.5.4",
"systemjs-builder": "^0.14.11",
"typescript": "^2.0.0",
"zone.js": "^0.6.10"
}
}

View File

@@ -1,59 +0,0 @@
If you just want to use this package, then you *don't have to build it*. Instead, just grab the prebuilt package from NPM:
npm install angular2-aspnet
The rest of this file is notes for anyone contributing to this package itself.
## How to build
Run the following:
npm install
npm run prepublish
Requirements:
* Node, NPM
* `tsc` installed globally (via `npm install -g typescript`)
## Project structure
This package is intended to be consumable both on the server in Node.js, and on the client. Also, it's written in TypeScript,
which neither of those environments knows natively, but the TypeScript type definitions need to get delivered with the package
so that developers get a good IDE experience when consuming it.
The build process is therefore:
1. Compile the TypeScript to produce the development-time (.d.ts) and server-side (.js) artifacts
`tsc` reads `tsconfig.json` and is instructed to compile all the `.ts` files in `src/`. It produces a corresponding
structure of `.js` and `.d.ts` files in `dist/`.
When a developer consumes the resulting package (via `npm install angular2-aspnet`),
- No additional copy of `angular2` will be installed, because this package's dependency on it is declared as a
`peerDependency`. This means it will work with whatever (compatible) version of `angular2` is already installed.
- At runtime inside Node.js, the `main` configuration in `package.json` means the developer can use a standard
`import` statement to consume this package (i.e., `import * from 'angular2-aspnet';` in either JS or TS files).
- At development time inside an IDE such as Visual Studio Code, the `typings` configuration in `package.json` means
the IDE will use the corresponding `.d.ts` file as type metadata for the variable imported that way.
2. Use the SystemJS builder to produce the client-side artifacts
`build.js` uses the SystemJS Builder API to combine files in `dist/` into `.js` files ready for use in client-side
SystemJS environments, and puts them in `bundles/`. The bundle files contain `System.register` calls so that any
other part of your client-side code that tries to import `angular2-aspnet` via SystemJS will get that module at runtime.
To make it work in an application:
- Set up some build step that copies your chosen bundle file from `bundles/` to some location where it will
be served to the client
- Below your `<script>` tag that loads SystemJS itself, and above the `<script>` tag that makes the first call to
`System.import`, have a `<script>` tag that loads the desired `angular2-aspnet.js` bundle file
For an example, see https://github.com/aspnet/NodeServices/tree/master/samples/angular/MusicStore
Of course, you can also bundle the `angular2-aspnet.js` file into a larger SystemJS bundle if you want to combine
it with the rest of the code in your application.
Currently, this build system does *not* attempt to send sourcemaps of the original TypeScript to the client. This
could be added if a strong need emerges.

View File

@@ -1,59 +0,0 @@
import { provide, Injectable, Provider } from 'angular2/core';
import { Connection, ConnectionBackend, Http, XHRBackend, RequestOptions, Request, RequestMethod, Response, ResponseOptions, ReadyState } from 'angular2/http';
@Injectable()
export class CachePrimedConnectionBackend extends ConnectionBackend {
private _preCachedResponses: PreCachedResponses;
constructor(private _underlyingBackend: ConnectionBackend, private _baseResponseOptions: ResponseOptions) {
super();
this._preCachedResponses = (<any>window).__preCachedResponses || {};
}
public createConnection(request: Request): Connection {
let cacheKey = request.url;
if (request.method === RequestMethod.Get && this._preCachedResponses.hasOwnProperty(cacheKey)) {
return new CacheHitConnection(request, this._preCachedResponses[cacheKey], this._baseResponseOptions);
} else {
return this._underlyingBackend.createConnection(request);
}
}
}
class CacheHitConnection implements Connection {
readyState: ReadyState;
request: Request;
response: any;
constructor (req: Request, cachedResponse: PreCachedResponse, baseResponseOptions: ResponseOptions) {
this.request = req;
this.readyState = ReadyState.Done;
// Workaround for difficulty consuming CommonJS default exports in TypeScript. Note that it has to be a dynamic
// 'require', and not an 'import' statement, because the module isn't available on the server.
let obsCtor: any = require('rxjs/Observable').Observable;
this.response = new obsCtor(responseObserver => {
let response = new Response(new ResponseOptions({ body: cachedResponse.body, status: cachedResponse.statusCode }));
responseObserver.next(response);
responseObserver.complete();
});
}
}
declare var require: any; // Part of the workaround mentioned below. Can remove this after updating Angular.
interface PreCachedResponses {
[url: string]: PreCachedResponse;
}
interface PreCachedResponse {
statusCode: number;
body: string;
}
export const CACHE_PRIMED_HTTP_PROVIDERS = [
provide(Http, {
useFactory: (xhrBackend, requestOptions, responseOptions) => new Http(new CachePrimedConnectionBackend(xhrBackend, responseOptions), requestOptions),
deps: [XHRBackend, RequestOptions, ResponseOptions]
}),
];

View File

@@ -1,2 +0,0 @@
export * from './CachePrimedHttp';
export * from './Validation';

View File

@@ -1,34 +0,0 @@
import { ControlGroup } from 'angular2/common';
import { Response } from 'angular2/http';
export class Validation {
public static showValidationErrors(response: ValidationErrorResult | Response, controlGroup: ControlGroup): void {
if (response instanceof Response) {
var httpResponse = <Response>response;
response = <ValidationErrorResult>(httpResponse.json());
}
// It's not yet clear whether this is a legitimate and supported use of the ng.ControlGroup API.
// Need feedback from the Angular 2 team on whether there's a better way.
var errors = <ValidationErrorResult>response;
Object.keys(errors || {}).forEach(key => {
errors[key].forEach(errorMessage => {
// If there's a specific control for this key, then use it. Otherwise associate the error
// with the whole control group.
var control = controlGroup.controls[key] || controlGroup;
// This is rough. Need to find out if there's a better way, or if this is even supported.
if (!control.errors) {
(<any>control)._errors = {};
}
control.errors[errorMessage] = true;
});
});
}
}
export interface ValidationErrorResult {
[propertyName: string]: string[];
}

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": false,
"declaration": true,
"experimentalDecorators": true,
"noLib": false,
"outDir": "./dist",
"lib": ["es2015", "dom"]
},
"exclude": [
"node_modules"
]
}

View File

@@ -1,6 +1,6 @@
{ {
"description": "Helpers for building Angular 2 applications on ASP.NET Core.", "description": "Helpers for building Angular 2 applications on ASP.NET Core.",
"version": "1.0.0-*", "version": "1.1.0-beta2-*",
"packOptions": { "packOptions": {
"repository": { "repository": {
"type": "git", "type": "git",
@@ -18,8 +18,8 @@
"xmlDoc": true "xmlDoc": true
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.Mvc.TagHelpers": "1.1.0",
"Microsoft.AspNetCore.SpaServices": "1.0.0-*" "Microsoft.AspNetCore.SpaServices": "1.1.0-*"
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": {},

View File

@@ -0,0 +1,3 @@
/bin/
/node_modules/
yarn.lock

View File

@@ -44,11 +44,81 @@
/* 0 */ /* 0 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(7); module.exports = __webpack_require__(1);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
// but simplifies things for the consumer of this module.
__webpack_require__(2);
var net = __webpack_require__(3);
var path = __webpack_require__(4);
var readline = __webpack_require__(5);
var ArgsUtil_1 = __webpack_require__(6);
var ExitWhenParentExits_1 = __webpack_require__(7);
var virtualConnectionServer = __webpack_require__(8);
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
// reference to Node's runtime 'require' function.
var dynamicRequire = eval('require');
// Signal to the .NET side when we're ready to accept invocations
var server = net.createServer().on('listening', function () {
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
});
// Each virtual connection represents a separate invocation
virtualConnectionServer.createInterface(server).on('connection', function (connection) {
readline.createInterface(connection, null).on('line', function (line) {
try {
// Get a reference to the function to invoke
var invocation = JSON.parse(line);
var invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
var invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
// Prepare a callback for accepting non-streamed JSON responses
var hasInvokedCallback_1 = false;
var invocationCallback = function (errorValue, successValue) {
if (hasInvokedCallback_1) {
throw new Error('Cannot supply more than one result. The callback has already been invoked,'
+ ' or the result stream has already been accessed');
}
hasInvokedCallback_1 = true;
connection.end(JSON.stringify({
result: successValue,
errorMessage: errorValue && (errorValue.message || errorValue),
errorDetails: errorValue && (errorValue.stack || null)
}));
};
// Also support streamed binary responses
Object.defineProperty(invocationCallback, 'stream', {
enumerable: true,
get: function () {
hasInvokedCallback_1 = true;
return connection;
}
});
// Actually invoke it, passing through any supplied args
invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
}
catch (ex) {
connection.end(JSON.stringify({
errorMessage: ex.message,
errorDetails: ex.stack
}));
}
});
});
// Begin listening now. The underlying transport varies according to the runtime platform.
// On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
var useWindowsNamedPipes = /^win/.test(process.platform);
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
server.listen(listenAddress);
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
/***/ }, /***/ },
/* 1 */,
/* 2 */ /* 2 */
/***/ function(module, exports) { /***/ function(module, exports) {
@@ -90,7 +160,12 @@
/***/ }, /***/ },
/* 3 */, /* 3 */
/***/ function(module, exports) {
module.exports = require("net");
/***/ },
/* 4 */ /* 4 */
/***/ function(module, exports) { /***/ function(module, exports) {
@@ -98,6 +173,12 @@
/***/ }, /***/ },
/* 5 */ /* 5 */
/***/ function(module, exports) {
module.exports = require("readline");
/***/ },
/* 6 */
/***/ function(module, exports) { /***/ function(module, exports) {
"use strict"; "use strict";
@@ -123,7 +204,7 @@
/***/ }, /***/ },
/* 6 */ /* 7 */
/***/ function(module, exports) { /***/ function(module, exports) {
/* /*
@@ -189,96 +270,13 @@
} }
/***/ },
/* 7 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
// but simplifies things for the consumer of this module.
__webpack_require__(2);
var net = __webpack_require__(8);
var path = __webpack_require__(4);
var readline = __webpack_require__(9);
var ArgsUtil_1 = __webpack_require__(5);
var ExitWhenParentExits_1 = __webpack_require__(6);
var virtualConnectionServer = __webpack_require__(10);
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
// reference to Node's runtime 'require' function.
var dynamicRequire = eval('require');
// Signal to the .NET side when we're ready to accept invocations
var server = net.createServer().on('listening', function () {
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
});
// Each virtual connection represents a separate invocation
virtualConnectionServer.createInterface(server).on('connection', function (connection) {
readline.createInterface(connection, null).on('line', function (line) {
try {
// Get a reference to the function to invoke
var invocation = JSON.parse(line);
var invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
var invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
// Prepare a callback for accepting non-streamed JSON responses
var hasInvokedCallback_1 = false;
var invocationCallback = function (errorValue, successValue) {
if (hasInvokedCallback_1) {
throw new Error('Cannot supply more than one result. The callback has already been invoked,'
+ ' or the result stream has already been accessed');
}
hasInvokedCallback_1 = true;
connection.end(JSON.stringify({
result: successValue,
errorMessage: errorValue && (errorValue.message || errorValue),
errorDetails: errorValue && (errorValue.stack || null)
}));
};
// Also support streamed binary responses
Object.defineProperty(invocationCallback, 'stream', {
enumerable: true,
get: function () {
hasInvokedCallback_1 = true;
return connection;
}
});
// Actually invoke it, passing through any supplied args
invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
}
catch (ex) {
connection.end(JSON.stringify({
errorMessage: ex.message,
errorDetails: ex.stack
}));
}
});
});
// Begin listening now. The underlying transport varies according to the runtime platform.
// On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
var useWindowsNamedPipes = /^win/.test(process.platform);
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
server.listen(listenAddress);
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
/***/ }, /***/ },
/* 8 */ /* 8 */
/***/ function(module, exports) {
module.exports = require("net");
/***/ },
/* 9 */
/***/ function(module, exports) {
module.exports = require("readline");
/***/ },
/* 10 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
"use strict"; "use strict";
var events_1 = __webpack_require__(11); var events_1 = __webpack_require__(9);
var VirtualConnection_1 = __webpack_require__(12); var VirtualConnection_1 = __webpack_require__(10);
// Keep this in sync with the equivalent constant in the .NET code. Both sides split up their transmissions into frames with this max length, // Keep this in sync with the equivalent constant in the .NET code. Both sides split up their transmissions into frames with this max length,
// and both will reject longer frames. // and both will reject longer frames.
var MaxFrameBodyLength = 16 * 1024; var MaxFrameBodyLength = 16 * 1024;
@@ -460,13 +458,13 @@
/***/ }, /***/ },
/* 11 */ /* 9 */
/***/ function(module, exports) { /***/ function(module, exports) {
module.exports = require("events"); module.exports = require("events");
/***/ }, /***/ },
/* 12 */ /* 10 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
"use strict"; "use strict";
@@ -475,17 +473,18 @@
function __() { this.constructor = d; } function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}; };
var stream_1 = __webpack_require__(13); var stream_1 = __webpack_require__(11);
/** /**
* Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection. * Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection.
*/ */
var VirtualConnection = (function (_super) { var VirtualConnection = (function (_super) {
__extends(VirtualConnection, _super); __extends(VirtualConnection, _super);
function VirtualConnection(_beginWriteCallback) { function VirtualConnection(_beginWriteCallback) {
_super.call(this); var _this = _super.call(this) || this;
this._beginWriteCallback = _beginWriteCallback; _this._beginWriteCallback = _beginWriteCallback;
this._flowing = false; _this._flowing = false;
this._receivedDataQueue = []; _this._receivedDataQueue = [];
return _this;
} }
VirtualConnection.prototype._read = function () { VirtualConnection.prototype._read = function () {
this._flowing = true; this._flowing = true;
@@ -516,7 +515,7 @@
/***/ }, /***/ },
/* 13 */ /* 11 */
/***/ function(module, exports) { /***/ function(module, exports) {
module.exports = require("stream"); module.exports = require("stream");

View File

@@ -2,7 +2,7 @@ using System.IO;
using System.IO.Pipes; using System.IO.Pipes;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
internal class NamedPipeConnection : StreamConnection internal class NamedPipeConnection : StreamConnection
{ {

View File

@@ -2,7 +2,7 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
internal abstract class StreamConnection : IDisposable internal abstract class StreamConnection : IDisposable
{ {

View File

@@ -2,7 +2,7 @@ using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
internal class UnixDomainSocketConnection : StreamConnection internal class UnixDomainSocketConnection : StreamConnection
{ {

View File

@@ -3,7 +3,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
// From System.IO.Pipes/src/System/Net/Sockets/UnixDomainSocketEndPoint.cs (an internal class in System.IO.Pipes) // From System.IO.Pipes/src/System/Net/Sockets/UnixDomainSocketEndPoint.cs (an internal class in System.IO.Pipes)
internal sealed class UnixDomainSocketEndPoint : EndPoint internal sealed class UnixDomainSocketEndPoint : EndPoint

View File

@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]

View File

@@ -1,16 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections; using Microsoft.AspNetCore.NodeServices.HostingModels;
using Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections; using Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections;
using Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNetCore.NodeServices.HostingModels namespace Microsoft.AspNetCore.NodeServices.Sockets
{ {
/// <summary> /// <summary>
/// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol /// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
@@ -42,21 +41,19 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private string _socketAddress; private string _socketAddress;
private VirtualConnectionClient _virtualConnectionClient; private VirtualConnectionClient _virtualConnectionClient;
public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, public SocketNodeInstance(NodeServicesOptions options, string socketAddress)
ILogger nodeInstanceOutputLogger, IDictionary<string, string> environmentVars,
int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort)
: base( : base(
EmbeddedResourceReader.Read( EmbeddedResourceReader.Read(
typeof(SocketNodeInstance), typeof(SocketNodeInstance),
"/Content/Node/entrypoint-socket.js"), "/Content/Node/entrypoint-socket.js"),
projectPath, options.ProjectPath,
watchFileExtensions, options.WatchFileExtensions,
MakeNewCommandLineOptions(socketAddress), MakeNewCommandLineOptions(socketAddress),
nodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
environmentVars, options.EnvironmentVariables,
invocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,
launchWithDebugging, options.LaunchWithDebugging,
debuggingPort) options.DebuggingPort)
{ {
_socketAddress = socketAddress; _socketAddress = socketAddress;
} }
@@ -79,7 +76,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
// wait for the same connection task. There's no reason why the first caller should have the // wait for the same connection task. There's no reason why the first caller should have the
// special ability to cancel the connection process in a way that would affect subsequent // special ability to cancel the connection process in a way that would affect subsequent
// callers. So, each caller just independently stops awaiting connection if that call is cancelled. // callers. So, each caller just independently stops awaiting connection if that call is cancelled.
await EnsureVirtualConnectionClientCreated().OrThrowOnCancellation(cancellationToken); await ThrowOnCancellation(EnsureVirtualConnectionClientCreated(), cancellationToken);
} }
// For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new // For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new
@@ -215,6 +212,17 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
return $"--listenAddress {listenAddress}"; return $"--listenAddress {listenAddress}";
} }
private static Task ThrowOnCancellation(Task task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task // If the task is already completed, no need to wrap it in a further layer of task
: task.ContinueWith(
_ => {}, // If the task completes, allow execution to continue
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
#pragma warning disable 649 // These properties are populated via JSON deserialization #pragma warning disable 649 // These properties are populated via JSON deserialization
private class RpcJsonResponse<TResult> private class RpcJsonResponse<TResult>
{ {

View File

@@ -0,0 +1,21 @@
using System;
namespace Microsoft.AspNetCore.NodeServices.Sockets
{
/// <summary>
/// Extension methods that help with populating a <see cref="NodeServicesOptions"/> object.
/// </summary>
public static class NodeServicesOptionsExtensions
{
/// <summary>
/// Configures the <see cref="INodeServices"/> service so that it will use out-of-process
/// Node.js instances and perform RPC calls over binary sockets (on Windows, this is
/// implemented as named pipes; on other platforms it uses domain sockets).
/// </summary>
public static void UseSocketHosting(this NodeServicesOptions options)
{
var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
options.NodeInstanceFactory = () => new SocketNodeInstance(options, pipeName);
}
}
}

View File

@@ -1,12 +1,12 @@
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive, // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
// but simplifies things for the consumer of this module. // but simplifies things for the consumer of this module.
import './Util/OverrideStdOutputs'; import '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/OverrideStdOutputs';
import * as net from 'net'; import * as net from 'net';
import * as path from 'path'; import * as path from 'path';
import * as readline from 'readline'; import * as readline from 'readline';
import { Duplex } from 'stream'; import { Duplex } from 'stream';
import { parseArgs } from './Util/ArgsUtil'; import { parseArgs } from '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/ArgsUtil';
import { exitWhenParentExits } from './Util/ExitWhenParentExits'; import { exitWhenParentExits } from '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits';
import * as virtualConnectionServer from './VirtualConnections/VirtualConnectionServer'; import * as virtualConnectionServer from './VirtualConnections/VirtualConnectionServer';
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es3",
"module": "commonjs",
"moduleResolution": "node",
"types": ["node"]
},
"exclude": [
"node_modules"
]
}

View File

@@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
{ {
/// <summary> /// <summary>
/// A virtual read/write connection, typically to a remote process. Multiple virtual connections can be /// A virtual read/write connection, typically to a remote process. Multiple virtual connections can be

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
{ {
/// <summary> /// <summary>
/// A callback that will be invoked if the <see cref="VirtualConnectionClient"/> encounters a read error. /// A callback that will be invoked if the <see cref="VirtualConnectionClient"/> encounters a read error.

View File

@@ -0,0 +1,18 @@
{
"name": "nodeservices.sockets",
"version": "1.0.0",
"description": "This is not really an NPM package and will not be published. This file exists only to reference compilation tools.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "./node_modules/.bin/webpack"
},
"author": "Microsoft",
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^6.0.42",
"ts-loader": "^0.8.2",
"typescript": "^2.0.0",
"webpack": "^1.13.1"
}
}

View File

@@ -0,0 +1,39 @@
{
"description": "Socket-based RPC for Microsoft.AspNetCore.NodeServices",
"version": "1.1.0-beta2-*",
"packOptions": {
"repository": {
"type": "git",
"url": "git://github.com/aspnet/javascriptservices"
},
"tags": [
"aspnetcore",
"aspnetcoremvc",
"nodeservices"
]
},
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk",
"embed": [
"Content/**/*"
],
"xmlDoc": true
},
"dependencies": {
"Microsoft.AspNetCore.NodeServices": "1.1.0-*"
},
"frameworks": {
"net451": {
"dependencies": {
"Microsoft.Tpl.Dataflow": "4.5.24"
}
},
"netstandard1.6": {
"dependencies": {
"System.IO.Pipes": "4.3.0",
"System.Threading.Tasks.Dataflow": "4.7.0"
}
}
}
}

View File

@@ -0,0 +1,20 @@
module.exports = {
target: 'node',
externals: ['fs', 'net', 'events', 'readline', 'stream'],
resolve: {
extensions: [ '.ts' ]
},
module: {
loaders: [
{ test: /\.ts$/, loader: 'ts-loader' },
]
},
entry: {
'entrypoint-socket': ['./TypeScript/SocketNodeInstanceEntryPoint'],
},
output: {
libraryTarget: 'commonjs',
path: './Content/Node',
filename: '[name].js'
}
};

View File

@@ -1,2 +1,3 @@
/bin/ /bin/
/node_modules/ /node_modules/
yarn.lock

View File

@@ -1,18 +0,0 @@
namespace Microsoft.AspNetCore.NodeServices
{
/// <summary>
/// Represents a way of creating and invoking code in a Node.js environment.
/// </summary>
public enum NodeHostingModel
{
/// <summary>
/// An out-of-process Node.js instance where RPC calls are made via HTTP.
/// </summary>
Http,
/// <summary>
/// An out-of-process Node.js instance where RPC calls are made over binary sockets.
/// </summary>
Socket,
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using Microsoft.AspNetCore.NodeServices.HostingModels;
namespace Microsoft.AspNetCore.NodeServices namespace Microsoft.AspNetCore.NodeServices
{ {
@@ -19,33 +18,8 @@ namespace Microsoft.AspNetCore.NodeServices
{ {
throw new ArgumentNullException(nameof (options)); throw new ArgumentNullException(nameof (options));
} }
return new NodeServicesImpl(() => CreateNodeInstance(options));
}
private static INodeInstance CreateNodeInstance(NodeServicesOptions options) return new NodeServicesImpl(options.NodeInstanceFactory);
{
if (options.NodeInstanceFactory != null)
{
// If you've explicitly supplied an INodeInstance factory, we'll use that. This is useful for
// custom INodeInstance implementations.
return options.NodeInstanceFactory();
}
else
{
switch (options.HostingModel)
{
case NodeHostingModel.Http:
return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0);
case NodeHostingModel.Socket:
var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort);
default:
throw new ArgumentException("Unknown hosting model: " + options.HostingModel);
}
}
} }
} }
} }

View File

@@ -13,11 +13,6 @@ namespace Microsoft.AspNetCore.NodeServices
/// </summary> /// </summary>
public class NodeServicesOptions public class NodeServicesOptions
{ {
/// <summary>
/// Defines the default <see cref="NodeHostingModel"/>.
/// </summary>
public const NodeHostingModel DefaultNodeHostingModel = NodeHostingModel.Http;
internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds); internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds);
private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000; private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000;
private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices";
@@ -36,7 +31,6 @@ namespace Microsoft.AspNetCore.NodeServices
EnvironmentVariables = new Dictionary<string, string>(); EnvironmentVariables = new Dictionary<string, string>();
InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds; InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
HostingModel = DefaultNodeHostingModel;
WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
@@ -53,15 +47,15 @@ namespace Microsoft.AspNetCore.NodeServices
NodeInstanceOutputLogger = loggerFactory != null NodeInstanceOutputLogger = loggerFactory != null
? loggerFactory.CreateLogger(LogCategoryName) ? loggerFactory.CreateLogger(LogCategoryName)
: new ConsoleLogger(LogCategoryName, null, false); : new ConsoleLogger(LogCategoryName, null, false);
// By default, we use this package's built-in out-of-process-via-HTTP hosting/transport
this.UseHttpHosting();
} }
/// <summary> /// <summary>
/// Specifies which <see cref="NodeHostingModel"/> should be used. /// Specifies how to construct Node.js instances. An <see cref="INodeInstance"/> encapsulates all details about
/// </summary> /// how Node.js instances are launched and communicated with. A new <see cref="INodeInstance"/> will be created
public NodeHostingModel HostingModel { get; set; } /// automatically if the previous instance has terminated (e.g., because a source file changed).
/// <summary>
/// If set, this callback function will be invoked to supply the <see cref="INodeServices"/> instance.
/// </summary> /// </summary>
public Func<INodeInstance> NodeInstanceFactory { get; set; } public Func<INodeInstance> NodeInstanceFactory { get; set; }

View File

@@ -125,7 +125,7 @@
// Signal to the NodeServices base class that we're ready to accept invocations // Signal to the NodeServices base class that we're ready to accept invocations
console.log('[Microsoft.AspNetCore.NodeServices:Listening]'); console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
}); });
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid)); ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
function readRequestBodyAsJson(request, callback) { function readRequestBodyAsJson(request, callback) {
var requestBodyAsString = ''; var requestBodyAsString = '';
request.on('data', function (chunk) { requestBodyAsString += chunk; }); request.on('data', function (chunk) { requestBodyAsString += chunk; });
@@ -255,7 +255,7 @@
*/ */
"use strict"; "use strict";
var pollIntervalMs = 1000; var pollIntervalMs = 1000;
function exitWhenParentExits(parentPid) { function exitWhenParentExits(parentPid, ignoreSigint) {
setInterval(function () { setInterval(function () {
if (!processExists(parentPid)) { if (!processExists(parentPid)) {
// Can't log anything at this point, because out stdout was connected to the parent, // Can't log anything at this point, because out stdout was connected to the parent,
@@ -263,6 +263,24 @@
process.exit(); process.exit();
} }
}, pollIntervalMs); }, pollIntervalMs);
if (ignoreSigint) {
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
// a delayed shutdown to allow ongoing requests to complete.
//
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
//
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
// https://github.com/nodejs/node/issues/2642).
process.on('SIGINT', function () {
console.log('Received SIGINT. Waiting for .NET process to exit...');
});
}
} }
exports.exitWhenParentExits = exitWhenParentExits; exports.exitWhenParentExits = exitWhenParentExits;
function processExists(pid) { function processExists(pid) {

View File

@@ -1,12 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@@ -36,21 +34,19 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private bool _disposed; private bool _disposed;
private int _portNumber; private int _portNumber;
public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, public HttpNodeInstance(NodeServicesOptions options, int port = 0)
IDictionary<string, string> environmentVars, int invocationTimeoutMilliseconds, bool launchWithDebugging,
int debuggingPort, int port = 0)
: base( : base(
EmbeddedResourceReader.Read( EmbeddedResourceReader.Read(
typeof(HttpNodeInstance), typeof(HttpNodeInstance),
"/Content/Node/entrypoint-http.js"), "/Content/Node/entrypoint-http.js"),
projectPath, options.ProjectPath,
watchFileExtensions, options.WatchFileExtensions,
MakeCommandLineOptions(port), MakeCommandLineOptions(port),
nodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
environmentVars, options.EnvironmentVariables,
invocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,
launchWithDebugging, options.LaunchWithDebugging,
debuggingPort) options.DebuggingPort)
{ {
_client = new HttpClient(); _client = new HttpClient();
} }
@@ -95,7 +91,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
case "application/octet-stream": case "application/octet-stream":
// Streamed responses have to be received as System.IO.Stream instances // Streamed responses have to be received as System.IO.Stream instances
if (typeof(T) != typeof(Stream)) if (typeof(T) != typeof(Stream) && typeof(T) != typeof(object))
{ {
throw new ArgumentException( throw new ArgumentException(
"Node module responded with binary stream. This cannot be converted to the requested generic type: " + "Node module responded with binary stream. This cannot be converted to the requested generic type: " +
@@ -140,4 +136,4 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
} }
} }
} }
} }

View File

@@ -0,0 +1,17 @@
namespace Microsoft.AspNetCore.NodeServices.HostingModels
{
/// <summary>
/// Extension methods that help with populating a <see cref="NodeServicesOptions"/> object.
/// </summary>
public static class NodeServicesOptionsExtensions
{
/// <summary>
/// Configures the <see cref="INodeServices"/> service so that it will use out-of-process
/// Node.js instances and perform RPC calls over HTTP.
/// </summary>
public static void UseHttpHosting(this NodeServicesOptions options)
{
options.NodeInstanceFactory = () => new HttpNodeInstance(options);
}
}
}

View File

@@ -222,7 +222,7 @@ If you haven't yet installed node-inspector, you can do so as follows:
} }
else else
{ {
debuggingArgs = string.Empty; debuggingArgs = string.Empty;
} }
var thisProcessPid = Process.GetCurrentProcess().Id; var thisProcessPid = Process.GetCurrentProcess().Id;
@@ -325,17 +325,26 @@ If you haven't yet installed node-inspector, you can do so as follows:
private static Process LaunchNodeProcess(ProcessStartInfo startInfo) private static Process LaunchNodeProcess(ProcessStartInfo startInfo)
{ {
var process = Process.Start(startInfo); try {
var process = Process.Start(startInfo);
// On Mac at least, a killed child process is left open as a zombie until the parent // On Mac at least, a killed child process is left open as a zombie until the parent
// captures its exit code. We don't need the exit code for this process, and don't want // captures its exit code. We don't need the exit code for this process, and don't want
// to use process.WaitForExit() explicitly (we'd have to block the thread until it really // to use process.WaitForExit() explicitly (we'd have to block the thread until it really
// has exited), but we don't want to leave zombies lying around either. It's sufficient // has exited), but we don't want to leave zombies lying around either. It's sufficient
// to use process.EnableRaisingEvents so that .NET will grab the exit code and let the // to use process.EnableRaisingEvents so that .NET will grab the exit code and let the
// zombie be cleaned away without having to block our thread. // zombie be cleaned away without having to block our thread.
process.EnableRaisingEvents = true; process.EnableRaisingEvents = true;
return process; return process;
} catch (Exception ex) {
var message = "Failed to start Node process. To resolve this:.\n\n"
+ "[1] Ensure that Node.js is installed and can be found in one of the PATH directories.\n"
+ $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n"
+ " Make sure the Node executable is in one of those directories, or update your PATH.\n\n"
+ "[2] See the InnerException for further details of the cause.";
throw new InvalidOperationException(message, ex);
}
} }
private static string UnencodeNewlines(string str) private static string UnencodeNewlines(string str)

View File

@@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices
return InvokeExportWithPossibleRetryAsync<T>(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken); return InvokeExportWithPossibleRetryAsync<T>(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken);
} }
public async Task<T> InvokeExportWithPossibleRetryAsync<T>(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken) private async Task<T> InvokeExportWithPossibleRetryAsync<T>(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken)
{ {
ThrowAnyOutstandingDelayedDisposalException(); ThrowAnyOutstandingDelayedDisposalException();
var nodeInstance = GetOrCreateCurrentNodeInstance(); var nodeInstance = GetOrCreateCurrentNodeInstance();

View File

@@ -67,7 +67,7 @@ public async Task<IActionResult> MyAction([FromServices] INodeServices nodeServi
} }
``` ```
Of course, you also need to supply the Node.js code you want to invoke. Create a file called `addNumber.js` at the root of your ASP.NET Core application, and add the following code: Of course, you also need to supply the Node.js code you want to invoke. Create a file called `addNumbers.js` at the root of your ASP.NET Core application, and add the following code:
```javascript ```javascript
module.exports = function (callback, first, second) { module.exports = function (callback, first, second) {
@@ -333,25 +333,23 @@ People have asked about using [VroomJS](https://github.com/fogzot/vroomjs) as a
### Built-in hosting models ### Built-in hosting models
Normally, you can just use the default hosting model, and not worry about it. But if you have some special requirements, select a hosting model by setting the `HostingModel` property on the `options` object in `AddNodeServices`. Example: Normally, you can just use the default hosting model, and not worry about it. But if you have some special requirements, you can write your own hosting model, or reference a package that supplies one.
For example, you could use the 'socket' hosting model. It performs RPC between .NET and Node.js using a fast, low-level binary channel rather than the default HTTP transport. To do this, first install the NuGet package `Microsoft.AspNetCore.NodeServices.Sockets`. Then, at the top of your `Startup.cs` file, add:
```csharp ```csharp
services.AddNodeServices(options => using Microsoft.AspNetCore.NodeServices.Sockets;
{ ```
options.HostingModel = NodeHostingModel.Socket;
...then in your `Startup.cs` file's `ConfigureServices` method, you can configure:
```csharp
services.AddNodeServices(options => {
options.UseSocketHosting();
}); });
``` ```
**Available hosting models** Now when you run your application, it will use the socket-based hosting and transport mechanism. In the past, the socket transport was faster than HTTP, but since .NET Core 1.1 improved the performance of `HttpClient` there isn't really any speed difference any more, so there's no longer any significant advantage to using `Microsoft.AspNetCore.NodeServices.Sockets`.
* `Socket`
* Launches Node as a separate process, and communicates with it using named pipes (on Windows) or domain sockets (on Linux / OS X).
* This is faster than `Http` because it uses a low-level binary protocol with very low overhead. It retains one continuous connection for the whole lifetime of the Node instance, so it doesn't have to keep waiting for new connections to open.
* `Http` (default)
* Launches Node as a separate process, and communicates with it by making HTTP requests.
* This primarily exists because it was implemented before `Socket`, but there's no particular reason to use it now that `Socket` is available. It could theoretically be useful if you wanted to run Node instances on separate servers (though there isn't currently any built-in API for configuring that).
The default transport may change from `Http` to `Socket` in the near future, because it's faster.
### Custom hosting models ### Custom hosting models

View File

@@ -76,7 +76,7 @@ server.listen(requestedPortOrZero, 'localhost', function () {
console.log('[Microsoft.AspNetCore.NodeServices:Listening]'); console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
}); });
exitWhenParentExits(parseInt(parsedArgs.parentPid)); exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
function readRequestBodyAsJson(request, callback) { function readRequestBodyAsJson(request, callback) {
let requestBodyAsString = ''; let requestBodyAsString = '';

View File

@@ -34,7 +34,7 @@ to check whether the parent PID is still running. So that's what we do here.
const pollIntervalMs = 1000; const pollIntervalMs = 1000;
export function exitWhenParentExits(parentPid: number) { export function exitWhenParentExits(parentPid: number, ignoreSigint: boolean) {
setInterval(() => { setInterval(() => {
if (!processExists(parentPid)) { if (!processExists(parentPid)) {
// Can't log anything at this point, because out stdout was connected to the parent, // Can't log anything at this point, because out stdout was connected to the parent,
@@ -42,6 +42,25 @@ export function exitWhenParentExits(parentPid: number) {
process.exit(); process.exit();
} }
}, pollIntervalMs); }, pollIntervalMs);
if (ignoreSigint) {
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
// a delayed shutdown to allow ongoing requests to complete.
//
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
//
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
// https://github.com/nodejs/node/issues/2642).
process.on('SIGINT', () => {
console.log('Received SIGINT. Waiting for .NET process to exit...');
});
}
} }
function processExists(pid: number) { function processExists(pid: number) {
@@ -56,7 +75,7 @@ function processExists(pid: number) {
if (ex.code === 'EPERM') { if (ex.code === 'EPERM') {
throw new Error(`Attempted to check whether process ${pid} was running, but got a permissions error.`); throw new Error(`Attempted to check whether process ${pid} was running, but got a permissions error.`);
} }
return false; return false;
} }
} }

View File

@@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.NodeServices
public sealed class StringAsTempFile : IDisposable public sealed class StringAsTempFile : IDisposable
{ {
private bool _disposedValue; private bool _disposedValue;
private bool _hasDeletedTempFile;
private object _fileDeletionLock = new object();
/// <summary> /// <summary>
/// Create a new instance of <see cref="StringAsTempFile"/>. /// Create a new instance of <see cref="StringAsTempFile"/>.
@@ -18,6 +20,18 @@ namespace Microsoft.AspNetCore.NodeServices
{ {
FileName = Path.GetTempFileName(); FileName = Path.GetTempFileName();
File.WriteAllText(FileName, content); File.WriteAllText(FileName, content);
// Because .NET finalizers don't reliably run when the process is terminating, also
// add event handlers for other shutdown scenarios.
#if NET451
AppDomain.CurrentDomain.ProcessExit += HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload += HandleProcessExit;
#else
// Note that this still doesn't capture SIGKILL (at least on macOS) - there doesn't
// appear to be a way of doing that. So in that case, the temporary file will be
// left behind.
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += HandleAssemblyUnloading;
#endif
} }
/// <summary> /// <summary>
@@ -40,15 +54,45 @@ namespace Microsoft.AspNetCore.NodeServices
{ {
if (disposing) if (disposing)
{ {
// Would dispose managed state here, if there was any // Dispose managed state
#if NET451
AppDomain.CurrentDomain.ProcessExit -= HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload -= HandleProcessExit;
#else
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading -= HandleAssemblyUnloading;
#endif
} }
File.Delete(FileName); EnsureTempFileDeleted();
_disposedValue = true; _disposedValue = true;
} }
} }
private void EnsureTempFileDeleted()
{
lock (_fileDeletionLock)
{
if (!_hasDeletedTempFile)
{
File.Delete(FileName);
_hasDeletedTempFile = true;
}
}
}
#if NET451
private void HandleProcessExit(object sender, EventArgs args)
{
EnsureTempFileDeleted();
}
#else
private void HandleAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)
{
EnsureTempFileDeleted();
}
#endif
/// <summary> /// <summary>
/// Implements the finalization part of the IDisposable pattern by calling Dispose(false). /// Implements the finalization part of the IDisposable pattern by calling Dispose(false).
/// </summary> /// </summary>

View File

@@ -1,6 +1,6 @@
{ {
"description": "Invoke Node.js modules at runtime in ASP.NET Core applications.", "description": "Invoke Node.js modules at runtime in ASP.NET Core applications.",
"version": "1.0.0-*", "version": "1.1.0-rc1-*",
"packOptions": { "packOptions": {
"repository": { "repository": {
"type": "git", "type": "git",
@@ -21,26 +21,18 @@
"xmlDoc": true "xmlDoc": true
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Hosting.Abstractions": "1.0.0", "Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"NETStandard.Library": "1.6.0" "NETStandard.Library": "1.6.1-*"
}, },
"frameworks": { "frameworks": {
"net451": { "net451": {},
"frameworkAssemblies": {
"System.Net.Http": "4.0.0"
},
"dependencies": {
"Microsoft.Tpl.Dataflow": "4.5.24"
}
},
"netstandard1.6": { "netstandard1.6": {
"dependencies": { "dependencies": {
"System.Diagnostics.Process": "4.1.0", "System.Diagnostics.Process": "4.3.0",
"System.IO.Pipes": "4.0.0", "System.IO.FileSystem.Watcher": "4.3.0",
"System.Threading.Tasks.Dataflow": "4.6.0" "System.Runtime.Loader": "4.3.0"
} }
} }
}, },

View File

@@ -10,8 +10,7 @@ module.exports = {
] ]
}, },
entry: { entry: {
'entrypoint-http': ['./TypeScript/HttpNodeInstanceEntryPoint'], 'entrypoint-http': ['./TypeScript/HttpNodeInstanceEntryPoint']
'entrypoint-socket': ['./TypeScript/SocketNodeInstanceEntryPoint'],
}, },
output: { output: {
libraryTarget: 'commonjs', libraryTarget: 'commonjs',

View File

@@ -1,3 +0,0 @@
/node_modules/
/*.js
/*.d.ts

View File

@@ -1,3 +0,0 @@
!/*.js
!/*.d.ts
/typings/

View File

@@ -1,12 +0,0 @@
Copyright (c) .NET Foundation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
these files except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

View File

@@ -1,27 +0,0 @@
{
"name": "redux-typed",
"version": "2.0.0",
"description": "Helpers for building React+Redux apps with strong TypeScript type checking everywhere",
"main": "main.js",
"typings": "main.d.ts",
"scripts": {
"prepublish": "rimraf *.d.ts && tsc && echo 'Finished building NPM package \"redux-typed\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Microsoft",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/aspnet/JavaScriptServices/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/aspnet/JavaScriptServices.git"
},
"devDependencies": {
"@types/react": "^0.14.38",
"@types/react-redux": "^4.4.32",
"@types/redux": "^3.5.30",
"rimraf": "^2.5.4",
"typescript": "^2.0.0"
}
}

View File

@@ -1,24 +0,0 @@
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
if (typeof (<any>Object).assign != 'function') {
(function () {
(<any>Object).assign = function (target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
};
})();
}

View File

@@ -1,42 +0,0 @@
// Credits for the type detection technique: http://www.bluewire-technologies.com/2015/redux-actions-for-typescript/
import * as React from 'react';
import { Dispatch } from 'redux';
import './ObjectAssignPolyfill';
export interface ActionClass<T extends Action> {
prototype: T;
}
export function typeName(name: string) {
return function<T extends Action>(actionClass: ActionClass<T>) {
// Although we could determine the type name using actionClass.prototype.constructor.name,
// it's dangerous to do that because minifiers may interfere with it, and then serialized state
// might not have the expected meaning after a recompile. So we explicitly ask for a name string.
actionClass.prototype.type = name;
}
}
export function isActionType<T extends Action>(action: Action, actionClass: ActionClass<T>): action is T {
return action.type == actionClass.prototype.type;
}
// Middleware for transforming Typed Actions into plain actions
export const typedToPlain = (store: any) => (next: any) => (action: any) => {
next((<any>Object).assign({}, action));
};
export abstract class Action {
type: string;
constructor() {
// Make it an own-property (not a prototype property) so that it's included when JSON-serializing
this.type = this.type;
}
}
export interface Reducer<TState> extends Function {
(state: TState, action: Action): TState;
}
export interface ActionCreatorGeneric<TState> extends Function {
(dispatch: Dispatch<TState>, getState: () => TState): any;
}

View File

@@ -1,22 +0,0 @@
import * as React from 'react';
import { connect as nativeConnect } from 'react-redux';
export type ReactComponentClass<T, S> = new(props: T) => React.Component<T, S>;
export class ComponentBuilder<TOwnProps, TActions, TExternalProps> {
constructor(private stateToProps: (appState: any) => TOwnProps, private actionCreators: TActions) {
}
public withExternalProps<TAddExternalProps>() {
return this as any as ComponentBuilder<TOwnProps, TActions, TAddExternalProps>;
}
public get allProps(): TOwnProps & TActions & TExternalProps { return null; }
public connect<TState>(componentClass: ReactComponentClass<TOwnProps & TActions & TExternalProps, TState>): ReactComponentClass<TExternalProps, TState> {
return nativeConnect(this.stateToProps, this.actionCreators as any)(componentClass) as any;
}
}
export function provide<TOwnProps, TActions>(stateToProps: (appState: any) => TOwnProps, actionCreators: TActions) {
return new ComponentBuilder<TOwnProps, TActions, {}>(stateToProps, actionCreators);
}

View File

@@ -1,2 +0,0 @@
export * from './StrongActions';
export * from './StrongProvide';

View File

@@ -1,16 +0,0 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": ".",
"lib": ["es2015", "dom"]
},
"files": [
"src/main.ts"
],
"exclude": [
"node_modules"
]
}

View File

@@ -1,6 +1,6 @@
{ {
"description": "Helpers for building React applications on ASP.NET Core.", "description": "Helpers for building React applications on ASP.NET Core.",
"version": "1.0.0-*", "version": "1.1.0-beta2-*",
"packOptions": { "packOptions": {
"repository": { "repository": {
"type": "git", "type": "git",
@@ -18,8 +18,8 @@
"xmlDoc": true "xmlDoc": true
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.Mvc.TagHelpers": "1.1.0",
"Microsoft.AspNetCore.SpaServices": "1.0.0-*" "Microsoft.AspNetCore.SpaServices": "1.1.0-*"
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": {},

View File

@@ -119,7 +119,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
unencodedAbsoluteUrl, unencodedAbsoluteUrl,
unencodedPathAndQuery, unencodedPathAndQuery,
CustomDataParameter, CustomDataParameter,
TimeoutMillisecondsParameter); TimeoutMillisecondsParameter,
request.PathBase.ToString());
if (!string.IsNullOrEmpty(result.RedirectUrl)) if (!string.IsNullOrEmpty(result.RedirectUrl))
{ {

View File

@@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// <param name="requestPathAndQuery">The path and query part of the URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param> /// <param name="requestPathAndQuery">The path and query part of the URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
/// <param name="customDataParameter">An optional JSON-serializable parameter to be supplied to the prerendering code.</param> /// <param name="customDataParameter">An optional JSON-serializable parameter to be supplied to the prerendering code.</param>
/// <param name="timeoutMilliseconds">The maximum duration to wait for prerendering to complete.</param> /// <param name="timeoutMilliseconds">The maximum duration to wait for prerendering to complete.</param>
/// <param name="requestPathBase">The PathBase for the currently-executing HTTP request.</param>
/// <returns></returns> /// <returns></returns>
public static Task<RenderToStringResult> RenderToString( public static Task<RenderToStringResult> RenderToString(
string applicationBasePath, string applicationBasePath,
@@ -38,7 +39,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
string requestAbsoluteUrl, string requestAbsoluteUrl,
string requestPathAndQuery, string requestPathAndQuery,
object customDataParameter, object customDataParameter,
int timeoutMilliseconds) int timeoutMilliseconds,
string requestPathBase)
{ {
return nodeServices.InvokeExportAsync<RenderToStringResult>( return nodeServices.InvokeExportAsync<RenderToStringResult>(
NodeScript.Value.FileName, NodeScript.Value.FileName,
@@ -48,7 +50,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
requestAbsoluteUrl, requestAbsoluteUrl,
requestPathAndQuery, requestPathAndQuery,
customDataParameter, customDataParameter,
timeoutMilliseconds); timeoutMilliseconds,
requestPathBase);
} }
} }
} }

View File

@@ -48,11 +48,14 @@ Instead, what `SpaServices` offers is ASP.NET Core APIs that know how to invoke
### 1. Enable the asp-prerender-* tag helpers ### 1. Enable the asp-prerender-* tag helpers
Make sure you've installed the `Microsoft.AspNetCore.SpaServices` NuGet package and the `aspnet-prerendering` NPM package. Together these contain the server-side and client-side library code you'll need. Make sure you've installed into your project:
Now go to your `Views/_ViewImports.cshtml` file, and add the following line: * The `Microsoft.AspNetCore.SpaServices` NuGet package, version 1.1.0-* or later
* The `aspnet-prerendering` NPM package, version 2.0.1 or later
@addTagHelper *, Microsoft.AspNetCore.SpaServices Together these contain the server-side and client-side library code you'll need. Now go to your `Views/_ViewImports.cshtml` file, and add the following line:
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
### 2. Use asp-prerender-* in a view ### 2. Use asp-prerender-* in a view
@@ -67,7 +70,9 @@ If you run your application now, and browse to whatever page renders the view yo
Create a JavaScript file at the path matching the `asp-prerender-module` value you specified above. In this example, that means creating a folder called `ClientApp` at the root of your project, and creating a file inside it called `boot-server.js`. Try putting the following into it: Create a JavaScript file at the path matching the `asp-prerender-module` value you specified above. In this example, that means creating a folder called `ClientApp` at the root of your project, and creating a file inside it called `boot-server.js`. Try putting the following into it:
```javascript ```javascript
module.exports = function(params) { var prerendering = require('aspnet-prerendering');
module.exports = prerendering.createServerRenderer(function(params) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var result = '<h1>Hello world!</h1>' var result = '<h1>Hello world!</h1>'
+ '<p>Current time in Node is: ' + new Date() + '</p>' + '<p>Current time in Node is: ' + new Date() + '</p>'
@@ -76,7 +81,7 @@ module.exports = function(params) {
resolve({ html: result }); resolve({ html: result });
}); });
}; });
``` ```
If you try running your app now, you should see the HTML snippet generated by your JavaScript getting injected into your page. If you try running your app now, you should see the HTML snippet generated by your JavaScript getting injected into your page.
@@ -98,7 +103,9 @@ For example, in your `cshtml`,
Now in your JavaScript prerendering function, you can access this data by reading `params.data`, e.g.: Now in your JavaScript prerendering function, you can access this data by reading `params.data`, e.g.:
```javascript ```javascript
module.exports = function(params) { var prerendering = require('aspnet-prerendering');
module.exports = prerendering.createServerRenderer(function(params) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var result = '<h1>Hello world!</h1>' var result = '<h1>Hello world!</h1>'
+ '<p>Is gold user: ' + params.data.isGoldUser + '</p>' + '<p>Is gold user: ' + params.data.isGoldUser + '</p>'
@@ -106,7 +113,7 @@ module.exports = function(params) {
resolve({ html: result }); resolve({ html: result });
}); });
}; });
``` ```
Notice that the property names are received in JavaScript-style casing (e.g., `isGoldUser`) even though they were sent in C#-style casing (e.g., `IsGoldUser`). This is because of how the JSON serialization is configured by default. Notice that the property names are received in JavaScript-style casing (e.g., `isGoldUser`) even though they were sent in C#-style casing (e.g., `IsGoldUser`). This is because of how the JSON serialization is configured by default.
@@ -182,7 +189,9 @@ If you don't already have a `tsconfig.json` file at the root of your project, ad
Now you can delete `ClientApp/boot-server.js`, and in its place, create `ClientApp/boot-server.ts`, containing the TypeScript equivalent of what you had before: Now you can delete `ClientApp/boot-server.js`, and in its place, create `ClientApp/boot-server.ts`, containing the TypeScript equivalent of what you had before:
```javascript ```javascript
export default function (params: any): Promise<{ html: string}> { import { createServerRenderer } from 'aspnet-prerendering';
export default createServerRenderer(params => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const html = ` const html = `
<h1>Hello world!</h1> <h1>Hello world!</h1>
@@ -192,7 +201,7 @@ export default function (params: any): Promise<{ html: string}> {
resolve({ html }); resolve({ html });
}); });
} });
``` ```
Finally, run `webpack` on the command line to build `ClientApp/dist/main-server.js`. Then you can tell `SpaServices` to use that file for server-side prerendering. In your MVC view where you use `aspnet-prerender-module`, update the attribute value: Finally, run `webpack` on the command line to build `ClientApp/dist/main-server.js`. Then you can tell `SpaServices` to use that file for server-side prerendering. In your MVC view where you use `aspnet-prerender-module`, update the attribute value:
@@ -529,7 +538,7 @@ module.exports = {
output: { output: {
path: path.join(__dirname, 'wwwroot', 'dist'), path: path.join(__dirname, 'wwwroot', 'dist'),
publicPath: '/dist', publicPath: '/dist/',
filename: '[name].js' filename: '[name].js'
}, },
}; };

View File

@@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
_pathPrefix = pathPrefix; _pathPrefix = pathPrefix;
_options = options; _options = options;
_httpClient = new HttpClient(new HttpClientHandler()); _httpClient = new HttpClient(new HttpClientHandler());
_httpClient.Timeout = _options.RequestTimeout;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)

View File

@@ -1,16 +1,20 @@
using System;
namespace Microsoft.AspNetCore.SpaServices.Webpack namespace Microsoft.AspNetCore.SpaServices.Webpack
{ {
internal class ConditionalProxyMiddlewareOptions internal class ConditionalProxyMiddlewareOptions
{ {
public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout)
{ {
Scheme = scheme; Scheme = scheme;
Host = host; Host = host;
Port = port; Port = port;
RequestTimeout = requestTimeout;
} }
public string Scheme { get; } public string Scheme { get; }
public string Host { get; } public string Host { get; }
public string Port { get; } public string Port { get; }
public TimeSpan RequestTimeout { get; }
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.AspNetCore.SpaServices.Webpack;
@@ -7,6 +8,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.PlatformAbstractions; using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
@@ -15,8 +17,6 @@ namespace Microsoft.AspNetCore.Builder
/// </summary> /// </summary>
public static class WebpackDevMiddleware public static class WebpackDevMiddleware
{ {
private const string WebpackDevMiddlewareScheme = "http";
private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
private const string DefaultConfigFile = "webpack.config.js"; private const string DefaultConfigFile = "webpack.config.js";
/// <summary> /// <summary>
@@ -75,12 +75,18 @@ namespace Microsoft.AspNetCore.Builder
"/Content/Node/webpack-dev-middleware.js"); "/Content/Node/webpack-dev-middleware.js");
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
// Ideally, this would be relative to the application's PathBase (so it could work in virtual directories)
// but it's not clear that such information exists during application startup, as opposed to within the context
// of a request.
var hmrEndpoint = "/__webpack_hmr";
// Tell Node to start the server hosting webpack-dev-middleware // Tell Node to start the server hosting webpack-dev-middleware
var devServerOptions = new var devServerOptions = new
{ {
webpackConfigPath = Path.Combine(nodeServicesOptions.ProjectPath, options.ConfigFile ?? DefaultConfigFile), webpackConfigPath = Path.Combine(nodeServicesOptions.ProjectPath, options.ConfigFile ?? DefaultConfigFile),
suppliedOptions = options, suppliedOptions = options,
understandsMultiplePublicPaths = true understandsMultiplePublicPaths = true,
hotModuleReplacementEndpointUrl = hmrEndpoint
}; };
var devServerInfo = var devServerInfo =
nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer", nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer",
@@ -94,33 +100,30 @@ namespace Microsoft.AspNetCore.Builder
} }
// Proxy the corresponding requests through ASP.NET and into the Node listener // Proxy the corresponding requests through ASP.NET and into the Node listener
// Anything under /<publicpath> (e.g., /dist) is proxied as a normal HTTP request with a typical timeout (100s is the default from HttpClient),
// plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request).
foreach (var publicPath in devServerInfo.PublicPaths)
{
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath, devServerInfo.Port, TimeSpan.FromSeconds(100));
}
appBuilder.UseProxyToLocalWebpackDevMiddleware(hmrEndpoint, devServerInfo.Port, Timeout.InfiniteTimeSpan);
}
private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout)
{
// Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the // Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the
// server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is // server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is
// the one making the internal HTTP requests, and it's going to be to some port on this machine // the one making the internal HTTP requests, and it's going to be to some port on this machine
// because aspnet-webpack hosts the dev server there. We can't use the hostname that the client // because aspnet-webpack hosts the dev server there. We can't use the hostname that the client
// sees, because that could be anything (e.g., some upstream load balancer) and we might not be // sees, because that could be anything (e.g., some upstream load balancer) and we might not be
// able to make outbound requests to it from here. // able to make outbound requests to it from here.
var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS,
"localhost", devServerInfo.Port.ToString()); // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic
foreach (var publicPath in devServerInfo.PublicPaths) // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have
{ // the necessary certificate).
appBuilder.UseMiddleware<ConditionalProxyMiddleware>(publicPath, proxyOptions); var proxyOptions = new ConditionalProxyMiddlewareOptions(
} "http", "localhost", proxyToPort.ToString(), requestTimeout);
appBuilder.UseMiddleware<ConditionalProxyMiddleware>(publicPath, proxyOptions);
// While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream,
// and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after
// a while. So, just serve a 302 for those. But note that we must use the hostname that the client
// sees, not "localhost", so that it works even when you're not running on localhost (e.g., Docker).
appBuilder.Map(WebpackHotMiddlewareEndpoint, builder =>
{
builder.Use(next => ctx =>
{
var hostname = ctx.Request.Host.Host;
ctx.Response.Redirect(
$"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}");
return Task.FromResult(0);
});
});
} }
#pragma warning disable CS0649 #pragma warning disable CS0649

View File

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-prerendering", "name": "aspnet-prerendering",
"version": "2.0.0", "version": "2.0.2",
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "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", "main": "index.js",
"scripts": { "scripts": {
@@ -17,7 +17,7 @@
"url": "https://github.com/aspnet/JavaScriptServices.git" "url": "https://github.com/aspnet/JavaScriptServices.git"
}, },
"dependencies": { "dependencies": {
"domain-task": "^2.0.1" "domain-task": "^2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^6.0.42", "@types/node": "^6.0.42",

View File

@@ -2,13 +2,12 @@
import * as url from 'url'; import * as url from 'url';
import * as path from 'path'; import * as path from 'path';
import * as domain from 'domain'; import * as domain from 'domain';
import { run as domainTaskRun } from 'domain-task/main'; import { run as domainTaskRun, baseUrl as domainTaskBaseUrl } from 'domain-task/main';
import { baseUrl } from 'domain-task/fetch';
const defaultTimeoutMilliseconds = 30 * 1000; const defaultTimeoutMilliseconds = 30 * 1000;
export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc { export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
const resultFunc = (callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) => { const resultFunc = (callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string) => {
// Prepare a promise that will represent the completion of all domain tasks in this execution context. // Prepare a promise that will represent the completion of all domain tasks in this execution context.
// The boot code will wait for this before performing its final render. // The boot code will wait for this before performing its final render.
let domainTaskCompletionPromiseResolve; let domainTaskCompletionPromiseResolve;
@@ -20,6 +19,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
location: url.parse(requestPathAndQuery), location: url.parse(requestPathAndQuery),
origin: parsedAbsoluteRequestUrl.protocol + '//' + parsedAbsoluteRequestUrl.host, origin: parsedAbsoluteRequestUrl.protocol + '//' + parsedAbsoluteRequestUrl.host,
url: requestPathAndQuery, url: requestPathAndQuery,
baseUrl: (requestPathBase || '') + '/',
absoluteUrl: absoluteRequestUrl, absoluteUrl: absoluteRequestUrl,
domainTasks: domainTaskCompletionPromise, domainTasks: domainTaskCompletionPromise,
data: customDataParameter data: customDataParameter
@@ -33,7 +33,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']); bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']);
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
baseUrl(absoluteRequestUrl); domainTaskBaseUrl(absoluteRequestUrl);
// Begin rendering, and apply a timeout // Begin rendering, and apply a timeout
const bootFuncPromise = bootFunc(params); const bootFuncPromise = bootFunc(params);

View File

@@ -1,5 +1,5 @@
interface RenderToStringFunc { interface RenderToStringFunc {
(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number): void; (callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string): void;
} }
interface RenderToStringCallback { interface RenderToStringCallback {
@@ -23,6 +23,7 @@ interface BootFuncParams {
location: any; // e.g., Location object containing information '/some/path' location: any; // e.g., Location object containing information '/some/path'
origin: string; // e.g., 'https://example.com:1234' origin: string; // e.g., 'https://example.com:1234'
url: string; // e.g., '/some/path' url: string; // e.g., '/some/path'
baseUrl: string; // e.g., '' or '/myVirtualDir'
absoluteUrl: string; // e.g., 'https://example.com:1234/some/path' absoluteUrl: string; // e.g., 'https://example.com:1234/some/path'
domainTasks: Promise<any>; domainTasks: Promise<any>;
data: any; // any custom object passed through from .NET data: any; // any custom object passed through from .NET

View File

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

View File

@@ -19,6 +19,7 @@ export interface CreateDevServerCallback {
interface CreateDevServerOptions { interface CreateDevServerOptions {
webpackConfigPath: string; webpackConfigPath: string;
suppliedOptions: DevServerOptions; suppliedOptions: DevServerOptions;
hotModuleReplacementEndpointUrl: string;
} }
// These are the options configured in C# and then JSON-serialized, hence the C#-style naming // These are the options configured in C# and then JSON-serialized, hence the C#-style naming
@@ -28,7 +29,7 @@ interface DevServerOptions {
ReactHotModuleReplacement: boolean; ReactHotModuleReplacement: boolean;
} }
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrEndpoint: string) { function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrClientEndpoint: string, hmrServerEndpoint: string) {
// Build the final Webpack config based on supplied options // Build the final Webpack config based on supplied options
if (enableHotModuleReplacement) { if (enableHotModuleReplacement) {
// For this, we only support the key/value config format, not string or string[], since // For this, we only support the key/value config format, not string or string[], since
@@ -44,7 +45,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
// Augment all entry points so they support HMR (unless they already do) // Augment all entry points so they support HMR (unless they already do)
Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => { Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => {
const webpackHotMiddlewareEntryPoint = 'webpack-hot-middleware/client'; const webpackHotMiddlewareEntryPoint = 'webpack-hot-middleware/client';
const webpackHotMiddlewareOptions = `?path=` + encodeURIComponent(hmrEndpoint); const webpackHotMiddlewareOptions = `?path=` + encodeURIComponent(hmrClientEndpoint);
if (typeof entryPoints[entryPointName] === 'string') { if (typeof entryPoints[entryPointName] === 'string') {
entryPoints[entryPointName] = [webpackHotMiddlewareEntryPoint + webpackHotMiddlewareOptions, entryPoints[entryPointName]]; entryPoints[entryPointName] = [webpackHotMiddlewareEntryPoint + webpackHotMiddlewareOptions, entryPoints[entryPointName]];
} else if (firstIndexOfStringStartingWith(entryPoints[entryPointName], webpackHotMiddlewareEntryPoint) < 0) { } else if (firstIndexOfStringStartingWith(entryPoints[entryPointName], webpackHotMiddlewareEntryPoint) < 0) {
@@ -107,7 +108,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
// file on disk wouldn't match the file served to the browser, and the source map line numbers wouldn't // file on disk wouldn't match the file served to the browser, and the source map line numbers wouldn't
// match up. Breakpoints would either not be hit, or would hit the wrong lines. // match up. Breakpoints would either not be hit, or would hit the wrong lines.
(compiler as any).plugin('done', stats => { (compiler as any).plugin('done', stats => {
copyRecursiveToRealFsSync(compiler.outputFileSystem, '/', [/\.hot-update\.(js|json)$/]); copyRecursiveToRealFsSync(compiler.outputFileSystem, '/', [/\.hot-update\.(js|json|js\.map)$/]);
}); });
if (enableHotModuleReplacement) { if (enableHotModuleReplacement) {
@@ -117,7 +118,9 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
} catch (ex) { } catch (ex) {
throw new Error('HotModuleReplacement failed because of an error while loading \'webpack-hot-middleware\'. Error was: ' + ex.stack); throw new Error('HotModuleReplacement failed because of an error while loading \'webpack-hot-middleware\'. Error was: ' + ex.stack);
} }
app.use(webpackHotMiddlewareModule(compiler)); app.use(webpackHotMiddlewareModule(compiler, {
path: hmrServerEndpoint
}));
} }
} }
@@ -198,8 +201,16 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
} }
normalizedPublicPaths.push(removeTrailingSlash(publicPath)); normalizedPublicPaths.push(removeTrailingSlash(publicPath));
const hmrEndpoint = `http://localhost:${listener.address().port}/__webpack_hmr`; // Newer versions of Microsoft.AspNetCore.SpaServices will explicitly pass an HMR endpoint URL
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrEndpoint); // (because it's relative to the app's URL space root, which the client doesn't otherwise know).
// For back-compatibility, fall back on connecting directly to the underlying HMR server (though
// that won't work if the app is hosted on HTTPS because of the mixed-content rule, and we can't
// run the HMR server itself on HTTPS because in general it has no valid cert).
const hmrClientEndpoint = options.hotModuleReplacementEndpointUrl // The URL that we'll proxy (e.g., /__asp_webpack_hmr)
|| `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
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientEndpoint, hmrServerEndpoint);
} }
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "domain-task", "name": "domain-task",
"version": "2.0.1", "version": "2.0.2",
"description": "Tracks outstanding operations for a logical thread of execution", "description": "Tracks outstanding operations for a logical thread of execution",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -1,13 +1,10 @@
import * as url from 'url'; import * as url from 'url';
import * as domain from 'domain'; import * as domain from 'domain';
import * as domainContext from 'domain-context'; import * as domainContext from 'domain-context';
import { baseUrl } from './main';
const isomorphicFetch = require('isomorphic-fetch'); const isomorphicFetch = require('isomorphic-fetch');
const isNode = typeof process === 'object' && process.versions && !!process.versions.node; const isNode = typeof process === 'object' && process.versions && !!process.versions.node;
// Not using a symbol, because this may need to run in a version of Node.js that doesn't support them
const domainTaskStateKey = '__DOMAIN_TASK_INTERNAL_FETCH_BASEURL__DO_NOT_REFERENCE_THIS__';
let noDomainBaseUrl: string;
function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit): Promise<any> { function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit): Promise<any> {
// Resolve relative URLs // Resolve relative URLs
if (baseUrl) { if (baseUrl) {
@@ -70,16 +67,6 @@ export function fetch(url: string | Request, init?: RequestInit): Promise<any> {
return issueRequest(baseUrl(), url, init); return issueRequest(baseUrl(), url, init);
} }
export function baseUrl(url?: string): string { // Re-exporting baseUrl from this module for back-compatibility only
if (url) { // Newer code that wants to access baseUrl should use the version exported from the root of this package
if (domain.active) { export { baseUrl } from './main';
// There's an active domain (e.g., in Node.js), so associate the base URL with it
domainContext.set(domainTaskStateKey, url);
} else {
// There's no active domain (e.g., in browser), so there's just one shared base URL
noDomainBaseUrl = url;
}
}
return domain.active ? domainContext.get(domainTaskStateKey) : noDomainBaseUrl;
}

View File

@@ -1,3 +1,3 @@
// This file determines the top-level package exports // This file determines the top-level package exports
export { addTask, run } from './main'; export { addTask, run, baseUrl } from './main';
export { fetch } from './fetch'; export { fetch } from './fetch';

View File

@@ -1,6 +1,11 @@
import * as domain from 'domain'; import * as domain from 'domain';
import * as domainContext from 'domain-context'; import * as domainContext from 'domain-context';
// Not using symbols, because this may need to run in a version of Node.js that doesn't support them
const domainTasksStateKey = '__DOMAIN_TASKS'; const domainTasksStateKey = '__DOMAIN_TASKS';
const domainTaskBaseUrlStateKey = '__DOMAIN_TASK_INTERNAL_FETCH_BASEURL__DO_NOT_REFERENCE_THIS__';
let noDomainBaseUrl: string;
export function addTask(task: PromiseLike<any>) { export function addTask(task: PromiseLike<any>) {
if (task && domain.active) { if (task && domain.active) {
@@ -57,6 +62,20 @@ export function run<T>(codeToRun: () => T, completionCallback: (error: any) => v
return synchronousResult; return synchronousResult;
} }
export function baseUrl(url?: string): string {
if (url) {
if (domain.active) {
// There's an active domain (e.g., in Node.js), so associate the base URL with it
domainContext.set(domainTaskBaseUrlStateKey, url);
} else {
// There's no active domain (e.g., in browser), so there's just one shared base URL
noDomainBaseUrl = url;
}
}
return domain.active ? domainContext.get(domainTaskBaseUrlStateKey) : noDomainBaseUrl;
}
interface DomainTasksState { interface DomainTasksState {
numRemainingTasks: number; numRemainingTasks: number;
hasIssuedSuccessCallback: boolean; hasIssuedSuccessCallback: boolean;

View File

@@ -1,6 +1,6 @@
{ {
"description": "Helpers for building single-page applications on ASP.NET MVC Core", "description": "Helpers for building single-page applications on ASP.NET MVC Core",
"version": "1.0.0-*", "version": "1.1.0-rc1-*",
"packOptions": { "packOptions": {
"repository": { "repository": {
"type": "git", "type": "git",
@@ -21,8 +21,8 @@
"xmlDoc": true "xmlDoc": true
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.NodeServices": "1.0.0-*" "Microsoft.AspNetCore.NodeServices": "1.1.0-*"
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": {},

View File

@@ -0,0 +1,2 @@
[config]
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json

View File

@@ -0,0 +1,90 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
</PropertyGroup>
<ItemGroup>
<None Include="**\*" Exclude="$(GlobalExclude)"/>
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.App">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NET.Sdk.Web">
<Version>1.0.0-alpha-20161104-2-112</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
<Version>1.1.0-preview4-final</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
<Version>1.1.0-*</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
<Version>1.1.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<!-- Files not to show in IDE -->
<None Remove=".bowerrc" />
<None Remove="yarn.lock" />
<!-- Files not to publish (note that the 'dist' subfolders are re-added below) -->
<Content Remove="ClientApp\**" />
</ItemGroup>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
<Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>

View File

@@ -1,4 +1,4 @@
<h2>Counter</h2> <h1>Counter</h1>
<p>This is a simple example of an Angular 2 component.</p> <p>This is a simple example of an Angular 2 component.</p>

View File

@@ -13,7 +13,7 @@ describe('Counter component', () => {
}); });
it('should display a title', async(() => { it('should display a title', async(() => {
const titleText = fixture.nativeElement.querySelector('h2').textContent; const titleText = fixture.nativeElement.querySelector('h1').textContent;
expect(titleText).toEqual('Counter'); expect(titleText).toEqual('Counter');
})); }));

View File

@@ -18,6 +18,7 @@ module.exports = function (config) {
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ['Chrome'],
mime: { 'application/javascript': ['ts','tsx'] },
singleRun: false, singleRun: false,
webpack: require('../../webpack.config.js').filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser webpack: require('../../webpack.config.js').filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
webpackMiddleware: { stats: 'errors-only' } webpackMiddleware: { stats: 'errors-only' }

View File

@@ -1,17 +1,16 @@
FROM microsoft/dotnet:1.0.0-preview2-onbuild FROM microsoft/dotnet:1.1.0-sdk-projectjson
RUN apt-get update RUN apt-get update
RUN wget -qO- https://deb.nodesource.com/setup_4.x | bash - RUN wget -qO- https://deb.nodesource.com/setup_4.x | bash -
RUN apt-get install -y build-essential nodejs RUN apt-get install -y build-essential nodejs
COPY . /app
WORKDIR /app WORKDIR /app
COPY project.json .
RUN ["dotnet", "restore"] RUN ["dotnet", "restore"]
COPY . /app
RUN ["dotnet", "build"] RUN ["dotnet", "build"]
EXPOSE 5000/tcp EXPOSE 5000/tcp
ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000"] CMD ["dotnet", "run", "--server.urls", "http://*:5000"]

View File

@@ -1,3 +1,3 @@
{ {
"sdk": { "version": "1.0.0-preview2-003121" } "sdk": { "version": "1.0.0-preview2-1-003177" }
} }

View File

@@ -1,26 +1,26 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.1", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.AngularServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Razor.Tools": { "Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.SpaServices": "1.1.0-*",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0"
}, },
"tools": { "tools": {
@@ -30,7 +30,7 @@
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.1": {
"imports": [ "imports": [
"dotnet5.6", "dotnet5.6",
"portable-net45+win8" "portable-net45+win8"

View File

@@ -1,11 +0,0 @@
------------------------------------------------------------------
Don't delete this file. Do include it in your source control repo.
------------------------------------------------------------------
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
('dotnet publish' does not publish any directories that didn't exist or were
empty before the publish script started, which means it's not enough just to
run 'npm install' during publishing: you need to ensure node_modules already
existed at least).
Hopefully, this can be removed after the move to the new MSBuild.

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