From 64a8ef11120418f4b7641ff9664186b3d955ffb6 Mon Sep 17 00:00:00 2001 From: Mike Mazmanyan Date: Tue, 26 Jul 2016 17:44:07 +0400 Subject: [PATCH 001/791] Moving and updating "aspnet-webpack" package in templates (#207) --- templates/Angular2Spa/package.json | 2 +- templates/KnockoutSpa/package.json | 2 +- templates/ReactReduxSpa/package.json | 2 +- templates/ReactSpa/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 047a6ead..afb42007 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -2,6 +2,7 @@ "name": "WebApplicationBasic", "version": "0.0.0", "devDependencies": { + "aspnet-webpack": "^1.0.6", "bootstrap": "^3.3.6", "css-loader": "^0.23.1", "expose-loader": "^0.7.1", @@ -28,7 +29,6 @@ "@angular/router": "3.0.0-alpha.8", "angular2-universal": "^0.104.1", "aspnet-prerendering": "^1.0.2", - "aspnet-webpack": "^1.0.1", "css": "^2.2.1", "isomorphic-fetch": "^2.2.1", "preboot": "^2.0.10", diff --git a/templates/KnockoutSpa/package.json b/templates/KnockoutSpa/package.json index c00f8953..9de97387 100644 --- a/templates/KnockoutSpa/package.json +++ b/templates/KnockoutSpa/package.json @@ -2,7 +2,7 @@ "name": "WebApplicationBasic", "version": "0.0.0", "devDependencies": { - "aspnet-webpack": "^1.0.0", + "aspnet-webpack": "^1.0.6", "bootstrap": "^3.3.6", "bundle-loader": "^0.5.4", "crossroads": "^0.12.2", diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index 4070c8af..a1d53df4 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -2,6 +2,7 @@ "name": "WebApplicationBasic", "version": "0.0.0", "devDependencies": { + "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.1", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", @@ -21,7 +22,6 @@ }, "dependencies": { "aspnet-prerendering": "^1.0.2", - "aspnet-webpack": "^1.0.2", "babel-core": "^6.5.2", "domain-task": "^2.0.0", "react": "^15.0.1", diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json index 2b6d0b0a..9776cdb4 100644 --- a/templates/ReactSpa/package.json +++ b/templates/ReactSpa/package.json @@ -2,7 +2,7 @@ "name": "WebApplicationBasic", "version": "0.0.0", "devDependencies": { - "aspnet-webpack": "^1.0.2", + "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.0", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", From 96228711f26cd356bdab816fadfa235383b43b5f Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Thu, 21 Jul 2016 11:41:43 -0400 Subject: [PATCH 002/791] chore(package): Update to rc4, beta router & universal 104.5 104.5 includes some bug fixes. Router beta update required pathMatch on home. Tested w/ JS on/off everything passes & works. --- templates/Angular2Spa/ClientApp/routes.ts | 2 +- templates/Angular2Spa/package.json | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/routes.ts b/templates/Angular2Spa/ClientApp/routes.ts index d693c44a..c0a45b73 100644 --- a/templates/Angular2Spa/ClientApp/routes.ts +++ b/templates/Angular2Spa/ClientApp/routes.ts @@ -4,7 +4,7 @@ import { FetchData } from './components/fetch-data/fetch-data'; import { Counter } from './components/counter/counter'; export const routes: RouterConfig = [ - { path: '', redirectTo: 'home' }, + { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: Home }, { path: 'counter', component: Counter }, { path: 'fetch-data', component: FetchData }, diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index afb42007..5c09d177 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -19,15 +19,15 @@ "webpack-hot-middleware": "^2.10.0" }, "dependencies": { - "@angular/common": "2.0.0-rc.3", - "@angular/compiler": "2.0.0-rc.3", - "@angular/core": "2.0.0-rc.3", - "@angular/http": "2.0.0-rc.3", - "@angular/platform-browser": "2.0.0-rc.3", - "@angular/platform-browser-dynamic": "2.0.0-rc.3", - "@angular/platform-server": "2.0.0-rc.3", - "@angular/router": "3.0.0-alpha.8", - "angular2-universal": "^0.104.1", + "@angular/common": "2.0.0-rc.4", + "@angular/compiler": "2.0.0-rc.4", + "@angular/core": "2.0.0-rc.4", + "@angular/http": "2.0.0-rc.4", + "@angular/platform-browser": "2.0.0-rc.4", + "@angular/platform-browser-dynamic": "2.0.0-rc.4", + "@angular/platform-server": "2.0.0-rc.4", + "@angular/router": "3.0.0-beta.2", + "angular2-universal": "^0.104.5", "aspnet-prerendering": "^1.0.2", "css": "^2.2.1", "isomorphic-fetch": "^2.2.1", From 77b404188bdeebbf33676dc2322de07f6a793031 Mon Sep 17 00:00:00 2001 From: Simon Kamlet Date: Fri, 22 Jul 2016 11:04:51 +0200 Subject: [PATCH 003/791] fix angular's (click) events not being triggered on IE9 --- templates/Angular2Spa/ClientApp/boot-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index be3ca226..d19ff24e 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,3 +1,4 @@ +import 'es6-shim'; require('zone.js'); import 'bootstrap'; import 'reflect-metadata'; From 2fe06ea784d9413a9fb54dd839d5a8c9db0d6421 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 26 Jul 2016 14:57:14 +0100 Subject: [PATCH 004/791] Make the dependency on es6-shim explicit, and include it in the vendor bundle --- templates/Angular2Spa/package.json | 1 + templates/Angular2Spa/webpack.config.vendor.js | 1 + 2 files changed, 2 insertions(+) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 5c09d177..37970041 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -30,6 +30,7 @@ "angular2-universal": "^0.104.5", "aspnet-prerendering": "^1.0.2", "css": "^2.2.1", + "es6-shim": "^0.35.1", "isomorphic-fetch": "^2.2.1", "preboot": "^2.0.10", "rxjs": "5.0.0-beta.6", diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 0fc256ab..cd2bbe00 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -18,6 +18,7 @@ module.exports = { vendor: [ 'bootstrap', 'bootstrap/dist/css/bootstrap.css', + 'es6-shim', 'style-loader', 'jquery', '@angular/common', From 79872c1bde1276861455ed72e216c469c4fd45a3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 26 Jul 2016 16:38:46 +0100 Subject: [PATCH 005/791] Amend aspnet-webpack for better node-inspector support --- .../npm/aspnet-webpack/package.json | 2 +- .../npm/aspnet-webpack/src/LoadViaWebpack.ts | 13 ++++++++++++- .../src/typings/require-from-string.d.ts | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 6ff72aa8..6e12ac70 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.6", + "version": "1.0.7", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/LoadViaWebpack.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/LoadViaWebpack.ts index a6f6fa72..c41d7e82 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/LoadViaWebpack.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/LoadViaWebpack.ts @@ -35,6 +35,12 @@ export function loadViaWebpack(webpackConfigPath: string, modulePath: string, }) } +function setExtension(filePath: string, newExtension: string) { + const oldExtensionIfAny = path.extname(filePath); + const basenameWithoutExtension = path.basename(filePath, oldExtensionIfAny); + return path.join(path.dirname(filePath), basenameWithoutExtension) + newExtension; +} + function loadViaWebpackNoCache(webpackConfigPath: string, modulePath: string) { return new Promise((resolve, reject) => { // Load the Webpack config and make alterations needed for loading the output into Node @@ -94,8 +100,13 @@ function loadViaWebpackNoCache(webpackConfigPath: string, modulePath: string) + stats.toString({ chunks: false })); } + // The dynamically-built module will only appear in node-inspector if it has some nonempty + // file path. The following value is arbitrary (since there's no real compiled file on disk) + // but is sufficient to enable debugging. + const fakeModulePath = setExtension(modulePath, '.js'); + const fileContent = compiler.outputFileSystem.readFileSync(outputVirtualPath, 'utf8'); - const moduleInstance = requireFromString(fileContent); + const moduleInstance = requireFromString(fileContent, fakeModulePath); resolve(moduleInstance); } catch(ex) { reject(ex); diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/typings/require-from-string.d.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/typings/require-from-string.d.ts index 80e46459..ba41884c 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/typings/require-from-string.d.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/typings/require-from-string.d.ts @@ -1,3 +1,3 @@ export namespace requirefromstring { - export function requireFromString(fileContent: string): T; + export function requireFromString(fileContent: string, filename?: string): T; } From f2f67fe880dc78e7aa07c89f2dff0c76c7993cb7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 26 Jul 2016 18:33:27 +0100 Subject: [PATCH 006/791] Support new config options to launch the Node process with a debug listener. This is compatible with node-inspector. --- .../Configuration/Configuration.cs | 6 ++- .../Configuration/NodeServicesOptions.cs | 2 + .../HostingModels/HttpNodeInstance.cs | 9 ++-- .../HostingModels/OutOfProcessNodeInstance.cs | 48 ++++++++++++++++--- .../HostingModels/SocketNodeInstance.cs | 8 +++- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs index bbae3d29..6ecd43fb 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs @@ -68,10 +68,12 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options) switch (options.HostingModel) { case NodeHostingModel.Http: - return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, logger, /* port */ 0); + return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, logger, + 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, logger); + return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName, logger, + options.LaunchWithDebugging, options.DebuggingPort); default: throw new ArgumentException("Unknown hosting model: " + options.HostingModel); } diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index 98c50ecb..5e6f5181 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -21,5 +21,7 @@ public NodeServicesOptions() public string ProjectPath { get; set; } public string[] WatchFileExtensions { get; set; } public ILogger NodeInstanceOutputLogger { get; set; } + public bool LaunchWithDebugging { get; set; } + public int? DebuggingPort { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 53dabf92..0e828ea7 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -33,15 +33,18 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private bool _disposed; private int _portNumber; - public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, int port = 0) - : base( + public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, + bool launchWithDebugging, int? debuggingPort, int port = 0) + : base( EmbeddedResourceReader.Read( typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, watchFileExtensions, MakeCommandLineOptions(port), - nodeInstanceOutputLogger) + nodeInstanceOutputLogger, + launchWithDebugging, + debuggingPort) { _client = new HttpClient(); } diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 06649994..01a90554 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -21,11 +21,22 @@ public abstract class OutOfProcessNodeInstance : INodeInstance { protected readonly ILogger OutputLogger; private const string ConnectionEstablishedMessage = "[Microsoft.AspNetCore.NodeServices:Listening]"; + private const string DebuggingStartedMessageFormat = @"----- +*** Node.js debugging is enabled *** +{0} + +To debug, run: + node-inspector{1} + +If you haven't yet installed node-inspector, you can do so as follows: + npm install -g node-inspector +-----"; private readonly TaskCompletionSource _connectionIsReadySource = new TaskCompletionSource(); private bool _disposed; private readonly StringAsTempFile _entryPointScript; private FileSystemWatcher _fileSystemWatcher; private readonly Process _nodeProcess; + private int? _nodeDebuggingPort; private bool _nodeProcessNeedsRestart; private readonly string[] _watchFileExtensions; @@ -34,7 +45,9 @@ public OutOfProcessNodeInstance( string projectPath, string[] watchFileExtensions, string commandLineArguments, - ILogger nodeOutputLogger) + ILogger nodeOutputLogger, + bool launchWithDebugging, + int? debuggingPort) { if (nodeOutputLogger == null) { @@ -43,8 +56,9 @@ public OutOfProcessNodeInstance( OutputLogger = nodeOutputLogger; _entryPointScript = new StringAsTempFile(entryPointScript); - - var startInfo = PrepareNodeProcessStartInfo(_entryPointScript.FileName, projectPath, commandLineArguments); + + var startInfo = PrepareNodeProcessStartInfo(_entryPointScript.FileName, projectPath, commandLineArguments, + launchWithDebugging, debuggingPort); _nodeProcess = LaunchNodeProcess(startInfo); _watchFileExtensions = watchFileExtensions; _fileSystemWatcher = BeginFileWatcher(projectPath); @@ -84,11 +98,23 @@ public void Dispose() // This method is virtual, as it provides a way to override the NODE_PATH or the path to node.exe protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( - string entryPointFilename, string projectPath, string commandLineArguments) + string entryPointFilename, string projectPath, string commandLineArguments, + bool launchWithDebugging, int? debuggingPort) { + string debuggingArgs; + if (launchWithDebugging) + { + debuggingArgs = debuggingPort.HasValue ? $"--debug={debuggingPort.Value} " : "--debug "; + _nodeDebuggingPort = debuggingPort; + } + else + { + debuggingArgs = string.Empty; + } + var startInfo = new ProcessStartInfo("node") { - Arguments = "\"" + entryPointFilename + "\" " + (commandLineArguments ?? string.Empty), + Arguments = debuggingArgs + "\"" + entryPointFilename + "\" " + (commandLineArguments ?? string.Empty), UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, @@ -201,7 +227,12 @@ private void ConnectToInputOutputStreams() { if (evt.Data != null) { - if (!initializationIsCompleted) + if (IsDebuggerListeningMessage(evt.Data)) + { + var debugPortArg = _nodeDebuggingPort.HasValue ? $" --debug-port={_nodeDebuggingPort.Value}" : string.Empty; + OutputLogger.LogWarning(string.Format(DebuggingStartedMessageFormat, evt.Data, debugPortArg)); + } + else if (!initializationIsCompleted) { _connectionIsReadySource.SetException( new InvalidOperationException("The Node.js process failed to initialize: " + evt.Data)); @@ -218,6 +249,11 @@ private void ConnectToInputOutputStreams() _nodeProcess.BeginErrorReadLine(); } + private static bool IsDebuggerListeningMessage(string message) + { + return message.StartsWith("Debugger listening on port ", StringComparison.OrdinalIgnoreCase); + } + private FileSystemWatcher BeginFileWatcher(string rootDir) { if (_watchFileExtensions == null || _watchFileExtensions.Length == 0) diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index f5bbce1a..5f3a6ec3 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -37,14 +37,18 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance private string _socketAddress; private VirtualConnectionClient _virtualConnectionClient; - public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, ILogger nodeInstanceOutputLogger) : base( + public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, + ILogger nodeInstanceOutputLogger, bool launchWithDebugging, int? debuggingPort) + : base( EmbeddedResourceReader.Read( typeof(SocketNodeInstance), "/Content/Node/entrypoint-socket.js"), projectPath, watchFileExtensions, MakeNewCommandLineOptions(socketAddress), - nodeInstanceOutputLogger) + nodeInstanceOutputLogger, + launchWithDebugging, + debuggingPort) { _socketAddress = socketAddress; } From c892f7da35560c79d9a6a46b20607c4ecb4f812c Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 09:50:43 +0100 Subject: [PATCH 007/791] Support debugging when on IPv6 network (Node's "Debugger listening" message is phrased differently there) --- .../HostingModels/OutOfProcessNodeInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 01a90554..e85ed8c1 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -251,7 +251,7 @@ private void ConnectToInputOutputStreams() private static bool IsDebuggerListeningMessage(string message) { - return message.StartsWith("Debugger listening on port ", StringComparison.OrdinalIgnoreCase); + return message.StartsWith("Debugger listening ", StringComparison.OrdinalIgnoreCase); } private FileSystemWatcher BeginFileWatcher(string rootDir) From 698921d15751a12296cf106479203c1e7bc4f3d8 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 10:43:16 +0100 Subject: [PATCH 008/791] Update Dockerfile in all templates to match .NET Core 1.0 RTM. Fixes #120 --- templates/Angular2Spa/Dockerfile | 14 ++++++++------ templates/KnockoutSpa/Dockerfile | 14 ++++++++------ templates/ReactReduxSpa/Dockerfile | 14 ++++++++------ templates/ReactSpa/Dockerfile | 14 ++++++++------ templates/WebApplicationBasic/Dockerfile | 14 ++++++++------ 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/templates/Angular2Spa/Dockerfile b/templates/Angular2Spa/Dockerfile index 63d7c456..10eb41aa 100644 --- a/templates/Angular2Spa/Dockerfile +++ b/templates/Angular2Spa/Dockerfile @@ -1,11 +1,13 @@ -FROM microsoft/aspnet:1.0.0-rc1-update1 - -RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list -RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +FROM microsoft/dotnet:latest COPY . /app + WORKDIR /app -RUN ["dnu", "restore"] + +RUN ["dotnet", "restore"] + +RUN ["dotnet", "build"] EXPOSE 5000/tcp -ENTRYPOINT ["dnx", "-p", "project.json", "web"] + +ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000/"] diff --git a/templates/KnockoutSpa/Dockerfile b/templates/KnockoutSpa/Dockerfile index 63d7c456..10eb41aa 100644 --- a/templates/KnockoutSpa/Dockerfile +++ b/templates/KnockoutSpa/Dockerfile @@ -1,11 +1,13 @@ -FROM microsoft/aspnet:1.0.0-rc1-update1 - -RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list -RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +FROM microsoft/dotnet:latest COPY . /app + WORKDIR /app -RUN ["dnu", "restore"] + +RUN ["dotnet", "restore"] + +RUN ["dotnet", "build"] EXPOSE 5000/tcp -ENTRYPOINT ["dnx", "-p", "project.json", "web"] + +ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000/"] diff --git a/templates/ReactReduxSpa/Dockerfile b/templates/ReactReduxSpa/Dockerfile index 63d7c456..10eb41aa 100644 --- a/templates/ReactReduxSpa/Dockerfile +++ b/templates/ReactReduxSpa/Dockerfile @@ -1,11 +1,13 @@ -FROM microsoft/aspnet:1.0.0-rc1-update1 - -RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list -RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +FROM microsoft/dotnet:latest COPY . /app + WORKDIR /app -RUN ["dnu", "restore"] + +RUN ["dotnet", "restore"] + +RUN ["dotnet", "build"] EXPOSE 5000/tcp -ENTRYPOINT ["dnx", "-p", "project.json", "web"] + +ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000/"] diff --git a/templates/ReactSpa/Dockerfile b/templates/ReactSpa/Dockerfile index 63d7c456..10eb41aa 100644 --- a/templates/ReactSpa/Dockerfile +++ b/templates/ReactSpa/Dockerfile @@ -1,11 +1,13 @@ -FROM microsoft/aspnet:1.0.0-rc1-update1 - -RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list -RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +FROM microsoft/dotnet:latest COPY . /app + WORKDIR /app -RUN ["dnu", "restore"] + +RUN ["dotnet", "restore"] + +RUN ["dotnet", "build"] EXPOSE 5000/tcp -ENTRYPOINT ["dnx", "-p", "project.json", "web"] + +ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000/"] diff --git a/templates/WebApplicationBasic/Dockerfile b/templates/WebApplicationBasic/Dockerfile index 63d7c456..10eb41aa 100644 --- a/templates/WebApplicationBasic/Dockerfile +++ b/templates/WebApplicationBasic/Dockerfile @@ -1,11 +1,13 @@ -FROM microsoft/aspnet:1.0.0-rc1-update1 - -RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list -RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +FROM microsoft/dotnet:latest COPY . /app + WORKDIR /app -RUN ["dnu", "restore"] + +RUN ["dotnet", "restore"] + +RUN ["dotnet", "build"] EXPOSE 5000/tcp -ENTRYPOINT ["dnx", "-p", "project.json", "web"] + +ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000/"] From 14337e32abe12a47e6365b54102f9dbb1b058e1e Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 11:03:04 +0100 Subject: [PATCH 009/791] WebpackDevMiddleware now preserves client's view of hostname when doing 302 to /__webpack_hmr --- .../Webpack/WebpackDevMiddleware.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index 959ee46e..a9398d72 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Builder public static class WebpackDevMiddleware { private const string WebpackDevMiddlewareScheme = "http"; - private const string WebpackDevMiddlewareHostname = "localhost"; private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; @@ -64,19 +63,27 @@ public static void UseWebpackDevMiddleware( JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener + // 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 + // 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 + // 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. var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, - WebpackDevMiddlewareHostname, devServerInfo.Port.ToString()); + "localhost", devServerInfo.Port.ToString()); appBuilder.UseMiddleware(devServerInfo.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. + // 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 => async ctx => { + var hostname = ctx.Request.Host.Host; ctx.Response.Redirect( - $"{WebpackDevMiddlewareScheme}://{WebpackDevMiddlewareHostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); + $"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); await Task.Yield(); }); }); From de960d80aa25c369bf2d49cc53b392f10fd834b7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 11:13:09 +0100 Subject: [PATCH 010/791] Allow explicit configuration of port number for webpack dev middleware server. Fixes #223. --- .../Webpack/WebpackDevMiddlewareOptions.cs | 1 + .../npm/aspnet-webpack/package.json | 2 +- .../npm/aspnet-webpack/src/WebpackDevMiddleware.ts | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs index 08a6dbd9..fcdde442 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs @@ -3,6 +3,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack public class WebpackDevMiddlewareOptions { public bool HotModuleReplacement { get; set; } + public int HotModuleReplacementServerPort { get; set; } public bool ReactHotModuleReplacement { get; set; } public string ConfigFile { get; set; } } diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 6e12ac70..4f652b7f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.7", + "version": "1.0.8", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts index e33433fc..af639c98 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts @@ -16,6 +16,7 @@ interface CreateDevServerOptions { // These are the options configured in C# and then JSON-serialized, hence the C#-style naming interface DevServerOptions { HotModuleReplacement: boolean; + HotModuleReplacementServerPort: number; ReactHotModuleReplacement: boolean; } @@ -35,9 +36,11 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option return; } + // The default value, 0, means 'choose randomly' + const suggestedHMRPortOrZero = options.suppliedOptions.HotModuleReplacementServerPort; + const app = connect(); - const defaultPort = 0; // 0 means 'choose randomly'. Could allow an explicit value to be supplied instead. - const listener = app.listen(defaultPort, () => { + const listener = app.listen(suggestedHMRPortOrZero, () => { // Build the final Webpack config based on supplied options if (enableHotModuleReplacement) { // TODO: Stop assuming there's an entry point called 'main' From e9ca43440512f06e0e7ef1aedad8ee58b1b90afa Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 12:25:32 +0100 Subject: [PATCH 011/791] Update all templates to match latest "yo aspnet" output --- templates/Angular2Spa/Program.cs | 31 ++++++++++++ templates/Angular2Spa/Startup.cs | 49 ++++++++++--------- templates/Angular2Spa/appsettings.json | 2 +- templates/Angular2Spa/project.json | 41 ++-------------- templates/KnockoutSpa/Program.cs | 31 ++++++++++++ templates/KnockoutSpa/Startup.cs | 49 ++++++++++--------- templates/KnockoutSpa/appsettings.json | 2 +- templates/KnockoutSpa/project.json | 41 ++-------------- templates/ReactReduxSpa/Program.cs | 31 ++++++++++++ templates/ReactReduxSpa/Startup.cs | 49 ++++++++++--------- templates/ReactReduxSpa/appsettings.json | 2 +- templates/ReactReduxSpa/project.json | 41 ++-------------- templates/ReactSpa/Program.cs | 31 ++++++++++++ templates/ReactSpa/Startup.cs | 49 ++++++++++--------- templates/ReactSpa/appsettings.json | 2 +- templates/ReactSpa/project.json | 41 ++-------------- templates/WebApplicationBasic/Program.cs | 31 ++++++++++++ templates/WebApplicationBasic/Startup.cs | 44 ++++++++++------- .../WebApplicationBasic/appsettings.json | 2 +- templates/WebApplicationBasic/project.json | 41 ++-------------- 20 files changed, 311 insertions(+), 299 deletions(-) create mode 100644 templates/Angular2Spa/Program.cs create mode 100644 templates/KnockoutSpa/Program.cs create mode 100644 templates/ReactReduxSpa/Program.cs create mode 100644 templates/ReactSpa/Program.cs create mode 100644 templates/WebApplicationBasic/Program.cs diff --git a/templates/Angular2Spa/Program.cs b/templates/Angular2Spa/Program.cs new file mode 100644 index 00000000..b2e5e4b8 --- /dev/null +++ b/templates/Angular2Spa/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/templates/Angular2Spa/Startup.cs b/templates/Angular2Spa/Startup.cs index 0c5cc48c..e5d73ed6 100755 --- a/templates/Angular2Spa/Startup.cs +++ b/templates/Angular2Spa/Startup.cs @@ -1,42 +1,57 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SpaServices.Webpack; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Serialization; namespace WebApplicationBasic { public class Startup { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().AddJsonOptions(options => - { - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); + // Add framework services. + services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseDeveloperExceptionPage(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); - if (env.IsDevelopment()) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true }); } + else + { + app.UseExceptionHandler("/Home/Error"); + } app.UseStaticFiles(); - loggerFactory.AddConsole(); + app.UseMvc(routes => { routes.MapRoute( @@ -48,17 +63,5 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHo defaults: new { controller = "Home", action = "Index" }); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseKestrel() - .UseStartup() - .Build(); - - host.Run(); - } } } diff --git a/templates/Angular2Spa/appsettings.json b/templates/Angular2Spa/appsettings.json index e5472e56..723c096a 100755 --- a/templates/Angular2Spa/appsettings.json +++ b/templates/Angular2Spa/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Verbose", + "Default": "Debug", "System": "Information", "Microsoft": "Information" } diff --git a/templates/Angular2Spa/project.json b/templates/Angular2Spa/project.json index 1ce5f413..845fe680 100755 --- a/templates/Angular2Spa/project.json +++ b/templates/Angular2Spa/project.json @@ -4,7 +4,6 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.AngularServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", @@ -17,46 +16,16 @@ "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { - "version": "1.0.0-preview2-final", - "type": "build" - } + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" }, "tools": { - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.AspNetCore.Server.IISIntegration.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.EntityFrameworkCore.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, - "Microsoft.Extensions.SecretManager.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" }, @@ -64,14 +33,12 @@ "netcoreapp1.0": { "imports": [ "dotnet5.6", - "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { - "debugType": "portable", "emitEntryPoint": true, "preserveCompilationContext": true }, diff --git a/templates/KnockoutSpa/Program.cs b/templates/KnockoutSpa/Program.cs new file mode 100644 index 00000000..b2e5e4b8 --- /dev/null +++ b/templates/KnockoutSpa/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/templates/KnockoutSpa/Startup.cs b/templates/KnockoutSpa/Startup.cs index 0c5cc48c..e5d73ed6 100755 --- a/templates/KnockoutSpa/Startup.cs +++ b/templates/KnockoutSpa/Startup.cs @@ -1,42 +1,57 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SpaServices.Webpack; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Serialization; namespace WebApplicationBasic { public class Startup { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().AddJsonOptions(options => - { - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); + // Add framework services. + services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseDeveloperExceptionPage(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); - if (env.IsDevelopment()) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true }); } + else + { + app.UseExceptionHandler("/Home/Error"); + } app.UseStaticFiles(); - loggerFactory.AddConsole(); + app.UseMvc(routes => { routes.MapRoute( @@ -48,17 +63,5 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHo defaults: new { controller = "Home", action = "Index" }); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseKestrel() - .UseStartup() - .Build(); - - host.Run(); - } } } diff --git a/templates/KnockoutSpa/appsettings.json b/templates/KnockoutSpa/appsettings.json index e5472e56..723c096a 100755 --- a/templates/KnockoutSpa/appsettings.json +++ b/templates/KnockoutSpa/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Verbose", + "Default": "Debug", "System": "Information", "Microsoft": "Information" } diff --git a/templates/KnockoutSpa/project.json b/templates/KnockoutSpa/project.json index 66ff1470..d3e2777a 100755 --- a/templates/KnockoutSpa/project.json +++ b/templates/KnockoutSpa/project.json @@ -4,7 +4,6 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.SpaServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", @@ -17,46 +16,16 @@ "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { - "version": "1.0.0-preview2-final", - "type": "build" - } + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" }, "tools": { - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.AspNetCore.Server.IISIntegration.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.EntityFrameworkCore.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, - "Microsoft.Extensions.SecretManager.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" }, @@ -64,14 +33,12 @@ "netcoreapp1.0": { "imports": [ "dotnet5.6", - "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { - "debugType": "portable", "emitEntryPoint": true, "preserveCompilationContext": true }, diff --git a/templates/ReactReduxSpa/Program.cs b/templates/ReactReduxSpa/Program.cs new file mode 100644 index 00000000..b2e5e4b8 --- /dev/null +++ b/templates/ReactReduxSpa/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/templates/ReactReduxSpa/Startup.cs b/templates/ReactReduxSpa/Startup.cs index 6f4babb1..750d79c0 100755 --- a/templates/ReactReduxSpa/Startup.cs +++ b/templates/ReactReduxSpa/Startup.cs @@ -1,43 +1,58 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SpaServices.Webpack; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Serialization; namespace WebApplicationBasic { public class Startup { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().AddJsonOptions(options => - { - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); + // Add framework services. + services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseDeveloperExceptionPage(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); - if (env.IsDevelopment()) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true, ReactHotModuleReplacement = true }); } + else + { + app.UseExceptionHandler("/Home/Error"); + } app.UseStaticFiles(); - loggerFactory.AddConsole(); + app.UseMvc(routes => { routes.MapRoute( @@ -49,17 +64,5 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHo defaults: new { controller = "Home", action = "Index" }); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseKestrel() - .UseStartup() - .Build(); - - host.Run(); - } } } diff --git a/templates/ReactReduxSpa/appsettings.json b/templates/ReactReduxSpa/appsettings.json index e5472e56..723c096a 100755 --- a/templates/ReactReduxSpa/appsettings.json +++ b/templates/ReactReduxSpa/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Verbose", + "Default": "Debug", "System": "Information", "Microsoft": "Information" } diff --git a/templates/ReactReduxSpa/project.json b/templates/ReactReduxSpa/project.json index f768ecaf..0794a38c 100755 --- a/templates/ReactReduxSpa/project.json +++ b/templates/ReactReduxSpa/project.json @@ -4,7 +4,6 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", @@ -17,46 +16,16 @@ "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { - "version": "1.0.0-preview2-final", - "type": "build" - } + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" }, "tools": { - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.AspNetCore.Server.IISIntegration.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.EntityFrameworkCore.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, - "Microsoft.Extensions.SecretManager.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" }, @@ -64,14 +33,12 @@ "netcoreapp1.0": { "imports": [ "dotnet5.6", - "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { - "debugType": "portable", "emitEntryPoint": true, "preserveCompilationContext": true }, diff --git a/templates/ReactSpa/Program.cs b/templates/ReactSpa/Program.cs new file mode 100644 index 00000000..b2e5e4b8 --- /dev/null +++ b/templates/ReactSpa/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/templates/ReactSpa/Startup.cs b/templates/ReactSpa/Startup.cs index 6f4babb1..750d79c0 100755 --- a/templates/ReactSpa/Startup.cs +++ b/templates/ReactSpa/Startup.cs @@ -1,43 +1,58 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SpaServices.Webpack; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Serialization; namespace WebApplicationBasic { public class Startup { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().AddJsonOptions(options => - { - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); + // Add framework services. + services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseDeveloperExceptionPage(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); - if (env.IsDevelopment()) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true, ReactHotModuleReplacement = true }); } + else + { + app.UseExceptionHandler("/Home/Error"); + } app.UseStaticFiles(); - loggerFactory.AddConsole(); + app.UseMvc(routes => { routes.MapRoute( @@ -49,17 +64,5 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHo defaults: new { controller = "Home", action = "Index" }); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseKestrel() - .UseStartup() - .Build(); - - host.Run(); - } } } diff --git a/templates/ReactSpa/appsettings.json b/templates/ReactSpa/appsettings.json index e5472e56..723c096a 100755 --- a/templates/ReactSpa/appsettings.json +++ b/templates/ReactSpa/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Verbose", + "Default": "Debug", "System": "Information", "Microsoft": "Information" } diff --git a/templates/ReactSpa/project.json b/templates/ReactSpa/project.json index f768ecaf..0794a38c 100755 --- a/templates/ReactSpa/project.json +++ b/templates/ReactSpa/project.json @@ -4,7 +4,6 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", @@ -17,46 +16,16 @@ "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { - "version": "1.0.0-preview2-final", - "type": "build" - } + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" }, "tools": { - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.AspNetCore.Server.IISIntegration.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.EntityFrameworkCore.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, - "Microsoft.Extensions.SecretManager.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" }, @@ -64,14 +33,12 @@ "netcoreapp1.0": { "imports": [ "dotnet5.6", - "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { - "debugType": "portable", "emitEntryPoint": true, "preserveCompilationContext": true }, diff --git a/templates/WebApplicationBasic/Program.cs b/templates/WebApplicationBasic/Program.cs new file mode 100644 index 00000000..b2e5e4b8 --- /dev/null +++ b/templates/WebApplicationBasic/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/templates/WebApplicationBasic/Startup.cs b/templates/WebApplicationBasic/Startup.cs index 4ddc86c9..fe5473d7 100755 --- a/templates/WebApplicationBasic/Startup.cs +++ b/templates/WebApplicationBasic/Startup.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -13,19 +12,42 @@ namespace WebApplicationBasic { public class Startup { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + // Add framework services. services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseDeveloperExceptionPage(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } app.UseStaticFiles(); - loggerFactory.AddConsole(); + app.UseMvc(routes => { routes.MapRoute( @@ -33,17 +55,5 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHo template: "{controller=Home}/{action=Index}/{id?}"); }); } - - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseKestrel() - .UseStartup() - .Build(); - - host.Run(); - } } } diff --git a/templates/WebApplicationBasic/appsettings.json b/templates/WebApplicationBasic/appsettings.json index e5472e56..723c096a 100755 --- a/templates/WebApplicationBasic/appsettings.json +++ b/templates/WebApplicationBasic/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Verbose", + "Default": "Debug", "System": "Information", "Microsoft": "Information" } diff --git a/templates/WebApplicationBasic/project.json b/templates/WebApplicationBasic/project.json index a28b4b39..4464a43f 100755 --- a/templates/WebApplicationBasic/project.json +++ b/templates/WebApplicationBasic/project.json @@ -4,7 +4,6 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Razor.Tools": { @@ -16,60 +15,28 @@ "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "1.0.0", "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "type": "build" - }, - "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { - "version": "1.0.0-preview2-final", - "type": "build" - } + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" }, "tools": { - "Microsoft.AspNetCore.Razor.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.AspNetCore.Server.IISIntegration.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.EntityFrameworkCore.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - }, - "Microsoft.Extensions.SecretManager.Tools": { - "version": "1.0.0-preview2-final", - "imports": "portable-net45+win8+dnxcore50" - }, - "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { - "version": "1.0.0-preview2-final", - "imports": [ - "portable-net45+win8+dnxcore50", - "portable-net45+win8" - ] - } + "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", - "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { - "debugType": "portable", "emitEntryPoint": true, "preserveCompilationContext": true }, From 0a3463031b622f7d9e5b6a426c979f5055e69c4d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 13:09:54 +0100 Subject: [PATCH 012/791] Add Node.js support in all the Docker containers --- templates/Angular2Spa/Dockerfile | 5 +++++ templates/KnockoutSpa/Dockerfile | 5 +++++ templates/ReactReduxSpa/Dockerfile | 5 +++++ templates/ReactSpa/Dockerfile | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/templates/Angular2Spa/Dockerfile b/templates/Angular2Spa/Dockerfile index 10eb41aa..b382b9ea 100644 --- a/templates/Angular2Spa/Dockerfile +++ b/templates/Angular2Spa/Dockerfile @@ -4,6 +4,11 @@ COPY . /app WORKDIR /app +# Add Node.js to the container. If you don't want to wait for this to install every +# time you rebuild your container, consider creating an image that has it preinstalled. +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/KnockoutSpa/Dockerfile b/templates/KnockoutSpa/Dockerfile index 10eb41aa..b382b9ea 100644 --- a/templates/KnockoutSpa/Dockerfile +++ b/templates/KnockoutSpa/Dockerfile @@ -4,6 +4,11 @@ COPY . /app WORKDIR /app +# Add Node.js to the container. If you don't want to wait for this to install every +# time you rebuild your container, consider creating an image that has it preinstalled. +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/ReactReduxSpa/Dockerfile b/templates/ReactReduxSpa/Dockerfile index 10eb41aa..b382b9ea 100644 --- a/templates/ReactReduxSpa/Dockerfile +++ b/templates/ReactReduxSpa/Dockerfile @@ -4,6 +4,11 @@ COPY . /app WORKDIR /app +# Add Node.js to the container. If you don't want to wait for this to install every +# time you rebuild your container, consider creating an image that has it preinstalled. +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/ReactSpa/Dockerfile b/templates/ReactSpa/Dockerfile index 10eb41aa..b382b9ea 100644 --- a/templates/ReactSpa/Dockerfile +++ b/templates/ReactSpa/Dockerfile @@ -4,6 +4,11 @@ COPY . /app WORKDIR /app +# Add Node.js to the container. If you don't want to wait for this to install every +# time you rebuild your container, consider creating an image that has it preinstalled. +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + RUN ["dotnet", "restore"] RUN ["dotnet", "build"] From 3403eb75487cd487666e550777d18fd9c2116a52 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 27 Jul 2016 13:29:18 +0100 Subject: [PATCH 013/791] Make aspnet-webpack compatible with older versions of the NodeServices package --- .../npm/aspnet-webpack/package.json | 2 +- .../npm/aspnet-webpack/src/WebpackDevMiddleware.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 4f652b7f..8980b92f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.8", + "version": "1.0.9", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts index af639c98..f34eefd8 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts @@ -37,7 +37,7 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option } // The default value, 0, means 'choose randomly' - const suggestedHMRPortOrZero = options.suppliedOptions.HotModuleReplacementServerPort; + const suggestedHMRPortOrZero = options.suppliedOptions.HotModuleReplacementServerPort || 0; const app = connect(); const listener = app.listen(suggestedHMRPortOrZero, () => { From c07bd9627009ef63b17d809093488ee94f62cab9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 4 Aug 2016 17:45:04 +1000 Subject: [PATCH 014/791] Update generator-aspnetcore-spa version --- templates/yeoman/src/generator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/yeoman/src/generator/package.json b/templates/yeoman/src/generator/package.json index ad60454d..839ed750 100644 --- a/templates/yeoman/src/generator/package.json +++ b/templates/yeoman/src/generator/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.2", + "version": "0.2.3", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From cf1a127e7dac44b8975d0f0ec5267eece1088db9 Mon Sep 17 00:00:00 2001 From: Aidan Steele Date: Thu, 4 Aug 2016 11:42:15 +1000 Subject: [PATCH 015/791] Perform nodejs installation before COPY in Dockerfile templates --- templates/Angular2Spa/Dockerfile | 8 +++----- templates/KnockoutSpa/Dockerfile | 8 +++----- templates/ReactReduxSpa/Dockerfile | 8 +++----- templates/ReactSpa/Dockerfile | 8 +++----- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/templates/Angular2Spa/Dockerfile b/templates/Angular2Spa/Dockerfile index b382b9ea..748edec0 100644 --- a/templates/Angular2Spa/Dockerfile +++ b/templates/Angular2Spa/Dockerfile @@ -1,14 +1,12 @@ FROM microsoft/dotnet:latest +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + COPY . /app WORKDIR /app -# Add Node.js to the container. If you don't want to wait for this to install every -# time you rebuild your container, consider creating an image that has it preinstalled. -RUN apt-get update -RUN apt-get install -y build-essential nodejs nodejs-legacy - RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/KnockoutSpa/Dockerfile b/templates/KnockoutSpa/Dockerfile index b382b9ea..748edec0 100644 --- a/templates/KnockoutSpa/Dockerfile +++ b/templates/KnockoutSpa/Dockerfile @@ -1,14 +1,12 @@ FROM microsoft/dotnet:latest +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + COPY . /app WORKDIR /app -# Add Node.js to the container. If you don't want to wait for this to install every -# time you rebuild your container, consider creating an image that has it preinstalled. -RUN apt-get update -RUN apt-get install -y build-essential nodejs nodejs-legacy - RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/ReactReduxSpa/Dockerfile b/templates/ReactReduxSpa/Dockerfile index b382b9ea..748edec0 100644 --- a/templates/ReactReduxSpa/Dockerfile +++ b/templates/ReactReduxSpa/Dockerfile @@ -1,14 +1,12 @@ FROM microsoft/dotnet:latest +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + COPY . /app WORKDIR /app -# Add Node.js to the container. If you don't want to wait for this to install every -# time you rebuild your container, consider creating an image that has it preinstalled. -RUN apt-get update -RUN apt-get install -y build-essential nodejs nodejs-legacy - RUN ["dotnet", "restore"] RUN ["dotnet", "build"] diff --git a/templates/ReactSpa/Dockerfile b/templates/ReactSpa/Dockerfile index b382b9ea..748edec0 100644 --- a/templates/ReactSpa/Dockerfile +++ b/templates/ReactSpa/Dockerfile @@ -1,14 +1,12 @@ FROM microsoft/dotnet:latest +RUN apt-get update +RUN apt-get install -y build-essential nodejs nodejs-legacy + COPY . /app WORKDIR /app -# Add Node.js to the container. If you don't want to wait for this to install every -# time you rebuild your container, consider creating an image that has it preinstalled. -RUN apt-get update -RUN apt-get install -y build-essential nodejs nodejs-legacy - RUN ["dotnet", "restore"] RUN ["dotnet", "build"] From a631f77a33a493d2e9961da1e8535f0ef2466703 Mon Sep 17 00:00:00 2001 From: Aidan Steele Date: Thu, 4 Aug 2016 11:56:10 +1000 Subject: [PATCH 016/791] Copy only project.json before dotnet restore in Dockerfile templates --- templates/Angular2Spa/Dockerfile | 4 ++-- templates/KnockoutSpa/Dockerfile | 4 ++-- templates/ReactReduxSpa/Dockerfile | 4 ++-- templates/ReactSpa/Dockerfile | 4 ++-- templates/WebApplicationBasic/Dockerfile | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/Angular2Spa/Dockerfile b/templates/Angular2Spa/Dockerfile index 748edec0..b828f968 100644 --- a/templates/Angular2Spa/Dockerfile +++ b/templates/Angular2Spa/Dockerfile @@ -3,12 +3,12 @@ FROM microsoft/dotnet:latest RUN apt-get update RUN apt-get install -y build-essential nodejs nodejs-legacy -COPY . /app - WORKDIR /app +COPY project.json . RUN ["dotnet", "restore"] +COPY . /app RUN ["dotnet", "build"] EXPOSE 5000/tcp diff --git a/templates/KnockoutSpa/Dockerfile b/templates/KnockoutSpa/Dockerfile index 748edec0..b828f968 100644 --- a/templates/KnockoutSpa/Dockerfile +++ b/templates/KnockoutSpa/Dockerfile @@ -3,12 +3,12 @@ FROM microsoft/dotnet:latest RUN apt-get update RUN apt-get install -y build-essential nodejs nodejs-legacy -COPY . /app - WORKDIR /app +COPY project.json . RUN ["dotnet", "restore"] +COPY . /app RUN ["dotnet", "build"] EXPOSE 5000/tcp diff --git a/templates/ReactReduxSpa/Dockerfile b/templates/ReactReduxSpa/Dockerfile index 748edec0..b828f968 100644 --- a/templates/ReactReduxSpa/Dockerfile +++ b/templates/ReactReduxSpa/Dockerfile @@ -3,12 +3,12 @@ FROM microsoft/dotnet:latest RUN apt-get update RUN apt-get install -y build-essential nodejs nodejs-legacy -COPY . /app - WORKDIR /app +COPY project.json . RUN ["dotnet", "restore"] +COPY . /app RUN ["dotnet", "build"] EXPOSE 5000/tcp diff --git a/templates/ReactSpa/Dockerfile b/templates/ReactSpa/Dockerfile index 748edec0..b828f968 100644 --- a/templates/ReactSpa/Dockerfile +++ b/templates/ReactSpa/Dockerfile @@ -3,12 +3,12 @@ FROM microsoft/dotnet:latest RUN apt-get update RUN apt-get install -y build-essential nodejs nodejs-legacy -COPY . /app - WORKDIR /app +COPY project.json . RUN ["dotnet", "restore"] +COPY . /app RUN ["dotnet", "build"] EXPOSE 5000/tcp diff --git a/templates/WebApplicationBasic/Dockerfile b/templates/WebApplicationBasic/Dockerfile index 10eb41aa..134c714c 100644 --- a/templates/WebApplicationBasic/Dockerfile +++ b/templates/WebApplicationBasic/Dockerfile @@ -1,11 +1,11 @@ FROM microsoft/dotnet:latest -COPY . /app - WORKDIR /app +COPY project.json . RUN ["dotnet", "restore"] +COPY . /app RUN ["dotnet", "build"] EXPOSE 5000/tcp From 4665d1f458194f8f9182ba94b94e3d0fdb9a2363 Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Wed, 27 Jul 2016 15:33:17 -0400 Subject: [PATCH 017/791] docs(readme): Show how to get started with yeoman --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed2f172c..df94094e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,19 @@ Everything here is cross-platform, and works with .NET Core 1.0 RC2 or later on If you want to build a brand-new ASP.NET Core app that uses Angular 2 / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework, and generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. -See: [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/). It's much easier than configuring everything to work together manually! +```` +// install yeoman, the generator, and a short-term global webpack dependency +npm install -g yo generator-aspnetcore-spa webpack + +// go to a new directory and install your framework of choice! +cd some-empty-directory +yo aspnetcore-spa + +// Run it! +dotnet run +```` + +For more details see: [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/). It's much easier than configuring everything to work together manually! ## Adding to existing applications From 7052fa0ad27d272257fec53c7c12cb25a35412ec Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 4 Aug 2016 18:03:43 +1000 Subject: [PATCH 018/791] Rephrased docs to clarify which are the parts you type into the command line --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index df94094e..85dd692f 100644 --- a/README.md +++ b/README.md @@ -24,22 +24,22 @@ Everything here is cross-platform, and works with .NET Core 1.0 RC2 or later on ## Creating new applications -If you want to build a brand-new ASP.NET Core app that uses Angular 2 / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework, and generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. +If you want to build a brand-new ASP.NET Core app that uses Angular 2 / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework, and generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually! -```` -// install yeoman, the generator, and a short-term global webpack dependency -npm install -g yo generator-aspnetcore-spa webpack +To do this, first install Yeoman and these generator templates: -// go to a new directory and install your framework of choice! -cd some-empty-directory -yo aspnetcore-spa + npm install -g yo generator-aspnetcore-spa -// Run it! -dotnet run -```` +Then you can generate your new application starting point: -For more details see: [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/). It's much easier than configuring everything to work together manually! + cd some-empty-directory + yo aspnetcore-spa +Finally, once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core Single Page Application: + + dotnet run + +For a more detailed walkthrough, see [getting started with the `aspnetcore-spa` generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/). ## Adding to existing applications From 2a6465b27aa0c8deebbeb672f5ee0e95d2f20676 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 8 Aug 2016 10:54:22 +1000 Subject: [PATCH 019/791] FIxing path separator to address #247 (#248) * Correcting path separator to ; * Using the proper API from System.IO to get path separator --- .../HostingModels/OutOfProcessNodeInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index e85ed8c1..3c9cfd51 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -126,7 +126,7 @@ protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty; if (existingNodePath != string.Empty) { - existingNodePath += ":"; + existingNodePath += Path.PathSeparator; } var nodePathValue = existingNodePath + Path.Combine(projectPath, "node_modules"); From 0d0d25b032f9f5939f9361e83f8a17fd3cc4c9fb Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 15 Aug 2016 14:40:38 -0700 Subject: [PATCH 020/791] In WebpackDevMiddleware, allow configuration of ProjectPath (implements #262) --- .../Webpack/WebpackDevMiddleware.cs | 16 +++++++++++++--- .../Webpack/WebpackDevMiddlewareOptions.cs | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index a9398d72..89d6950e 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -35,15 +35,25 @@ public static void UseWebpackDevMiddleware( "To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement."); } + string projectPath; + if (options.ProjectPath == null) + { + var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + projectPath = hostEnv.ContentRootPath; + } + else + { + projectPath = options.ProjectPath; + } + // Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it // use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance // because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack // middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't // as fast as some theoretical future alternative. - var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { - ProjectPath = hostEnv.ContentRootPath, + ProjectPath = projectPath, WatchFileExtensions = new string[] { } // Don't watch anything }); @@ -55,7 +65,7 @@ public static void UseWebpackDevMiddleware( // Tell Node to start the server hosting webpack-dev-middleware var devServerOptions = new { - webpackConfigPath = Path.Combine(hostEnv.ContentRootPath, options.ConfigFile ?? DefaultConfigFile), + webpackConfigPath = Path.Combine(projectPath, options.ConfigFile ?? DefaultConfigFile), suppliedOptions = options }; var devServerInfo = diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs index fcdde442..ebfe36df 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddlewareOptions.cs @@ -6,5 +6,6 @@ public class WebpackDevMiddlewareOptions public int HotModuleReplacementServerPort { get; set; } public bool ReactHotModuleReplacement { get; set; } public string ConfigFile { get; set; } + public string ProjectPath { get; set; } } } \ No newline at end of file From c53bd8f8f613dd90b57eac5db7303589e8635132 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 16 Aug 2016 11:44:55 -0700 Subject: [PATCH 021/791] Prerenderer now passes original (unescaped) URL to Node - fixes #250 --- .../Prerendering/PrerenderTagHelper.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index e0dcd293..e773bbb0 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -2,13 +2,11 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; namespace Microsoft.AspNetCore.SpaServices.Prerendering @@ -60,7 +58,19 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { + // We want to pass the original, unencoded incoming URL data through to Node, so that + // server-side code has the same view of the URL as client-side code (on the client, + // location.pathname returns an unencoded string). + // The following logic handles special characters in URL paths in the same way that + // Node and client-side JS does. For example, the path "/a=b%20c" gets passed through + // unchanged (whereas other .NET APIs do change it - Path.Value will return it as + // "/a=b c" and Path.ToString() will return it as "/a%3db%20c") + var requestFeature = ViewContext.HttpContext.Features.Get(); + var unencodedPathAndQuery = requestFeature.RawTarget; + var request = ViewContext.HttpContext.Request; + var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}"; + var result = await Prerenderer.RenderToString( _applicationBasePath, _nodeServices, @@ -69,8 +79,8 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu ExportName = ExportName, WebpackConfig = WebpackConfigPath }, - request.GetEncodedUrl(), - request.Path + request.QueryString.Value, + unencodedAbsoluteUrl, + unencodedPathAndQuery, CustomDataParameter); output.Content.SetHtmlContent(result.Html); From 56cb898bdeac5fb897b92e38ad708e6d01e79543 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 16 Aug 2016 13:57:37 -0700 Subject: [PATCH 022/791] Rename PrimeCache to PrimeCacheAsync (keeping older name as obsolete overload). Fixes #246. --- samples/angular/MusicStore/Views/Home/Index.cshtml | 4 ++-- .../PrimeCacheHelper.cs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/samples/angular/MusicStore/Views/Home/Index.cshtml b/samples/angular/MusicStore/Views/Home/Index.cshtml index 9ac9f01a..a2799f70 100755 --- a/samples/angular/MusicStore/Views/Home/Index.cshtml +++ b/samples/angular/MusicStore/Views/Home/Index.cshtml @@ -4,8 +4,8 @@ Loading... - @await Html.PrimeCache(Url.Action("GenreMenuList", "GenresApi")) - @await Html.PrimeCache(Url.Action("MostPopular", "AlbumsApi")) + @await Html.PrimeCacheAsync(Url.Action("GenreMenuList", "GenresApi")) + @await Html.PrimeCacheAsync(Url.Action("MostPopular", "AlbumsApi")) @section scripts { diff --git a/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs b/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs index 1b016622..388ab069 100644 --- a/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs +++ b/src/Microsoft.AspNetCore.AngularServices/PrimeCacheHelper.cs @@ -11,9 +11,15 @@ namespace Microsoft.AspNetCore.AngularServices { public static class PrimeCacheHelper { - public static async Task PrimeCache(this IHtmlHelper html, string url) + [Obsolete("Use PrimeCacheAsync instead")] + public static Task PrimeCache(this IHtmlHelper html, string url) { - // TODO: Consider deduplicating the PrimeCache calls (that is, if there are multiple requests to precache + return PrimeCacheAsync(html, url); + } + + public static async Task PrimeCacheAsync(this IHtmlHelper html, string url) + { + // TODO: Consider deduplicating the PrimeCacheAsync calls (that is, if there are multiple requests to precache // the same URL, only return nonempty for one of them). This will make it easier to auto-prime-cache any // HTTP requests made during server-side rendering, without risking unnecessary duplicate requests. From 098159998d3d56aa7f255a423558e3bc75cee134 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 16 Aug 2016 16:26:07 -0700 Subject: [PATCH 023/791] Add ability to configure environment variables for Node instances, plus auto-populate NODE_ENV based on IHostingEnvironment when possible. Fixes #230 --- .../Configuration/Configuration.cs | 9 +++-- .../Configuration/NodeServicesOptions.cs | 18 ++++++++++ .../HostingModels/HttpNodeInstance.cs | 4 ++- .../HostingModels/OutOfProcessNodeInstance.cs | 34 +++++++++++++++---- .../HostingModels/SocketNodeInstance.cs | 5 ++- .../Prerendering/PrerenderTagHelper.cs | 2 +- .../Webpack/WebpackDevMiddleware.cs | 14 ++------ 7 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs index 6ecd43fb..bd76dc8d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs @@ -20,12 +20,15 @@ public static void AddNodeServices(this IServiceCollection serviceCollection, No { // Since this instance is being created through DI, we can access the IHostingEnvironment // to populate options.ProjectPath if it wasn't explicitly specified. + var hostEnv = serviceProvider.GetRequiredService(); if (string.IsNullOrEmpty(options.ProjectPath)) { - var hostEnv = serviceProvider.GetRequiredService(); options.ProjectPath = hostEnv.ContentRootPath; } + // Similarly, we can determine the 'is development' value from the hosting environment + options.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment()); + // Likewise, if no logger was specified explicitly, we should use the one from DI. // If it doesn't provide one, CreateNodeInstance will set up a default. if (options.NodeInstanceOutputLogger == null) @@ -69,11 +72,11 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options) { case NodeHostingModel.Http: return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, logger, - options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); + options.EnvironmentVariables, 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, logger, - options.LaunchWithDebugging, options.DebuggingPort); + options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort); default: throw new ArgumentException("Unknown hosting model: " + options.HostingModel); } diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index 5e6f5181..4ff981ec 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.Extensions.Logging; @@ -22,6 +23,23 @@ public NodeServicesOptions() public string[] WatchFileExtensions { get; set; } public ILogger NodeInstanceOutputLogger { get; set; } public bool LaunchWithDebugging { get; set; } + public IDictionary EnvironmentVariables { get; set; } public int? DebuggingPort { get; set; } + + public NodeServicesOptions AddDefaultEnvironmentVariables(bool isDevelopmentMode) + { + if (EnvironmentVariables == null) + { + EnvironmentVariables = new Dictionary(); + } + + if (!EnvironmentVariables.ContainsKey("NODE_ENV")) + { + // These strings are a de-facto standard in Node + EnvironmentVariables["NODE_ENV"] = isDevelopmentMode ? "development" : "production"; + } + + return this; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 0e828ea7..9400d6e1 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text; @@ -34,7 +35,7 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private int _portNumber; public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, - bool launchWithDebugging, int? debuggingPort, int port = 0) + IDictionary environmentVars, bool launchWithDebugging, int? debuggingPort, int port = 0) : base( EmbeddedResourceReader.Read( typeof(HttpNodeInstance), @@ -43,6 +44,7 @@ public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogge watchFileExtensions, MakeCommandLineOptions(port), nodeInstanceOutputLogger, + environmentVars, launchWithDebugging, debuggingPort) { diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 3c9cfd51..d9ede585 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -46,6 +47,7 @@ public OutOfProcessNodeInstance( string[] watchFileExtensions, string commandLineArguments, ILogger nodeOutputLogger, + IDictionary environmentVars, bool launchWithDebugging, int? debuggingPort) { @@ -58,7 +60,7 @@ public OutOfProcessNodeInstance( _entryPointScript = new StringAsTempFile(entryPointScript); var startInfo = PrepareNodeProcessStartInfo(_entryPointScript.FileName, projectPath, commandLineArguments, - launchWithDebugging, debuggingPort); + environmentVars, launchWithDebugging, debuggingPort); _nodeProcess = LaunchNodeProcess(startInfo); _watchFileExtensions = watchFileExtensions; _fileSystemWatcher = BeginFileWatcher(projectPath); @@ -99,7 +101,7 @@ public void Dispose() // This method is virtual, as it provides a way to override the NODE_PATH or the path to node.exe protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( string entryPointFilename, string projectPath, string commandLineArguments, - bool launchWithDebugging, int? debuggingPort) + IDictionary environmentVars, bool launchWithDebugging, int? debuggingPort) { string debuggingArgs; if (launchWithDebugging) @@ -122,6 +124,19 @@ protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( WorkingDirectory = projectPath }; + // Append environment vars + if (environmentVars != null) + { + foreach (var envVarKey in environmentVars.Keys) + { + var envVarValue = environmentVars[envVarKey]; + if (envVarValue != null) + { + SetEnvironmentVariable(startInfo, envVarKey, envVarValue); + } + } + } + // Append projectPath to NODE_PATH so it can locate node_modules var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty; if (existingNodePath != string.Empty) @@ -130,11 +145,7 @@ protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( } var nodePathValue = existingNodePath + Path.Combine(projectPath, "node_modules"); -#if NET451 - startInfo.EnvironmentVariables["NODE_PATH"] = nodePathValue; -#else - startInfo.Environment["NODE_PATH"] = nodePathValue; -#endif + SetEnvironmentVariable(startInfo, "NODE_PATH", nodePathValue); return startInfo; } @@ -179,6 +190,15 @@ private void EnsureFileSystemWatcherIsDisposed() } } + private static void SetEnvironmentVariable(ProcessStartInfo startInfo, string name, string value) + { +#if NET451 + startInfo.EnvironmentVariables[name] = value; +#else + startInfo.Environment[name] = value; +#endif + } + private static Process LaunchNodeProcess(ProcessStartInfo startInfo) { var process = Process.Start(startInfo); diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 5f3a6ec3..46115f69 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -38,7 +39,8 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance private VirtualConnectionClient _virtualConnectionClient; public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, - ILogger nodeInstanceOutputLogger, bool launchWithDebugging, int? debuggingPort) + ILogger nodeInstanceOutputLogger, IDictionary environmentVars, + bool launchWithDebugging, int? debuggingPort) : base( EmbeddedResourceReader.Read( typeof(SocketNodeInstance), @@ -47,6 +49,7 @@ public SocketNodeInstance(string projectPath, string[] watchFileExtensions, stri watchFileExtensions, MakeNewCommandLineOptions(socketAddress), nodeInstanceOutputLogger, + environmentVars, launchWithDebugging, debuggingPort) { diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index e773bbb0..1baaa601 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -36,7 +36,7 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) _nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { ProjectPath = _applicationBasePath - }); + }.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment())); } } diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index 89d6950e..5a8c06fd 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -35,16 +35,8 @@ public static void UseWebpackDevMiddleware( "To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement."); } - string projectPath; - if (options.ProjectPath == null) - { - var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - projectPath = hostEnv.ContentRootPath; - } - else - { - projectPath = options.ProjectPath; - } + var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath; // Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it // use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance @@ -55,7 +47,7 @@ public static void UseWebpackDevMiddleware( { ProjectPath = projectPath, WatchFileExtensions = new string[] { } // Don't watch anything - }); + }.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment())); // Get a filename matching the middleware Node script var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware), From 101902694392a6db9a957e1c0d61a2b8733c040b Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 10:48:13 -0700 Subject: [PATCH 024/791] Build NuGet package containing dotnetnew templates --- templates/yeoman/.gitignore | 2 +- templates/yeoman/src/build/build.ts | 127 +++++++++++++----- .../Microsoft.AspNetCore.Spa.Templates.nuspec | 19 +++ templates/yeoman/tsconfig.json | 2 +- 4 files changed, 115 insertions(+), 35 deletions(-) create mode 100644 templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec diff --git a/templates/yeoman/.gitignore b/templates/yeoman/.gitignore index 33254eb3..f0cef27c 100644 --- a/templates/yeoman/.gitignore +++ b/templates/yeoman/.gitignore @@ -1,3 +1,3 @@ /node_modules/ /built/ -/generator-aspnetcore-spa/ +/dist/ diff --git a/templates/yeoman/src/build/build.ts b/templates/yeoman/src/build/build.ts index ce071005..72f0f37e 100644 --- a/templates/yeoman/src/build/build.ts +++ b/templates/yeoman/src/build/build.ts @@ -5,28 +5,19 @@ import * as path from 'path'; import * as _ from 'lodash'; import * as mkdirp from 'mkdirp'; import * as rimraf from 'rimraf'; +import * as childProcess from 'child_process'; -const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.ts', '.tsx', '.xproj']; +const isWindows = /^win/.test(process.platform); +const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj']; +const yeomanGeneratorSource = './src/generator'; -const templates = { - 'angular-2': '../../templates/Angular2Spa/', - 'knockout': '../../templates/KnockoutSpa/', - 'react-redux': '../../templates/ReactReduxSpa/', - 'react': '../../templates/ReactSpa/' +const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string } } = { + 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2' }, + 'knockout': { dir: '../../templates/KnockoutSpa/', dotNetNewId: 'Knockout', displayName: 'Knockout.js' }, + 'react-redux': { dir: '../../templates/ReactReduxSpa/', dotNetNewId: 'ReactRedux', displayName: 'React.js and Redux' }, + 'react': { dir: '../../templates/ReactSpa/', dotNetNewId: 'React', displayName: 'React.js' } }; -const contentReplacements: { from: RegExp, to: string }[] = [ - { from: /\bWebApplicationBasic\b/g, to: '<%= namePascalCase %>' }, - { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: '<%= projectGuid %>' }, - { from: /.*?<\/RootNamespace>/g, to: '<%= namePascalCase %>'}, - { from: /\s*/g, to: '' }, - { from: /\s*/g, to: '' }, -]; - -const filenameReplacements: { from: RegExp, to: string }[] = [ - { from: /.*\.xproj$/, to: 'tokenreplace-namePascalCase.xproj' } -]; - function isTextFile(filename: string): boolean { return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0; } @@ -49,7 +40,7 @@ function listFilesExcludingGitignored(root: string): string[] { .filter(fn => gitignoreEvaluator.accepts(fn)); } -function writeTemplate(sourceRoot: string, destRoot: string) { +function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[]) { listFilesExcludingGitignored(sourceRoot).forEach(fn => { let sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); @@ -80,20 +71,90 @@ function copyRecursive(sourceRoot: string, destRoot: string, matchGlob: string) }); } -const outputRoot = './generator-aspnetcore-spa'; -const outputTemplatesRoot = path.join(outputRoot, 'app/templates'); -rimraf.sync(outputTemplatesRoot); +function buildYeomanNpmPackage() { + const outputRoot = './dist/generator-aspnetcore-spa'; + const outputTemplatesRoot = path.join(outputRoot, 'app/templates'); + rimraf.sync(outputTemplatesRoot); + + // Copy template files + const filenameReplacements = [ + { from: /.*\.xproj$/, to: 'tokenreplace-namePascalCase.xproj' } + ]; + const contentReplacements = [ + { from: /\bWebApplicationBasic\b/g, to: '<%= namePascalCase %>' }, + { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: '<%= projectGuid %>' }, + { from: /.*?<\/RootNamespace>/g, to: '<%= namePascalCase %>'}, + { from: /\s*/g, to: '' }, + { from: /\s*/g, to: '' }, + ]; + _.forEach(templates, (templateConfig, templateName) => { + const outputDir = path.join(outputTemplatesRoot, templateName); + writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements); + }); + + // Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) + const tempRoot = './tmp'; + copyRecursive(path.join(tempRoot, 'generator'), outputRoot, '**/*.js'); + copyRecursive(yeomanGeneratorSource, outputRoot, '**/!(*.ts)'); -// Copy template files -_.forEach(templates, (templateRootDir, templateName) => { - const outputDir = path.join(outputTemplatesRoot, templateName); - writeTemplate(templateRootDir, outputDir); -}); + // Clean up + rimraf.sync(tempRoot); +} + +function buildDotNetNewNuGetPackage() { + const outputRoot = './dist/dotnetnew'; + rimraf.sync(outputRoot); + + // Copy template files + const sourceProjectName = 'WebApplicationBasic'; + const projectGuid = '00000000-0000-0000-0000-000000000000'; + const filenameReplacements = [ + { from: /.*\.xproj$/, to: `${sourceProjectName}.xproj` }, + { from: /\btemplate_gitignore$/, to: '.gitignore' } + ]; + const contentReplacements = [ + { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: `${projectGuid}` }, + { from: /.*?<\/RootNamespace>/g, to: `${sourceProjectName}`}, + { from: /\s*/g, to: '' }, + { from: /\s*/g, to: '' }, + ]; + _.forEach(templates, (templateConfig, templateName) => { + const templateOutputDir = path.join(outputRoot, 'templates', templateName); + const templateOutputProjectDir = path.join(templateOutputDir, sourceProjectName); + writeTemplate(templateConfig.dir, templateOutputProjectDir, contentReplacements, filenameReplacements); + + // Add a .netnew.json file + fs.writeFileSync(path.join(templateOutputDir, '.netnew.json'), JSON.stringify({ + author: 'Microsoft', + classifications: [ 'Standard>>Quick Starts' ], + name: `ASP.NET Core SPA with ${templateConfig.displayName}`, + groupIdentity: `Microsoft.AspNetCore.Spa.${templateConfig.dotNetNewId}`, + identity: `Microsoft.AspNetCore.Spa.${templateConfig.dotNetNewId}`, + shortName: `aspnetcorespa-${templateConfig.dotNetNewId.toLowerCase()}`, + tags: { language: 'C#' }, + guids: [ projectGuid ], + sourceName: sourceProjectName + }, null, 2)); + }); -// Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) -const tempRoot = './tmp'; -copyRecursive(path.join(tempRoot, 'generator'), outputRoot, '**/*.js'); -copyRecursive('./src/generator', outputRoot, '**/!(*.ts)'); + // Invoke NuGet to create the final package + const yeomanPackageVersion = JSON.parse(fs.readFileSync(path.join(yeomanGeneratorSource, 'package.json'), 'utf8')).version; + writeTemplate('./src/dotnetnew', outputRoot, [ + { from: /\{version\}/g, to: yeomanPackageVersion }, + ], []); + const nugetExe = path.join(process.cwd(), './bin/NuGet.exe'); + const nugetStartInfo = { cwd: outputRoot, stdio: 'inherit' }; + if (isWindows) { + // Invoke NuGet.exe directly + childProcess.spawnSync(nugetExe, ['pack'], nugetStartInfo); + } else { + // Invoke via Mono (relying on that being available) + childProcess.spawnSync('mono', [nugetExe, 'pack'], nugetStartInfo); + } + + // Clean up + rimraf.sync('./tmp'); +} -// Clean up -rimraf.sync(tempRoot); +buildYeomanNpmPackage(); +buildDotNetNewNuGetPackage(); diff --git a/templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec b/templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec new file mode 100644 index 00000000..46c00114 --- /dev/null +++ b/templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec @@ -0,0 +1,19 @@ + + + + Microsoft.AspNetCore.Spa.Templates + {version} + Class Library and Console Application Templates for .NET Core + Microsoft + false + My package description. + + + + + + + + + + \ No newline at end of file diff --git a/templates/yeoman/tsconfig.json b/templates/yeoman/tsconfig.json index 107dfae8..b3a87590 100644 --- a/templates/yeoman/tsconfig.json +++ b/templates/yeoman/tsconfig.json @@ -7,6 +7,6 @@ }, "exclude": [ "node_modules", - "generator-aspnetcore-spa" + "dist" ] } From d928ef4f123c343716f9fb631c8bbf0d485287f9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 10:58:04 -0700 Subject: [PATCH 025/791] Rename folders since the template package generator now creates a package for "dotnet new" as well as Yeoman --- templates/{yeoman => package-builder}/.gitignore | 0 templates/{yeoman => package-builder}/README.md | 0 templates/{yeoman => package-builder}/package.json | 2 +- templates/{yeoman => package-builder}/src/build/build.ts | 4 ++-- .../src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec | 0 .../src/generator => package-builder/src/yeoman}/.gitignore | 0 .../src/generator => package-builder/src/yeoman}/app/index.ts | 0 .../src/generator => package-builder/src/yeoman}/package.json | 0 templates/{yeoman => package-builder}/tsconfig.json | 0 templates/{yeoman => package-builder}/tsd.json | 0 .../typings/gitignore-parser/gitignore-parser.d.ts | 0 templates/{yeoman => package-builder}/typings/glob/glob.d.ts | 0 .../{yeoman => package-builder}/typings/lodash/lodash.d.ts | 0 .../typings/minimatch/minimatch.d.ts | 0 .../{yeoman => package-builder}/typings/mkdirp/mkdirp.d.ts | 0 .../typings/node-uuid/node-uuid-base.d.ts | 0 .../typings/node-uuid/node-uuid-cjs.d.ts | 0 .../typings/node-uuid/node-uuid.d.ts | 0 templates/{yeoman => package-builder}/typings/node/node.d.ts | 0 .../{yeoman => package-builder}/typings/rimraf/rimraf.d.ts | 0 templates/{yeoman => package-builder}/typings/tsd.d.ts | 0 .../typings/yeoman-generator/yeoman-generator.d.ts | 0 22 files changed, 3 insertions(+), 3 deletions(-) rename templates/{yeoman => package-builder}/.gitignore (100%) rename templates/{yeoman => package-builder}/README.md (100%) rename templates/{yeoman => package-builder}/package.json (82%) rename templates/{yeoman => package-builder}/src/build/build.ts (98%) rename templates/{yeoman => package-builder}/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec (100%) rename templates/{yeoman/src/generator => package-builder/src/yeoman}/.gitignore (100%) rename templates/{yeoman/src/generator => package-builder/src/yeoman}/app/index.ts (100%) rename templates/{yeoman/src/generator => package-builder/src/yeoman}/package.json (100%) rename templates/{yeoman => package-builder}/tsconfig.json (100%) rename templates/{yeoman => package-builder}/tsd.json (100%) rename templates/{yeoman => package-builder}/typings/gitignore-parser/gitignore-parser.d.ts (100%) rename templates/{yeoman => package-builder}/typings/glob/glob.d.ts (100%) rename templates/{yeoman => package-builder}/typings/lodash/lodash.d.ts (100%) rename templates/{yeoman => package-builder}/typings/minimatch/minimatch.d.ts (100%) rename templates/{yeoman => package-builder}/typings/mkdirp/mkdirp.d.ts (100%) rename templates/{yeoman => package-builder}/typings/node-uuid/node-uuid-base.d.ts (100%) rename templates/{yeoman => package-builder}/typings/node-uuid/node-uuid-cjs.d.ts (100%) rename templates/{yeoman => package-builder}/typings/node-uuid/node-uuid.d.ts (100%) rename templates/{yeoman => package-builder}/typings/node/node.d.ts (100%) rename templates/{yeoman => package-builder}/typings/rimraf/rimraf.d.ts (100%) rename templates/{yeoman => package-builder}/typings/tsd.d.ts (100%) rename templates/{yeoman => package-builder}/typings/yeoman-generator/yeoman-generator.d.ts (100%) diff --git a/templates/yeoman/.gitignore b/templates/package-builder/.gitignore similarity index 100% rename from templates/yeoman/.gitignore rename to templates/package-builder/.gitignore diff --git a/templates/yeoman/README.md b/templates/package-builder/README.md similarity index 100% rename from templates/yeoman/README.md rename to templates/package-builder/README.md diff --git a/templates/yeoman/package.json b/templates/package-builder/package.json similarity index 82% rename from templates/yeoman/package.json rename to templates/package-builder/package.json index e97d33f6..b61181b6 100644 --- a/templates/yeoman/package.json +++ b/templates/package-builder/package.json @@ -1,7 +1,7 @@ { "name": "generator-aspnetcore-spa-generator", "version": "1.0.0", - "description": "Creates the Yeoman generator for ASP.NET Core SPA templates", + "description": "Creates the Yeoman generator and 'dotnet new' package for ASP.NET Core SPA templates", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/templates/yeoman/src/build/build.ts b/templates/package-builder/src/build/build.ts similarity index 98% rename from templates/yeoman/src/build/build.ts rename to templates/package-builder/src/build/build.ts index 72f0f37e..546ff891 100644 --- a/templates/yeoman/src/build/build.ts +++ b/templates/package-builder/src/build/build.ts @@ -9,7 +9,7 @@ import * as childProcess from 'child_process'; const isWindows = /^win/.test(process.platform); const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj']; -const yeomanGeneratorSource = './src/generator'; +const yeomanGeneratorSource = './src/yeoman'; const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string } } = { 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2' }, @@ -94,7 +94,7 @@ function buildYeomanNpmPackage() { // Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) const tempRoot = './tmp'; - copyRecursive(path.join(tempRoot, 'generator'), outputRoot, '**/*.js'); + copyRecursive(path.join(tempRoot, 'yeoman'), outputRoot, '**/*.js'); copyRecursive(yeomanGeneratorSource, outputRoot, '**/!(*.ts)'); // Clean up diff --git a/templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec b/templates/package-builder/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec similarity index 100% rename from templates/yeoman/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec rename to templates/package-builder/src/dotnetnew/Microsoft.AspNetCore.Spa.Templates.nuspec diff --git a/templates/yeoman/src/generator/.gitignore b/templates/package-builder/src/yeoman/.gitignore similarity index 100% rename from templates/yeoman/src/generator/.gitignore rename to templates/package-builder/src/yeoman/.gitignore diff --git a/templates/yeoman/src/generator/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts similarity index 100% rename from templates/yeoman/src/generator/app/index.ts rename to templates/package-builder/src/yeoman/app/index.ts diff --git a/templates/yeoman/src/generator/package.json b/templates/package-builder/src/yeoman/package.json similarity index 100% rename from templates/yeoman/src/generator/package.json rename to templates/package-builder/src/yeoman/package.json diff --git a/templates/yeoman/tsconfig.json b/templates/package-builder/tsconfig.json similarity index 100% rename from templates/yeoman/tsconfig.json rename to templates/package-builder/tsconfig.json diff --git a/templates/yeoman/tsd.json b/templates/package-builder/tsd.json similarity index 100% rename from templates/yeoman/tsd.json rename to templates/package-builder/tsd.json diff --git a/templates/yeoman/typings/gitignore-parser/gitignore-parser.d.ts b/templates/package-builder/typings/gitignore-parser/gitignore-parser.d.ts similarity index 100% rename from templates/yeoman/typings/gitignore-parser/gitignore-parser.d.ts rename to templates/package-builder/typings/gitignore-parser/gitignore-parser.d.ts diff --git a/templates/yeoman/typings/glob/glob.d.ts b/templates/package-builder/typings/glob/glob.d.ts similarity index 100% rename from templates/yeoman/typings/glob/glob.d.ts rename to templates/package-builder/typings/glob/glob.d.ts diff --git a/templates/yeoman/typings/lodash/lodash.d.ts b/templates/package-builder/typings/lodash/lodash.d.ts similarity index 100% rename from templates/yeoman/typings/lodash/lodash.d.ts rename to templates/package-builder/typings/lodash/lodash.d.ts diff --git a/templates/yeoman/typings/minimatch/minimatch.d.ts b/templates/package-builder/typings/minimatch/minimatch.d.ts similarity index 100% rename from templates/yeoman/typings/minimatch/minimatch.d.ts rename to templates/package-builder/typings/minimatch/minimatch.d.ts diff --git a/templates/yeoman/typings/mkdirp/mkdirp.d.ts b/templates/package-builder/typings/mkdirp/mkdirp.d.ts similarity index 100% rename from templates/yeoman/typings/mkdirp/mkdirp.d.ts rename to templates/package-builder/typings/mkdirp/mkdirp.d.ts diff --git a/templates/yeoman/typings/node-uuid/node-uuid-base.d.ts b/templates/package-builder/typings/node-uuid/node-uuid-base.d.ts similarity index 100% rename from templates/yeoman/typings/node-uuid/node-uuid-base.d.ts rename to templates/package-builder/typings/node-uuid/node-uuid-base.d.ts diff --git a/templates/yeoman/typings/node-uuid/node-uuid-cjs.d.ts b/templates/package-builder/typings/node-uuid/node-uuid-cjs.d.ts similarity index 100% rename from templates/yeoman/typings/node-uuid/node-uuid-cjs.d.ts rename to templates/package-builder/typings/node-uuid/node-uuid-cjs.d.ts diff --git a/templates/yeoman/typings/node-uuid/node-uuid.d.ts b/templates/package-builder/typings/node-uuid/node-uuid.d.ts similarity index 100% rename from templates/yeoman/typings/node-uuid/node-uuid.d.ts rename to templates/package-builder/typings/node-uuid/node-uuid.d.ts diff --git a/templates/yeoman/typings/node/node.d.ts b/templates/package-builder/typings/node/node.d.ts similarity index 100% rename from templates/yeoman/typings/node/node.d.ts rename to templates/package-builder/typings/node/node.d.ts diff --git a/templates/yeoman/typings/rimraf/rimraf.d.ts b/templates/package-builder/typings/rimraf/rimraf.d.ts similarity index 100% rename from templates/yeoman/typings/rimraf/rimraf.d.ts rename to templates/package-builder/typings/rimraf/rimraf.d.ts diff --git a/templates/yeoman/typings/tsd.d.ts b/templates/package-builder/typings/tsd.d.ts similarity index 100% rename from templates/yeoman/typings/tsd.d.ts rename to templates/package-builder/typings/tsd.d.ts diff --git a/templates/yeoman/typings/yeoman-generator/yeoman-generator.d.ts b/templates/package-builder/typings/yeoman-generator/yeoman-generator.d.ts similarity index 100% rename from templates/yeoman/typings/yeoman-generator/yeoman-generator.d.ts rename to templates/package-builder/typings/yeoman-generator/yeoman-generator.d.ts From 9e714b61fe751bdfd59933ad9806f1626437b858 Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Wed, 17 Aug 2016 19:06:16 -0400 Subject: [PATCH 026/791] Bug(KnockoutSpa): Router TS issue TS erroring out (unable to build) due to `crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;` <-- left hand assignment ``` ERROR in ./ClientApp/router.ts (21,9): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property. ``` --- templates/KnockoutSpa/ClientApp/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/KnockoutSpa/ClientApp/router.ts b/templates/KnockoutSpa/ClientApp/router.ts index 526394e6..12e65c1b 100644 --- a/templates/KnockoutSpa/ClientApp/router.ts +++ b/templates/KnockoutSpa/ClientApp/router.ts @@ -1,5 +1,5 @@ import * as ko from 'knockout'; -import * as crossroads from 'crossroads'; +var crossroads = require('crossroads'); // This module configures crossroads.js, a routing library. If you prefer, you // can use any other routing library (or none at all) as Knockout is designed to From 89034b59c517794968c79b647b3566bb2293af72 Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Thu, 18 Aug 2016 15:16:13 -0400 Subject: [PATCH 027/791] Change to mix import/require style --- templates/KnockoutSpa/ClientApp/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/KnockoutSpa/ClientApp/router.ts b/templates/KnockoutSpa/ClientApp/router.ts index 12e65c1b..46000e78 100644 --- a/templates/KnockoutSpa/ClientApp/router.ts +++ b/templates/KnockoutSpa/ClientApp/router.ts @@ -1,5 +1,5 @@ import * as ko from 'knockout'; -var crossroads = require('crossroads'); +import crossroads = require('crossroads'); // This module configures crossroads.js, a routing library. If you prefer, you // can use any other routing library (or none at all) as Knockout is designed to From 6d8767d14158734e26fd3432cc279583b798d90d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 13:49:20 -0700 Subject: [PATCH 028/791] Tweak WebpackDevMiddleware.cs to avoid unnecessary Task.Yield() --- .../Webpack/WebpackDevMiddleware.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index 5a8c06fd..1a0c9bb5 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -81,12 +81,12 @@ public static void UseWebpackDevMiddleware( // 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 => async ctx => + builder.Use(next => ctx => { var hostname = ctx.Request.Host.Host; ctx.Response.Redirect( $"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); - await Task.Yield(); + return Task.FromResult(0); }); }); } From 0ad048393569abb126e88e2722bb45d24de1cd24 Mon Sep 17 00:00:00 2001 From: Geir Sagberg Date: Fri, 12 Aug 2016 11:42:19 +0200 Subject: [PATCH 029/791] Update aspnet-webpack-react to React 15 ^15.0.0 is enough Update ReactGrid example to work with newest Griddle version Several fixes to stop build.sh from failing --- .gitignore | 1 + .../ReactApp/components/PeopleGrid.jsx | 4 +- samples/react/ReactGrid/package.json | 54 +++++++++---------- .../npm/package.json | 3 +- .../npm/redux-typed/package.json | 3 +- .../npm/redux-typed/src/StrongActions.ts | 2 +- .../npm/aspnet-prerendering/package.json | 3 +- .../npm/aspnet-webpack-react/package.json | 5 +- .../npm/aspnet-webpack/package.json | 4 +- .../npm/domain-task/package.json | 3 +- 10 files changed, 45 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 62ebab8f..06cc11a3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ npm-debug.log # repo have to be excluded here. /templates/*/node_modules/ /templates/*/wwwroot/dist/ +.vscode/ diff --git a/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx index a9dbec1c..e3cd3fcf 100644 --- a/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx +++ b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx @@ -5,6 +5,8 @@ import { fakeData } from '../data/fakeData.js'; import { columnMeta } from '../data/columnMeta.jsx'; const resultsPerPage = 10; +const fakeDataWithAction = fakeData.map(data => Object.assign(data, {actions: ''})); + export class PeopleGrid extends React.Component { render() { var pageIndex = this.props.params ? (this.props.params.pageIndex || 1) - 1 : 0; @@ -12,7 +14,7 @@ export class PeopleGrid extends React.Component {

People

- x.columnName)} columnMetadata={columnMeta} resultsPerPage={resultsPerPage} diff --git a/samples/react/ReactGrid/package.json b/samples/react/ReactGrid/package.json index 123cc473..9329a550 100644 --- a/samples/react/ReactGrid/package.json +++ b/samples/react/ReactGrid/package.json @@ -2,38 +2,38 @@ "name": "ReactExample", "version": "0.0.0", "dependencies": { - "babel-core": "^6.4.5", - "bootstrap": "^3.3.5", - "domain-task": "^2.0.0", - "formsy-react": "^0.17.0", - "formsy-react-components": "^0.6.3", - "griddle-react": "^0.3.1", - "history": "^1.12.6", + "babel-core": "^6.13.2", + "bootstrap": "^3.3.7", + "domain-task": "^2.0.1", + "formsy-react": "^0.18.1", + "formsy-react-components": "^0.8.1", + "griddle-react": "^0.6.1", + "history": "^3.0.0", "memory-fs": "^0.3.0", - "react": "^0.14.7", - "react-dom": "^0.14.7", - "react-router": "^2.0.0-rc5", - "require-from-string": "^1.1.0", + "react": "^15.3.0", + "react-dom": "^15.3.0", + "react-router": "^2.6.1", + "require-from-string": "^1.2.0", "underscore": "^1.8.3", "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { - "aspnet-prerendering": "^1.0.0", - "aspnet-webpack": "^1.0.3", + "aspnet-prerendering": "^1.0.4", + "aspnet-webpack": "^1.0.9", "aspnet-webpack-react": "^1.0.1", - "babel-loader": "^6.2.1", - "babel-plugin-react-transform": "^2.0.0", - "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "css-loader": "^0.21.0", - "express": "^4.13.4", - "extract-text-webpack-plugin": "^0.8.2", - "file-loader": "^0.8.4", - "react-transform-hmr": "^1.0.1", - "style-loader": "^0.13.0", - "url-loader": "^0.5.6", - "webpack": "^1.12.2", - "webpack-dev-middleware": "^1.5.1", - "webpack-hot-middleware": "^2.6.4" + "babel-loader": "^6.2.4", + "babel-plugin-react-transform": "^2.0.2", + "babel-preset-es2015": "^6.13.2", + "babel-preset-react": "^6.11.1", + "css-loader": "^0.23.1", + "express": "^4.14.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.9.0", + "react-transform-hmr": "^1.0.4", + "style-loader": "^0.13.1", + "url-loader": "^0.5.7", + "webpack": "^1.13.1", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.12.2" } } diff --git a/src/Microsoft.AspNetCore.AngularServices/npm/package.json b/src/Microsoft.AspNetCore.AngularServices/npm/package.json index 8f7c19ed..10c3f776 100644 --- a/src/Microsoft.AspNetCore.AngularServices/npm/package.json +++ b/src/Microsoft.AspNetCore.AngularServices/npm/package.json @@ -5,7 +5,7 @@ "main": "./dist/Exports", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "prepublish": "tsd install && tsc && node build.js" + "prepublish": "rimraf *.d.ts dist/*.d.ts && tsd install && tsc && node build.js" }, "typings": "dist/Exports", "author": "Microsoft", @@ -17,6 +17,7 @@ "devDependencies": { "es6-shim": "^0.35.0", "reflect-metadata": "^0.1.2", + "rimraf": "^2.5.4", "systemjs-builder": "^0.14.11", "typescript": "^1.8.10", "zone.js": "^0.6.10" diff --git a/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/package.json b/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/package.json index 1baee3ac..b7962420 100644 --- a/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/package.json +++ b/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/package.json @@ -5,12 +5,13 @@ "main": "main.js", "typings": "main.d.ts", "scripts": { - "prepublish": "tsd update && tsc && echo 'Finished building NPM package \"redux-typed\"'", + "prepublish": "rimraf *.d.ts && tsd update && tsc && echo 'Finished building NPM package \"redux-typed\"'", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Microsoft", "license": "Apache-2.0", "devDependencies": { + "rimraf": "^2.5.4", "typescript": "^1.8.10" } } diff --git a/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/src/StrongActions.ts b/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/src/StrongActions.ts index 55893bf7..f14c9121 100644 --- a/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/src/StrongActions.ts +++ b/src/Microsoft.AspNetCore.ReactServices/npm/redux-typed/src/StrongActions.ts @@ -38,5 +38,5 @@ export interface Reducer extends Function { } export interface ActionCreatorGeneric extends Function { - (dispatch: Dispatch, getState: () => TState): any; + (dispatch: Dispatch, getState: () => TState): any; } diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index 11bd4ecb..156a6f1e 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -4,7 +4,7 @@ "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { - "prepublish": "tsd update && tsc && echo 'Finished building NPM package \"aspnet-prerendering\"'", + "prepublish": "rimraf *.d.ts && tsd update && tsc && echo 'Finished building NPM package \"aspnet-prerendering\"'", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Microsoft", @@ -14,6 +14,7 @@ "es6-promise": "^3.1.2" }, "devDependencies": { + "rimraf": "^2.5.4", "typescript": "^1.8.10" } } diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json index 419c0586..3b9fd5e2 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json @@ -4,7 +4,7 @@ "description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { - "prepublish": "tsd update && tsc && echo 'Finished building NPM package \"aspnet-webpack-react\"'", + "prepublish": "rimraf *.d.ts && tsd update && tsc && echo 'Finished building NPM package \"aspnet-webpack-react\"'", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Microsoft", @@ -15,11 +15,12 @@ "babel-plugin-react-transform": "^2.0.2", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", - "react": "^0.14.7", + "react": "^15.0.0", "react-transform-hmr": "^1.0.4", "webpack": "^1.12.14" }, "devDependencies": { + "rimraf": "^2.5.4", "typescript": "^1.8.10" } } diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 8980b92f..327d65d5 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -19,7 +19,7 @@ "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { - "typescript": "^1.8.10", - "rimraf": "^2.5.2" + "rimraf": "^2.5.4", + "typescript": "^1.8.10" } } diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/domain-task/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/domain-task/package.json index 0bceb713..a55fcd81 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/domain-task/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/domain-task/package.json @@ -4,7 +4,7 @@ "description": "Tracks outstanding operations for a logical thread of execution", "main": "index.js", "scripts": { - "prepublish": "tsd update && tsc && echo 'Finished building NPM package \"domain-task\"'", + "prepublish": "rimraf *.d.ts && tsd update && tsc && echo 'Finished building NPM package \"domain-task\"'", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Microsoft", @@ -14,6 +14,7 @@ "isomorphic-fetch": "^2.2.1" }, "devDependencies": { + "rimraf": "^2.5.4", "typescript": "^1.8.10" } } From 0a0afed84b904045088437177952818bde22c49d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 13:57:49 -0700 Subject: [PATCH 030/791] Add comment about why the 'actions' property is being patched on --- samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx index e3cd3fcf..6ce0ade6 100644 --- a/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx +++ b/samples/react/ReactGrid/ReactApp/components/PeopleGrid.jsx @@ -5,7 +5,9 @@ import { fakeData } from '../data/fakeData.js'; import { columnMeta } from '../data/columnMeta.jsx'; const resultsPerPage = 10; -const fakeDataWithAction = fakeData.map(data => Object.assign(data, {actions: ''})); +// Griddle requires each row to have a property matching each column, even if you're not displaying +// any data from the row in that column +fakeData.forEach(row => { row.actions = ''; }); export class PeopleGrid extends React.Component { render() { @@ -14,7 +16,7 @@ export class PeopleGrid extends React.Component {

People

- x.columnName)} columnMetadata={columnMeta} resultsPerPage={resultsPerPage} From a4e3360e65361c89f5fc7b76b11459b810d54f2f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 14:07:11 -0700 Subject: [PATCH 031/791] Bump aspnet-webpack-react package version --- .../npm/aspnet-webpack-react/package.json | 2 +- templates/ReactReduxSpa/package.json | 2 +- templates/ReactSpa/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json index 3b9fd5e2..1f0a94ea 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack-react", - "version": "1.0.1", + "version": "1.0.2", "description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index a1d53df4..82fb4918 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "devDependencies": { "aspnet-webpack": "^1.0.6", - "aspnet-webpack-react": "^1.0.1", + "aspnet-webpack-react": "^1.0.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json index 9776cdb4..a66c99db 100644 --- a/templates/ReactSpa/package.json +++ b/templates/ReactSpa/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "devDependencies": { "aspnet-webpack": "^1.0.6", - "aspnet-webpack-react": "^1.0.0", + "aspnet-webpack-react": "^1.0.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", From 22da55a47310384aaf926f31ec342b248b7a568f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 18 Aug 2016 14:07:53 -0700 Subject: [PATCH 032/791] Bump generator-aspnetcore-spa package version --- templates/package-builder/src/yeoman/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 839ed750..82025189 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.3", + "version": "0.2.4", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 5bc47aacd8484cc43d4e8a861b9e60b7dc7582dc Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 19 Aug 2016 01:08:09 +0100 Subject: [PATCH 033/791] Tweaks to .xproj files made automatically by VS --- samples/misc/Webpack/Webpack.xproj | 4 ++-- samples/react/MusicStore/MusicStore.xproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/misc/Webpack/Webpack.xproj b/samples/misc/Webpack/Webpack.xproj index 5f9cc8f9..49bc6e47 100644 --- a/samples/misc/Webpack/Webpack.xproj +++ b/samples/misc/Webpack/Webpack.xproj @@ -10,11 +10,11 @@ a8905301-8492-42fd-9e83-f715a0fdc3a2 Webpack ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) - ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\ + .\bin\ 2.0 2018 - + \ No newline at end of file diff --git a/samples/react/MusicStore/MusicStore.xproj b/samples/react/MusicStore/MusicStore.xproj index 3096e79c..672fd3fc 100644 --- a/samples/react/MusicStore/MusicStore.xproj +++ b/samples/react/MusicStore/MusicStore.xproj @@ -10,11 +10,11 @@ c870a92c-9e3f-4bf2-82b8-5758545a8b7c MusicStore ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) - ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\ + .\bin\ 2.0 2018 - + \ No newline at end of file From 48eb2b7a05e861f4281b05e83ce62774bd0d74f3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 19 Aug 2016 01:08:57 +0100 Subject: [PATCH 034/791] In KnockoutSpa, use isomorphic-fetch for IE/Edge compatibility --- .../components/fetch-data/fetch-data.ts | 1 + templates/KnockoutSpa/package.json | 1 + templates/KnockoutSpa/tsd.json | 6 +- .../isomorphic-fetch/isomorphic-fetch.d.ts | 119 ++++++++++++++++++ templates/KnockoutSpa/typings/tsd.d.ts | 2 +- .../typings/whatwg-fetch/whatwg-fetch.d.ts | 85 ------------- .../KnockoutSpa/webpack.config.vendor.js | 2 +- 7 files changed, 126 insertions(+), 90 deletions(-) create mode 100644 templates/KnockoutSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts delete mode 100644 templates/KnockoutSpa/typings/whatwg-fetch/whatwg-fetch.d.ts diff --git a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts index deab0682..3dc781c0 100644 --- a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts @@ -1,4 +1,5 @@ import * as ko from 'knockout'; +import * as fetch from 'isomorphic-fetch'; interface WeatherForecast { dateFormatted: string; diff --git a/templates/KnockoutSpa/package.json b/templates/KnockoutSpa/package.json index 9de97387..96a49bd7 100644 --- a/templates/KnockoutSpa/package.json +++ b/templates/KnockoutSpa/package.json @@ -11,6 +11,7 @@ "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", "history": "^2.0.1", + "isomorphic-fetch": "^2.2.1", "jquery": "^2.2.1", "knockout": "^3.4.0", "raw-loader": "^0.5.1", diff --git a/templates/KnockoutSpa/tsd.json b/templates/KnockoutSpa/tsd.json index 0e5592c3..cde96c4e 100644 --- a/templates/KnockoutSpa/tsd.json +++ b/templates/KnockoutSpa/tsd.json @@ -5,9 +5,6 @@ "path": "typings", "bundle": "typings/tsd.d.ts", "installed": { - "whatwg-fetch/whatwg-fetch.d.ts": { - "commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7" - }, "knockout/knockout.d.ts": { "commit": "9f0f926a12026287b5a4a229e5672c01e7549313" }, @@ -28,6 +25,9 @@ }, "js-signals/js-signals.d.ts": { "commit": "9f0f926a12026287b5a4a229e5672c01e7549313" + }, + "isomorphic-fetch/isomorphic-fetch.d.ts": { + "commit": "57ec5fbb76060329c10959d449eb1d4e70b15a65" } } } diff --git a/templates/KnockoutSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts b/templates/KnockoutSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts new file mode 100644 index 00000000..19754db0 --- /dev/null +++ b/templates/KnockoutSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts @@ -0,0 +1,119 @@ +// Type definitions for isomorphic-fetch +// Project: https://github.com/matthew-andrews/isomorphic-fetch +// Definitions by: Todd Lucas +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare enum RequestContext { + "audio", "beacon", "cspreport", "download", "embed", "eventsource", + "favicon", "fetch", "font", "form", "frame", "hyperlink", "iframe", + "image", "imageset", "import", "internal", "location", "manifest", + "object", "ping", "plugin", "prefetch", "script", "serviceworker", + "sharedworker", "subresource", "style", "track", "video", "worker", + "xmlhttprequest", "xslt" +} +declare enum RequestMode { "same-origin", "no-cors", "cors" } +declare enum RequestCredentials { "omit", "same-origin", "include" } +declare enum RequestCache { + "default", "no-store", "reload", "no-cache", "force-cache", + "only-if-cached" +} +declare enum ResponseType { "basic", "cors", "default", "error", "opaque" } + +declare type HeaderInit = Headers | Array; +declare type BodyInit = ArrayBuffer | ArrayBufferView | Blob | FormData | string; +declare type RequestInfo = Request | string; + +interface RequestInit { + method?: string; + headers?: HeaderInit | { [index: string]: string }; + body?: BodyInit; + mode?: string | RequestMode; + credentials?: string | RequestCredentials; + cache?: string | RequestCache; +} + +interface IHeaders { + get(name: string): string; + getAll(name: string): Array; + has(name: string): boolean; +} + +declare class Headers implements IHeaders { + append(name: string, value: string): void; + delete(name: string):void; + get(name: string): string; + getAll(name: string): Array; + has(name: string): boolean; + set(name: string, value: string): void; +} + +interface IBody { + bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + json(): Promise; + text(): Promise; +} + +declare class Body implements IBody { + bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + json(): Promise; + text(): Promise; +} + +interface IRequest extends IBody { + method: string; + url: string; + headers: Headers; + context: string | RequestContext; + referrer: string; + mode: string | RequestMode; + credentials: string | RequestCredentials; + cache: string | RequestCache; +} + +declare class Request extends Body implements IRequest { + constructor(input: string | Request, init?: RequestInit); + method: string; + url: string; + headers: Headers; + context: string | RequestContext; + referrer: string; + mode: string | RequestMode; + credentials: string | RequestCredentials; + cache: string | RequestCache; +} + +interface IResponse extends IBody { + url: string; + status: number; + statusText: string; + ok: boolean; + headers: IHeaders; + type: string | ResponseType; + size: number; + timeout: number; + redirect(url: string, status: number): IResponse; + error(): IResponse; + clone(): IResponse; +} + +interface IFetchStatic { + Promise: any; + Headers: IHeaders + Request: IRequest; + Response: IResponse; + (url: string | IRequest, init?: RequestInit): Promise; +} + +declare var fetch: IFetchStatic; + +declare module "isomorphic-fetch" { + export = fetch; +} diff --git a/templates/KnockoutSpa/typings/tsd.d.ts b/templates/KnockoutSpa/typings/tsd.d.ts index c0e628b1..6a8cecae 100644 --- a/templates/KnockoutSpa/typings/tsd.d.ts +++ b/templates/KnockoutSpa/typings/tsd.d.ts @@ -1,8 +1,8 @@ /// /// /// -/// /// /// /// /// +/// diff --git a/templates/KnockoutSpa/typings/whatwg-fetch/whatwg-fetch.d.ts b/templates/KnockoutSpa/typings/whatwg-fetch/whatwg-fetch.d.ts deleted file mode 100644 index 64dd9048..00000000 --- a/templates/KnockoutSpa/typings/whatwg-fetch/whatwg-fetch.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Type definitions for fetch API -// Project: https://github.com/github/fetch -// Definitions by: Ryan Graham -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -declare class Request extends Body { - constructor(input: string|Request, init?:RequestInit); - method: string; - url: string; - headers: Headers; - context: string|RequestContext; - referrer: string; - mode: string|RequestMode; - credentials: string|RequestCredentials; - cache: string|RequestCache; -} - -interface RequestInit { - method?: string; - headers?: HeaderInit|{ [index: string]: string }; - body?: BodyInit; - mode?: string|RequestMode; - credentials?: string|RequestCredentials; - cache?: string|RequestCache; -} - -declare enum RequestContext { - "audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch", - "font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import", - "internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script", - "serviceworker", "sharedworker", "subresource", "style", "track", "video", "worker", - "xmlhttprequest", "xslt" -} -declare enum RequestMode { "same-origin", "no-cors", "cors" } -declare enum RequestCredentials { "omit", "same-origin", "include" } -declare enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" } - -declare class Headers { - append(name: string, value: string): void; - delete(name: string):void; - get(name: string): string; - getAll(name: string): Array; - has(name: string): boolean; - set(name: string, value: string): void; -} - -declare class Body { - bodyUsed: boolean; - arrayBuffer(): Promise; - blob(): Promise; - formData(): Promise; - json(): Promise; - json(): Promise; - text(): Promise; -} -declare class Response extends Body { - constructor(body?: BodyInit, init?: ResponseInit); - error(): Response; - redirect(url: string, status: number): Response; - type: string|ResponseType; - url: string; - status: number; - ok: boolean; - statusText: string; - headers: Headers; - clone(): Response; -} - -declare enum ResponseType { "basic", "cors", "default", "error", "opaque" } - -interface ResponseInit { - status: number; - statusText?: string; - headers?: HeaderInit; -} - -declare type HeaderInit = Headers|Array; -declare type BodyInit = Blob|FormData|string; -declare type RequestInfo = Request|string; - -interface Window { - fetch(url: string|Request, init?: RequestInit): Promise; -} - -declare var fetch: typeof window.fetch; diff --git a/templates/KnockoutSpa/webpack.config.vendor.js b/templates/KnockoutSpa/webpack.config.vendor.js index 36375795..6c212ac4 100644 --- a/templates/KnockoutSpa/webpack.config.vendor.js +++ b/templates/KnockoutSpa/webpack.config.vendor.js @@ -15,7 +15,7 @@ module.exports = { ] }, entry: { - vendor: ['bootstrap', 'bootstrap/dist/css/bootstrap.css', 'knockout', 'crossroads', 'history', 'style-loader', 'jquery'], + vendor: ['bootstrap', 'bootstrap/dist/css/bootstrap.css', 'knockout', 'crossroads', 'history', 'isomorphic-fetch', 'style-loader', 'jquery'], }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), From 1ce8a2215c7572f178adc00581e31e1485a40f50 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 19 Aug 2016 01:40:35 +0100 Subject: [PATCH 035/791] In ReactSpa, use isomorphic-fetch for IE/Edge compatibility --- .../components/fetch-data/fetch-data.ts | 2 +- .../ClientApp/components/FetchData.tsx | 1 + templates/ReactSpa/package.json | 1 + templates/ReactSpa/tsd.json | 4 +- .../isomorphic-fetch/isomorphic-fetch.d.ts | 119 ++++++++++++++++++ templates/ReactSpa/typings/tsd.d.ts | 2 +- .../typings/whatwg-fetch/whatwg-fetch.d.ts | 85 ------------- templates/ReactSpa/webpack.config.vendor.js | 2 +- 8 files changed, 126 insertions(+), 90 deletions(-) create mode 100644 templates/ReactSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts delete mode 100644 templates/ReactSpa/typings/whatwg-fetch/whatwg-fetch.d.ts diff --git a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts index 3dc781c0..a6618cd3 100644 --- a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts @@ -1,5 +1,5 @@ import * as ko from 'knockout'; -import * as fetch from 'isomorphic-fetch'; +import 'isomorphic-fetch'; interface WeatherForecast { dateFormatted: string; diff --git a/templates/ReactSpa/ClientApp/components/FetchData.tsx b/templates/ReactSpa/ClientApp/components/FetchData.tsx index a010bac0..2ee2f1d1 100644 --- a/templates/ReactSpa/ClientApp/components/FetchData.tsx +++ b/templates/ReactSpa/ClientApp/components/FetchData.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import 'isomorphic-fetch'; interface FetchDataExampleState { forecasts: WeatherForecast[]; diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json index a66c99db..9e07d1b7 100644 --- a/templates/ReactSpa/package.json +++ b/templates/ReactSpa/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "babel-core": "^6.5.2", + "isomorphic-fetch": "^2.2.1", "react": "^15.0.1", "react-dom": "^15.0.1", "react-router": "^2.1.1" diff --git a/templates/ReactSpa/tsd.json b/templates/ReactSpa/tsd.json index 523893de..b69d22fe 100644 --- a/templates/ReactSpa/tsd.json +++ b/templates/ReactSpa/tsd.json @@ -17,8 +17,8 @@ "react-router/history.d.ts": { "commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7" }, - "whatwg-fetch/whatwg-fetch.d.ts": { - "commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7" + "isomorphic-fetch/isomorphic-fetch.d.ts": { + "commit": "57ec5fbb76060329c10959d449eb1d4e70b15a65" } } } diff --git a/templates/ReactSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts b/templates/ReactSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts new file mode 100644 index 00000000..19754db0 --- /dev/null +++ b/templates/ReactSpa/typings/isomorphic-fetch/isomorphic-fetch.d.ts @@ -0,0 +1,119 @@ +// Type definitions for isomorphic-fetch +// Project: https://github.com/matthew-andrews/isomorphic-fetch +// Definitions by: Todd Lucas +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare enum RequestContext { + "audio", "beacon", "cspreport", "download", "embed", "eventsource", + "favicon", "fetch", "font", "form", "frame", "hyperlink", "iframe", + "image", "imageset", "import", "internal", "location", "manifest", + "object", "ping", "plugin", "prefetch", "script", "serviceworker", + "sharedworker", "subresource", "style", "track", "video", "worker", + "xmlhttprequest", "xslt" +} +declare enum RequestMode { "same-origin", "no-cors", "cors" } +declare enum RequestCredentials { "omit", "same-origin", "include" } +declare enum RequestCache { + "default", "no-store", "reload", "no-cache", "force-cache", + "only-if-cached" +} +declare enum ResponseType { "basic", "cors", "default", "error", "opaque" } + +declare type HeaderInit = Headers | Array; +declare type BodyInit = ArrayBuffer | ArrayBufferView | Blob | FormData | string; +declare type RequestInfo = Request | string; + +interface RequestInit { + method?: string; + headers?: HeaderInit | { [index: string]: string }; + body?: BodyInit; + mode?: string | RequestMode; + credentials?: string | RequestCredentials; + cache?: string | RequestCache; +} + +interface IHeaders { + get(name: string): string; + getAll(name: string): Array; + has(name: string): boolean; +} + +declare class Headers implements IHeaders { + append(name: string, value: string): void; + delete(name: string):void; + get(name: string): string; + getAll(name: string): Array; + has(name: string): boolean; + set(name: string, value: string): void; +} + +interface IBody { + bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + json(): Promise; + text(): Promise; +} + +declare class Body implements IBody { + bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + json(): Promise; + text(): Promise; +} + +interface IRequest extends IBody { + method: string; + url: string; + headers: Headers; + context: string | RequestContext; + referrer: string; + mode: string | RequestMode; + credentials: string | RequestCredentials; + cache: string | RequestCache; +} + +declare class Request extends Body implements IRequest { + constructor(input: string | Request, init?: RequestInit); + method: string; + url: string; + headers: Headers; + context: string | RequestContext; + referrer: string; + mode: string | RequestMode; + credentials: string | RequestCredentials; + cache: string | RequestCache; +} + +interface IResponse extends IBody { + url: string; + status: number; + statusText: string; + ok: boolean; + headers: IHeaders; + type: string | ResponseType; + size: number; + timeout: number; + redirect(url: string, status: number): IResponse; + error(): IResponse; + clone(): IResponse; +} + +interface IFetchStatic { + Promise: any; + Headers: IHeaders + Request: IRequest; + Response: IResponse; + (url: string | IRequest, init?: RequestInit): Promise; +} + +declare var fetch: IFetchStatic; + +declare module "isomorphic-fetch" { + export = fetch; +} diff --git a/templates/ReactSpa/typings/tsd.d.ts b/templates/ReactSpa/typings/tsd.d.ts index f54e1c6a..6491ac94 100644 --- a/templates/ReactSpa/typings/tsd.d.ts +++ b/templates/ReactSpa/typings/tsd.d.ts @@ -3,4 +3,4 @@ /// /// /// -/// +/// diff --git a/templates/ReactSpa/typings/whatwg-fetch/whatwg-fetch.d.ts b/templates/ReactSpa/typings/whatwg-fetch/whatwg-fetch.d.ts deleted file mode 100644 index 64dd9048..00000000 --- a/templates/ReactSpa/typings/whatwg-fetch/whatwg-fetch.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Type definitions for fetch API -// Project: https://github.com/github/fetch -// Definitions by: Ryan Graham -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -declare class Request extends Body { - constructor(input: string|Request, init?:RequestInit); - method: string; - url: string; - headers: Headers; - context: string|RequestContext; - referrer: string; - mode: string|RequestMode; - credentials: string|RequestCredentials; - cache: string|RequestCache; -} - -interface RequestInit { - method?: string; - headers?: HeaderInit|{ [index: string]: string }; - body?: BodyInit; - mode?: string|RequestMode; - credentials?: string|RequestCredentials; - cache?: string|RequestCache; -} - -declare enum RequestContext { - "audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch", - "font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import", - "internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script", - "serviceworker", "sharedworker", "subresource", "style", "track", "video", "worker", - "xmlhttprequest", "xslt" -} -declare enum RequestMode { "same-origin", "no-cors", "cors" } -declare enum RequestCredentials { "omit", "same-origin", "include" } -declare enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" } - -declare class Headers { - append(name: string, value: string): void; - delete(name: string):void; - get(name: string): string; - getAll(name: string): Array; - has(name: string): boolean; - set(name: string, value: string): void; -} - -declare class Body { - bodyUsed: boolean; - arrayBuffer(): Promise; - blob(): Promise; - formData(): Promise; - json(): Promise; - json(): Promise; - text(): Promise; -} -declare class Response extends Body { - constructor(body?: BodyInit, init?: ResponseInit); - error(): Response; - redirect(url: string, status: number): Response; - type: string|ResponseType; - url: string; - status: number; - ok: boolean; - statusText: string; - headers: Headers; - clone(): Response; -} - -declare enum ResponseType { "basic", "cors", "default", "error", "opaque" } - -interface ResponseInit { - status: number; - statusText?: string; - headers?: HeaderInit; -} - -declare type HeaderInit = Headers|Array; -declare type BodyInit = Blob|FormData|string; -declare type RequestInfo = Request|string; - -interface Window { - fetch(url: string|Request, init?: RequestInit): Promise; -} - -declare var fetch: typeof window.fetch; diff --git a/templates/ReactSpa/webpack.config.vendor.js b/templates/ReactSpa/webpack.config.vendor.js index 814f23dd..ac56c0ca 100644 --- a/templates/ReactSpa/webpack.config.vendor.js +++ b/templates/ReactSpa/webpack.config.vendor.js @@ -15,7 +15,7 @@ module.exports = { ] }, entry: { - vendor: ['bootstrap', 'bootstrap/dist/css/bootstrap.css', 'react', 'react-dom', 'react-router', 'style-loader', 'jquery'], + vendor: ['bootstrap', 'bootstrap/dist/css/bootstrap.css', 'isomorphic-fetch', 'react', 'react-dom', 'react-router', 'style-loader', 'jquery'], }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), From eed4d8c2119fc75aea95b7b45bb2cbefa0e5277a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 19 Aug 2016 16:38:39 -0700 Subject: [PATCH 036/791] Child Node processes poll and exit when parent has exited. Fixes #270 --- .../Content/Node/entrypoint-http.js | 69 ++++++++++++++ .../Content/Node/entrypoint-socket.js | 95 ++++++++++++++++--- .../HostingModels/OutOfProcessNodeInstance.cs | 3 +- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 3 + .../SocketNodeInstanceEntryPoint.ts | 3 + .../TypeScript/Util/ExitWhenParentExits.ts | 62 ++++++++++++ 6 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 src/Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits.ts diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index fef873e7..76a00ef7 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -58,6 +58,7 @@ var http = __webpack_require__(3); var path = __webpack_require__(4); var ArgsUtil_1 = __webpack_require__(5); + var ExitWhenParentExits_1 = __webpack_require__(6); // 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'); @@ -121,6 +122,7 @@ // Signal to the NodeServices base class that we're ready to accept invocations console.log('[Microsoft.AspNetCore.NodeServices:Listening]'); }); + ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid)); function readRequestBodyAsJson(request, callback) { var requestBodyAsString = ''; request @@ -208,5 +210,72 @@ exports.parseArgs = parseArgs; +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + /* + In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit, + because we have no further use for them. If the .NET process shuts down gracefully, it will run its + finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately. + + But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have + any opportunity to shut down its child processes, and by default they will keep running. In this case, it's + up to the child process to detect this has happened and terminate itself. + + There are many possible approaches to detecting when a parent process has exited, most of which behave + differently between Windows and Linux/OS X: + + - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when + the parent does (http://stackoverflow.com/a/4657392). Not cross-platform. + - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)). + But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X). + - The child Node process can get a callback when its stdin/stdout are disconnected, as described at + http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows + causes the process to terminate prematurely. + - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes + the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere. + - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone + - You can directly pass a parent process PID to the child, and then have the child poll to see if it's + still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists, + as per https://nodejs.org/api/process.html#process_process_kill_pid_signal) + - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw. + However I don't see this documented anywhere. It would be nice if you could just poll for whether or not + process.stdout is still connected (without actually writing to it) but I haven't found any property whose + value changes until you actually try to write to it. + + Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling + to check whether the parent PID is still running. So that's what we do here. + */ + "use strict"; + var pollIntervalMs = 1000; + function exitWhenParentExits(parentPid) { + setInterval(function () { + if (!processExists(parentPid)) { + // Can't log anything at this point, because out stdout was connected to the parent, + // but the parent is gone. + process.exit(); + } + }, pollIntervalMs); + } + exports.exitWhenParentExits = exitWhenParentExits; + function processExists(pid) { + try { + // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't + // throw, that means it does exist. + process.kill(pid, 0); + return true; + } + catch (ex) { + // If the reason for the error is that we don't have permission to ask about this process, + // report that as a separate problem. + if (ex.code === 'EPERM') { + throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error."); + } + return false; + } + } + + /***/ } /******/ ]))); \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js index e59b6edf..c32a8821 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js @@ -44,7 +44,7 @@ /* 0 */ /***/ function(module, exports, __webpack_require__) { - module.exports = __webpack_require__(6); + module.exports = __webpack_require__(7); /***/ }, @@ -124,17 +124,85 @@ /***/ }, /* 6 */ +/***/ function(module, exports) { + + /* + In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit, + because we have no further use for them. If the .NET process shuts down gracefully, it will run its + finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately. + + But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have + any opportunity to shut down its child processes, and by default they will keep running. In this case, it's + up to the child process to detect this has happened and terminate itself. + + There are many possible approaches to detecting when a parent process has exited, most of which behave + differently between Windows and Linux/OS X: + + - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when + the parent does (http://stackoverflow.com/a/4657392). Not cross-platform. + - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)). + But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X). + - The child Node process can get a callback when its stdin/stdout are disconnected, as described at + http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows + causes the process to terminate prematurely. + - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes + the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere. + - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone + - You can directly pass a parent process PID to the child, and then have the child poll to see if it's + still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists, + as per https://nodejs.org/api/process.html#process_process_kill_pid_signal) + - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw. + However I don't see this documented anywhere. It would be nice if you could just poll for whether or not + process.stdout is still connected (without actually writing to it) but I haven't found any property whose + value changes until you actually try to write to it. + + Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling + to check whether the parent PID is still running. So that's what we do here. + */ + "use strict"; + var pollIntervalMs = 1000; + function exitWhenParentExits(parentPid) { + setInterval(function () { + if (!processExists(parentPid)) { + // Can't log anything at this point, because out stdout was connected to the parent, + // but the parent is gone. + process.exit(); + } + }, pollIntervalMs); + } + exports.exitWhenParentExits = exitWhenParentExits; + function processExists(pid) { + try { + // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't + // throw, that means it does exist. + process.kill(pid, 0); + return true; + } + catch (ex) { + // If the reason for the error is that we don't have permission to ask about this process, + // report that as a separate problem. + if (ex.code === 'EPERM') { + throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error."); + } + return false; + } + } + + +/***/ }, +/* 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__(7); + var net = __webpack_require__(8); var path = __webpack_require__(4); - var readline = __webpack_require__(8); + var readline = __webpack_require__(9); var ArgsUtil_1 = __webpack_require__(5); - var virtualConnectionServer = __webpack_require__(9); + 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'); @@ -189,27 +257,28 @@ var parsedArgs = ArgsUtil_1.parseArgs(process.argv); var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress; server.listen(listenAddress); + ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid)); /***/ }, -/* 7 */ +/* 8 */ /***/ function(module, exports) { module.exports = require("net"); /***/ }, -/* 8 */ +/* 9 */ /***/ function(module, exports) { module.exports = require("readline"); /***/ }, -/* 9 */ +/* 10 */ /***/ function(module, exports, __webpack_require__) { "use strict"; - var events_1 = __webpack_require__(10); - var VirtualConnection_1 = __webpack_require__(11); + var events_1 = __webpack_require__(11); + var VirtualConnection_1 = __webpack_require__(12); // 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. var MaxFrameBodyLength = 16 * 1024; @@ -390,13 +459,13 @@ /***/ }, -/* 10 */ +/* 11 */ /***/ function(module, exports) { module.exports = require("events"); /***/ }, -/* 11 */ +/* 12 */ /***/ function(module, exports, __webpack_require__) { "use strict"; @@ -405,7 +474,7 @@ function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; - var stream_1 = __webpack_require__(12); + var stream_1 = __webpack_require__(13); /** * Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection. */ @@ -446,7 +515,7 @@ /***/ }, -/* 12 */ +/* 13 */ /***/ function(module, exports) { module.exports = require("stream"); diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index d9ede585..73dcedc0 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -114,9 +114,10 @@ protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( debuggingArgs = string.Empty; } + var thisProcessPid = Process.GetCurrentProcess().Id; var startInfo = new ProcessStartInfo("node") { - Arguments = debuggingArgs + "\"" + entryPointFilename + "\" " + (commandLineArguments ?? string.Empty), + Arguments = $"{debuggingArgs}\"{entryPointFilename}\" --parentPid {thisProcessPid} {commandLineArguments ?? string.Empty}", UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index 0e10d993..431810e6 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -4,6 +4,7 @@ import './Util/OverrideStdOutputs'; import * as http from 'http'; import * as path from 'path'; import { parseArgs } from './Util/ArgsUtil'; +import { exitWhenParentExits } from './Util/ExitWhenParentExits'; // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct // reference to Node's runtime 'require' function. @@ -73,6 +74,8 @@ server.listen(requestedPortOrZero, 'localhost', function () { console.log('[Microsoft.AspNetCore.NodeServices:Listening]'); }); +exitWhenParentExits(parseInt(parsedArgs.parentPid)); + function readRequestBodyAsJson(request, callback) { let requestBodyAsString = ''; request diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/SocketNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/SocketNodeInstanceEntryPoint.ts index 0a6f713d..acb5df1d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/SocketNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/SocketNodeInstanceEntryPoint.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import * as readline from 'readline'; import { Duplex } from 'stream'; import { parseArgs } from './Util/ArgsUtil'; +import { exitWhenParentExits } from './Util/ExitWhenParentExits'; import * as virtualConnectionServer from './VirtualConnections/VirtualConnectionServer'; // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct @@ -69,6 +70,8 @@ const parsedArgs = parseArgs(process.argv); const listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress; server.listen(listenAddress); +exitWhenParentExits(parseInt(parsedArgs.parentPid)); + interface RpcInvocation { moduleName: string; exportedFunctionName: string; diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits.ts new file mode 100644 index 00000000..672e40b6 --- /dev/null +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits.ts @@ -0,0 +1,62 @@ +/* +In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit, +because we have no further use for them. If the .NET process shuts down gracefully, it will run its +finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately. + +But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have +any opportunity to shut down its child processes, and by default they will keep running. In this case, it's +up to the child process to detect this has happened and terminate itself. + +There are many possible approaches to detecting when a parent process has exited, most of which behave +differently between Windows and Linux/OS X: + + - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when + the parent does (http://stackoverflow.com/a/4657392). Not cross-platform. + - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)). + But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X). + - The child Node process can get a callback when its stdin/stdout are disconnected, as described at + http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows + causes the process to terminate prematurely. + - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes + the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere. + - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone + - You can directly pass a parent process PID to the child, and then have the child poll to see if it's + still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists, + as per https://nodejs.org/api/process.html#process_process_kill_pid_signal) + - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw. + However I don't see this documented anywhere. It would be nice if you could just poll for whether or not + process.stdout is still connected (without actually writing to it) but I haven't found any property whose + value changes until you actually try to write to it. + +Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling +to check whether the parent PID is still running. So that's what we do here. +*/ + +const pollIntervalMs = 1000; + +export function exitWhenParentExits(parentPid: number) { + setInterval(() => { + if (!processExists(parentPid)) { + // Can't log anything at this point, because out stdout was connected to the parent, + // but the parent is gone. + process.exit(); + } + }, pollIntervalMs); +} + +function processExists(pid: number) { + try { + // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't + // throw, that means it does exist. + process.kill(pid, 0); + return true; + } catch (ex) { + // If the reason for the error is that we don't have permission to ask about this process, + // report that as a separate problem. + if (ex.code === 'EPERM') { + throw new Error(`Attempted to check whether process ${pid} was running, but got a permissions error.`); + } + + return false; + } +} From 7f5810a62221429878664e3a7cbae674531c6116 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 12:36:47 +0100 Subject: [PATCH 037/791] Add docs about configuring Webpack to build LESS/SASS --- .../README.md | 134 +++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/README.md b/src/Microsoft.AspNetCore.SpaServices/README.md index 59e05fdb..4cfb961a 100644 --- a/src/Microsoft.AspNetCore.SpaServices/README.md +++ b/src/Microsoft.AspNetCore.SpaServices/README.md @@ -325,7 +325,7 @@ Benefits: It lets you work as if the browser natively understands whatever file types you are working with (e.g., TypeScript, SASS), because it's as if there's no build process to wait for. -### Example: A simple Webpack setup +### Example: A simple Webpack setup that builds TypeScript **Note:** If you already have Webpack in your project, then you can skip this section. @@ -376,6 +376,138 @@ The Webpack loader, `ts-loader`, follows all chains of reference from `MyApp.ts` So that's enough to build TypeScript. Here's where webpack dev middleware comes in to auto-build your code whenever needed (so you don't need any file watchers or to run `webpack` manually), and optionally hot module replacement (HMR) to push your changes automatically from code editor to browser without even reloading the page. +### Example: A simple Webpack setup that builds LESS + +Following on from the preceding example that builds TypeScript, you could extend your Webpack configuration further to support building LESS. There are two major approaches to doing this: + +1. **Have each build embed the style information into your JavaScript code**. At runtime, Webpack can dynamically attach the styles to your document via JavaScript. This has certain benefits during development. + +2. **Or, have each build write a standalone `.css` file to disk**. At runtime, load it using a regular `` tag. This is likely to be the approach you'll want for production use as it's the most robust and best-performing option. + +If instead of LESS you prefer SASS or another CSS preprocessor, the exact same techniques should work, but of course you'll need to replace the `less-loader` with an equivalent Webpack loader for SASS or your chosen preprocessor. + +#### Approach 1: Loading the styles using JavaScript + +This technique is a little simpler to set up than technique 1, plus it works flawlessly with Hot Module Replacement (HMR). The downside is that it's really only good for development time, because in production you probably don't want users to wait until JavaScript is loaded before styles are applied to the page (this would mean they'd see a 'flash of unstyled content' while the page is being loaded). + +First create a `.less` file in your project. For example, create a file at `ClientApp/styles/mystyles.less` containing: + +```less +@base: #f938ab; + +h1 { + color: @base; +} +``` + +Reference this file from an `import` or `require` statement in one of your JavaScript or TypeScript files. For example, if you've got a `boot-client.ts` file, add the following near the top: + +```javascript +import './styles/mystyles.less'; +``` + +If you try to run the Webpack compiler now (e.g., via `webpack` on the command line), you'll get an error saying it doesn't know how to build `.less` files. So, it's time to install a Webpack loader for LESS (plus related NPM modules). In a command prompt at your project's root directory, run: + +``` +npm install --save less-loader less +``` + +Finally, tell Webpack to use this whenever it encounters a `.less` file. In `webpack.config.js`, add to the `loaders` array: + +``` +{ test: /\.less/, loader: 'style!css!less' } +``` + +This means that when you `import` or `require` a `.less` file, it should pass it first to the LESS compiler to produce CSS, then the output goes to the CSS and Style loaders that know how to attach it dynamically to the page at runtime. + +That's all you need to do! Restart your site and you should see the LESS styles being applied. This technique is compatible with both source maps and Hot Module Replacement (HMR), so you can edit your `.less` files at will and see the changes appearing live in the browser. + +**Scoping styles in Angular 2 components** + +If you're using Angular 2, you can define styles on a per-component basis rather than just globally for your whole app. Angular then takes care of ensuring that only the intended styles are applied to each component, even if the selector names would otherwise clash. To extend the above technique to per-component styling, first install the `to-string-loader` NPM module: + +``` +npm install --save to-string-loader +``` + +Then in your `webpack.config.js`, simplify the `loader` entry for LESS files so that it just outputs `css` (without preparing it for use in a `style` tag): + +```javascript +{ test: /\.less/, loader: 'css!less' } +``` + +Now **you must remove any direct global references to the `.less` file**, since you'll no longer be loading it globally. So if you previously loaded `mystyles.less` using an `import` or `require` statement in `boot-client.ts` or similar, remove that line. + +Finally, load the LESS file scoped to a particular Angular 2 component by declaring a `styles` value for that component. For example, + +```javascript +@ng.Component({ + selector: ... leave value unchanged ..., + template: ... leave value unchanged ..., + styles: [require('to-string!../../styles/mystyles.less')] +}) +export class YourComponent { + ... code remains here ... +} +``` + +Now when you reload your page, you should file that the styles in `mystyles.less` are applied, but only to the component where you attached it. It's reasonable to use this technique in production because, even though the styles now depend on JavaScript to be applied, they are only used on elements that are injected via JavaScript anyway. + +#### Approach 2: Building LESS to CSS files on disk + +This technique takes a little more work to set up than technique 1, and lacks compatibility with HMR. But it's much better for production use if your styles are applied to the whole page (not just elements constructed via JavaScript), because it loads the CSS independently of JavaScript. + +First add a `.less` file into your project. For example, create a file at `ClientApp/styles/mystyles.less` containing: + +```less +@base: #f938ab; + +h1 { + color: @base; +} +``` + +Reference this file from an `import` or `require` statement in one of your JavaScript or TypeScript files. For example, if you've got a `boot-client.ts` file, add the following near the top: + +```javascript +import './styles/mystyles.less'; +``` + +If you try to run the Webpack compiler now (e.g., via `webpack` on the command line), you'll get an error saying it doesn't know how to build `.less` files. So, it's time to install a Webpack loader for LESS (plus related NPM modules). In a command prompt at your project's root directory, run: + +``` +npm install --save less less-loader extract-text-webpack-plugin +``` + +Next, you can extend your Webpack configuration to handle `.less` files. In `webpack.config.js`, at the top, add: + +```javascript +var extractStyles = new (require('extract-text-webpack-plugin'))('mystyles.css'); +``` + +This creates a plugin instance that will output text to a file called `mystyles.css`. You can now compile `.less` files and emit the resulting CSS text into that file. To do so, add the following to the `loaders` array in your Webpack configuration: + +```javascript +{ test: /\.less$/, loader: extractStyles.extract('css!less') } +``` + +This tells Webpack that, whenever it finds a `.less` file, it should use the LESS loader to produce CSS, and then feed that CSS into the `extractStyles` object which you've already configured to write a file on disk called `mystyles.css`. Finally, for this to actually work, you need to include `extractStyles` in the list of active plugins. Just add that object to the `plugins` array in your Webpack config, e.g.: + +```javascript +plugins: [ + extractStyles, + ... leave any other plugins here ... +] +``` + +If you run `webpack` on the command line now, you should now find that it emits a new file at `dist/mystyles.css`. You can make browsers load this file simply by adding a regular `` tag. For example, in `Views/Shared/_Layout.cshtml`, add: + +```html + +``` + +**Note** This technique (writing the built `.css` file to disk) is ideal for production use. But note that, at development time, *it does not support Hot Module Replacement (HMR)*. You will need to reload the page each time you edit your `.less` file. This is a known limitation of `extract-text-webpack-plugin`. If you have constructive opinions on how this can be improved, see the [discussion here](https://github.com/webpack/extract-text-webpack-plugin/issues/30). + ### Enabling webpack dev middleware First install the `Microsoft.AspNetCore.SpaServices` NuGet package and the `aspnet-webpack` NPM package, then go to your `Startup.cs` file, and **before your call to `UseStaticFiles`**, add the following: From 4effd630a4ae1763fc7820e2b9a14665e16c4c78 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 12:42:12 +0100 Subject: [PATCH 038/791] Fix typos --- src/Microsoft.AspNetCore.SpaServices/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/README.md b/src/Microsoft.AspNetCore.SpaServices/README.md index 4cfb961a..fda8baf3 100644 --- a/src/Microsoft.AspNetCore.SpaServices/README.md +++ b/src/Microsoft.AspNetCore.SpaServices/README.md @@ -388,7 +388,7 @@ If instead of LESS you prefer SASS or another CSS preprocessor, the exact same t #### Approach 1: Loading the styles using JavaScript -This technique is a little simpler to set up than technique 1, plus it works flawlessly with Hot Module Replacement (HMR). The downside is that it's really only good for development time, because in production you probably don't want users to wait until JavaScript is loaded before styles are applied to the page (this would mean they'd see a 'flash of unstyled content' while the page is being loaded). +This technique is a little simpler to set up than technique 2, plus it works flawlessly with Hot Module Replacement (HMR). The downside is that it's really only good for development time, because in production you probably don't want users to wait until JavaScript is loaded before styles are applied to the page (this would mean they'd see a 'flash of unstyled content' while the page is being loaded). First create a `.less` file in your project. For example, create a file at `ClientApp/styles/mystyles.less` containing: @@ -506,7 +506,7 @@ If you run `webpack` on the command line now, you should now find that it emits ``` -**Note** This technique (writing the built `.css` file to disk) is ideal for production use. But note that, at development time, *it does not support Hot Module Replacement (HMR)*. You will need to reload the page each time you edit your `.less` file. This is a known limitation of `extract-text-webpack-plugin`. If you have constructive opinions on how this can be improved, see the [discussion here](https://github.com/webpack/extract-text-webpack-plugin/issues/30). +**Note:** This technique (writing the built `.css` file to disk) is ideal for production use. But note that, at development time, *it does not support Hot Module Replacement (HMR)*. You will need to reload the page each time you edit your `.less` file. This is a known limitation of `extract-text-webpack-plugin`. If you have constructive opinions on how this can be improved, see the [discussion here](https://github.com/webpack/extract-text-webpack-plugin/issues/30). ### Enabling webpack dev middleware From 09e1cd3b077aa505be73fdcd2f87658510e8cc34 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 13:59:28 +0100 Subject: [PATCH 039/791] TS compiler should ignore "bin" dir because, after VS publish, it might contain duplicates of the app's source files. Fixes #279. --- templates/Angular2Spa/tsconfig.json | 1 + templates/KnockoutSpa/tsconfig.json | 1 + templates/ReactReduxSpa/tsconfig.json | 1 + templates/ReactSpa/tsconfig.json | 1 + templates/WebApplicationBasic/tsconfig.json | 1 + 5 files changed, 5 insertions(+) diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json index ef5deed5..e141587c 100644 --- a/templates/Angular2Spa/tsconfig.json +++ b/templates/Angular2Spa/tsconfig.json @@ -8,6 +8,7 @@ "skipDefaultLibCheck": true }, "exclude": [ + "bin", "node_modules" ] } diff --git a/templates/KnockoutSpa/tsconfig.json b/templates/KnockoutSpa/tsconfig.json index 61872e16..1404455b 100644 --- a/templates/KnockoutSpa/tsconfig.json +++ b/templates/KnockoutSpa/tsconfig.json @@ -6,6 +6,7 @@ "skipDefaultLibCheck": true }, "exclude": [ + "bin", "node_modules" ] } diff --git a/templates/ReactReduxSpa/tsconfig.json b/templates/ReactReduxSpa/tsconfig.json index b6603ac4..c5ae2118 100644 --- a/templates/ReactReduxSpa/tsconfig.json +++ b/templates/ReactReduxSpa/tsconfig.json @@ -8,6 +8,7 @@ "skipDefaultLibCheck": true }, "exclude": [ + "bin", "node_modules" ] } diff --git a/templates/ReactSpa/tsconfig.json b/templates/ReactSpa/tsconfig.json index 39a15ea3..69ed93a9 100644 --- a/templates/ReactSpa/tsconfig.json +++ b/templates/ReactSpa/tsconfig.json @@ -7,6 +7,7 @@ "skipDefaultLibCheck": true }, "exclude": [ + "bin", "node_modules" ] } diff --git a/templates/WebApplicationBasic/tsconfig.json b/templates/WebApplicationBasic/tsconfig.json index 61872e16..1404455b 100644 --- a/templates/WebApplicationBasic/tsconfig.json +++ b/templates/WebApplicationBasic/tsconfig.json @@ -6,6 +6,7 @@ "skipDefaultLibCheck": true }, "exclude": [ + "bin", "node_modules" ] } From 61fd9009749a6c8b55f375209b40898a5db6b2b0 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 14:10:42 +0100 Subject: [PATCH 040/791] Add missing setup step to docs. Fixes #290. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 85dd692f..93224181 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Also in this repo, [the `samples` directory](https://github.com/aspnet/JavaScrip * Clone this repo * Change directory to the same you want to run (e.g., `cd samples/angular/MusicStore`) - * Restore dependencies (run `dotnet restore` and `npm install`) + * Restore dependencies (run `dotnet restore` and `npm install`). + * If you're trying to run the Angular 2 "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this. * Run the application (`dotnet run`) * Browse to [http://localhost:5000](http://localhost:5000) From f04fb8c42115d3816f9b8005dacefe7e6e485325 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 15:51:53 +0100 Subject: [PATCH 041/791] Design review: Always instantiate via DI --- samples/misc/LatencyTest/Program.cs | 21 +++-- samples/misc/LatencyTest/project.json | 3 +- samples/misc/Webpack/webpack.config.js | 2 +- .../Configuration/Configuration.cs | 89 ++++++++++--------- .../Configuration/NodeServicesOptions.cs | 16 ---- .../NodeServicesImpl.cs | 4 +- .../README.md | 30 +++++-- .../Prerendering/PrerenderTagHelper.cs | 7 +- .../Webpack/WebpackDevMiddleware.cs | 16 ++-- 9 files changed, 97 insertions(+), 91 deletions(-) diff --git a/samples/misc/LatencyTest/Program.cs b/samples/misc/LatencyTest/Program.cs index 2087d29e..9dc3ce81 100755 --- a/samples/misc/LatencyTest/Program.cs +++ b/samples/misc/LatencyTest/Program.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.NodeServices; +using Microsoft.Extensions.DependencyInjection; namespace ConsoleApplication { @@ -12,7 +13,17 @@ namespace ConsoleApplication public class Program { public static void Main(string[] args) { - using (var nodeServices = CreateNodeServices(NodeServicesOptions.DefaultNodeHostingModel)) { + // Set up the DI system + var services = new ServiceCollection(); + services.AddNodeServices(new NodeServicesOptions { + HostingModel = NodeServicesOptions.DefaultNodeHostingModel, + ProjectPath = Directory.GetCurrentDirectory(), + WatchFileExtensions = new string[] {} // Don't watch anything + }); + var serviceProvider = services.BuildServiceProvider(); + + // Now instantiate an INodeServices and use it + using (var nodeServices = serviceProvider.GetRequiredService()) { MeasureLatency(nodeServices).Wait(); } } @@ -34,13 +45,5 @@ private static async Task MeasureLatency(INodeServices nodeServices) { Console.WriteLine("\nTotal time: {0:F2} milliseconds", 1000 * elapsedSeconds); Console.WriteLine("\nTime per invocation: {0:F2} milliseconds", 1000 * elapsedSeconds / requestCount); } - - private static INodeServices CreateNodeServices(NodeHostingModel hostingModel) { - return Configuration.CreateNodeServices(new NodeServicesOptions { - HostingModel = hostingModel, - ProjectPath = Directory.GetCurrentDirectory(), - WatchFileExtensions = new string[] {} // Don't watch anything - }); - } } } diff --git a/samples/misc/LatencyTest/project.json b/samples/misc/LatencyTest/project.json index 33820b5d..15e349ca 100755 --- a/samples/misc/LatencyTest/project.json +++ b/samples/misc/LatencyTest/project.json @@ -8,7 +8,8 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.AspNetCore.NodeServices": "1.0.0-*" + "Microsoft.AspNetCore.NodeServices": "1.0.0-*", + "Microsoft.Extensions.DependencyInjection": "1.0.0" }, "frameworks": { "netcoreapp1.0": { diff --git a/samples/misc/Webpack/webpack.config.js b/samples/misc/Webpack/webpack.config.js index 5ff186da..011ed5f2 100644 --- a/samples/misc/Webpack/webpack.config.js +++ b/samples/misc/Webpack/webpack.config.js @@ -10,7 +10,7 @@ module.exports = merge({ }, module: { loaders: [ - { test: /\.ts(x?)$/, exclude: /node_modules/, loader: 'ts-loader' } + { test: /\.ts(x?)$/, exclude: /node_modules/, loader: 'ts-loader?silent' } ], }, entry: { diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs index bd76dc8d..4cbba695 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; +using System.Collections.Generic; namespace Microsoft.AspNetCore.NodeServices { @@ -16,48 +17,18 @@ public static void AddNodeServices(this IServiceCollection serviceCollection) public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) { - serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider => - { - // Since this instance is being created through DI, we can access the IHostingEnvironment - // to populate options.ProjectPath if it wasn't explicitly specified. - var hostEnv = serviceProvider.GetRequiredService(); - if (string.IsNullOrEmpty(options.ProjectPath)) - { - options.ProjectPath = hostEnv.ContentRootPath; - } - - // Similarly, we can determine the 'is development' value from the hosting environment - options.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment()); - - // Likewise, if no logger was specified explicitly, we should use the one from DI. - // If it doesn't provide one, CreateNodeInstance will set up a default. - if (options.NodeInstanceOutputLogger == null) - { - var loggerFactory = serviceProvider.GetService(); - if (loggerFactory != null) - { - options.NodeInstanceOutputLogger = loggerFactory.CreateLogger(LogCategoryName); - } - } - - return new NodeServicesImpl(options, () => CreateNodeInstance(options)); - }); + serviceCollection.AddSingleton( + typeof(INodeServices), + serviceProvider => CreateNodeServices(serviceProvider, options)); } - public static INodeServices CreateNodeServices(NodeServicesOptions options) + public static INodeServices CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options) { - return new NodeServicesImpl(options, () => CreateNodeInstance(options)); + return new NodeServicesImpl(() => CreateNodeInstance(serviceProvider, options)); } - private static INodeInstance CreateNodeInstance(NodeServicesOptions options) + private static INodeInstance CreateNodeInstance(IServiceProvider serviceProvider, NodeServicesOptions options) { - // If you've specified no logger, fall back on a default console logger - var logger = options.NodeInstanceOutputLogger; - if (logger == null) - { - logger = new ConsoleLogger(LogCategoryName, null, false); - } - if (options.NodeInstanceFactory != null) { // If you've explicitly supplied an INodeInstance factory, we'll use that. This is useful for @@ -66,17 +37,51 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options) } else { - // Otherwise we'll construct the type of INodeInstance specified by the HostingModel property, - // which itself has a useful default value. + // Otherwise we'll construct the type of INodeInstance specified by the HostingModel property + // (which itself has a useful default value), plus obtain config information from the DI system. + var projectPath = options.ProjectPath; + var envVars = options.EnvironmentVariables == null + ? new Dictionary() + : new Dictionary(options.EnvironmentVariables); + + var hostEnv = serviceProvider.GetService(); + if (hostEnv != null) + { + // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few + // things that you'd otherwise have to specify manually + if (string.IsNullOrEmpty(projectPath)) + { + projectPath = hostEnv.ContentRootPath; + } + + // Similarly, we can determine the 'is development' value from the hosting environment + if (!envVars.ContainsKey("NODE_ENV")) + { + // These strings are a de-facto standard in Node + envVars["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; + } + } + + // If no logger was specified explicitly, we should use the one from DI. + // If it doesn't provide one, we'll set up a default one. + var logger = options.NodeInstanceOutputLogger; + if (logger == null) + { + var loggerFactory = serviceProvider.GetService(); + logger = loggerFactory != null + ? loggerFactory.CreateLogger(LogCategoryName) + : new ConsoleLogger(LogCategoryName, null, false); + } + switch (options.HostingModel) { case NodeHostingModel.Http: - return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, logger, - options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); + return new HttpNodeInstance(projectPath, options.WatchFileExtensions, logger, + envVars, 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, logger, - options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort); + return new SocketNodeInstance(projectPath, options.WatchFileExtensions, pipeName, logger, + envVars, options.LaunchWithDebugging, options.DebuggingPort); default: throw new ArgumentException("Unknown hosting model: " + options.HostingModel); } diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index 4ff981ec..ddc96862 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -25,21 +25,5 @@ public NodeServicesOptions() public bool LaunchWithDebugging { get; set; } public IDictionary EnvironmentVariables { get; set; } public int? DebuggingPort { get; set; } - - public NodeServicesOptions AddDefaultEnvironmentVariables(bool isDevelopmentMode) - { - if (EnvironmentVariables == null) - { - EnvironmentVariables = new Dictionary(); - } - - if (!EnvironmentVariables.ContainsKey("NODE_ENV")) - { - // These strings are a de-facto standard in Node - EnvironmentVariables["NODE_ENV"] = isDevelopmentMode ? "development" : "production"; - } - - return this; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs index 82232150..037cc96d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs +++ b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs @@ -19,15 +19,13 @@ namespace Microsoft.AspNetCore.NodeServices internal class NodeServicesImpl : INodeServices { private static TimeSpan ConnectionDrainingTimespan = TimeSpan.FromSeconds(15); - private NodeServicesOptions _options; private Func _nodeInstanceFactory; private INodeInstance _currentNodeInstance; private object _currentNodeInstanceAccessLock = new object(); private Exception _instanceDelayedDisposalException; - internal NodeServicesImpl(NodeServicesOptions options, Func nodeInstanceFactory) + internal NodeServicesImpl(Func nodeInstanceFactory) { - _options = options; _nodeInstanceFactory = nodeInstanceFactory; } diff --git a/src/Microsoft.AspNetCore.NodeServices/README.md b/src/Microsoft.AspNetCore.NodeServices/README.md index 3fb99282..6cf57ccc 100644 --- a/src/Microsoft.AspNetCore.NodeServices/README.md +++ b/src/Microsoft.AspNetCore.NodeServices/README.md @@ -90,18 +90,33 @@ If you want to put `addNumber.js` inside a subfolder rather than the root of you ## For non-ASP.NET apps -In other types of .NET app where you don't have ASP.NET Core's DI system, you can get an instance of `NodeServices` as follows: +In other types of .NET Core app, where you don't have ASP.NET supplying an `IServiceCollection` to you, you'll need to instantiate your own DI container. For example, add a reference to the .NET package `Microsoft.Extensions.DependencyInjection`, and then you can construct an `IServiceCollection`, then register NodeServices as usual: ```csharp // Remember to add 'using Microsoft.AspNetCore.NodeServices;' at the top of your file +var services = new ServiceCollection(); +services.AddNodeServices(new NodeServicesOptions { /* your options here */ }); +``` + +Now you can ask it to supply the shared `INodeServices` instance: + +```csharp +var serviceProvider = services.BuildServiceProvider(); +var nodeServices = serviceProvider.GetRequiredService(); +``` + +Or, if you want to obtain a separate (non-shared) `INodeServices` instance: -var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions()); +```csharp +var options = new NodeServicesOptions { /* your options here */ }; +var nodeServices = Microsoft.AspNetCore.NodeServices.Configuration.CreateNodeServices(serviceProvider, options); ``` Besides this, the usage is the same as described for ASP.NET above, so you can now call `nodeServices.InvokeAsync(...)` etc. -You can dispose the `nodeServices` object whenever you are done with it (and it will shut down the associated Node.js instance), but because these instances are expensive to create, you should whenever possible retain and reuse instances. They are thread-safe - you can call `InvokeAsync` simultaneously from multiple threads. Also, `NodeServices` instances are smart enough to detect if the associated Node instance has died and will automatically start a new Node instance if needed. +You can dispose the `nodeServices` object whenever you are done with it (and it will shut down the associated Node.js instance), but because these instances are expensive to create, you should whenever possible retain and reuse instances. Don't dispose the shared instance returned from `serviceProvider.GetRequiredService` (except perhaps if you know your application is shutting down, although .NET's finalizers will dispose it anyway if the shutdown is graceful). +NodeServices instances are thread-safe - you can call `InvokeAsync` simultaneously from multiple threads. Also, they are smart enough to detect if the associated Node instance has died and will automatically start a new Node instance if needed. # API Reference @@ -158,21 +173,22 @@ If no `options` is passed, the default `WatchFileExtensions` array includes `.js **Signature:** ```csharp -CreateNodeServices(NodeServicesOptions options) +CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options) ``` -Directly supplies an instance of `NodeServices` without using ASP.NET's DI system. +Supplies a new (non-shared) instance of `NodeServices`. It takes configuration from the .NET DI system (hence requiring an `IServiceProvider`), though some aspects of configuration can be overridden via the `options` parameter. **Example** ```csharp -var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions { +var nodeServices = Configuration.CreateNodeServices(serviceProvider, new NodeServicesOptions { HostingModel = NodeHostingModel.Socket }); ``` **Parameters** - + * `serviceProvider` - type: `IServiceProvider` + * An instance of .NET's standard DI service provider. You can get an instance of this by calling `BuildServiceProvider` on an `IServiceCollection` object. See the example usage of `CreateNodeServices` earlier in this document. * `options` - type: `NodeServicesOptions`. * Configures the returned `NodeServices` instance. * Properties: diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index 1baaa601..8ee24847 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -33,10 +33,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) // in your startup file, but then again it might be confusing that you don't need to. if (_nodeServices == null) { - _nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions - { - ProjectPath = _applicationBasePath - }.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment())); + _nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices( + serviceProvider, + new NodeServicesOptions()); } } diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index 1a0c9bb5..dc7de6ed 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -35,19 +35,17 @@ public static void UseWebpackDevMiddleware( "To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement."); } - var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath; - // Unlike other consumers of NodeServices, WebpackDevMiddleware dosen't share Node instances, nor does it // use your DI configuration. It's important for WebpackDevMiddleware to have its own private Node instance // because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack // middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't // as fast as some theoretical future alternative. - var nodeServices = Configuration.CreateNodeServices(new NodeServicesOptions - { - ProjectPath = projectPath, - WatchFileExtensions = new string[] { } // Don't watch anything - }.AddDefaultEnvironmentVariables(hostEnv.IsDevelopment())); + var nodeServices = Configuration.CreateNodeServices( + appBuilder.ApplicationServices, + new NodeServicesOptions + { + WatchFileExtensions = new string[] { } // Don't watch anything + }); // Get a filename matching the middleware Node script var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware), @@ -55,6 +53,8 @@ public static void UseWebpackDevMiddleware( var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit // Tell Node to start the server hosting webpack-dev-middleware + var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath; var devServerOptions = new { webpackConfigPath = Path.Combine(projectPath, options.ConfigFile ?? DefaultConfigFile), From f0d954b2a609c3110c1520b58e610b97879b85d6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 17:46:59 +0100 Subject: [PATCH 042/791] Design review: Change AddNodeServices to take an Action like other aspects of MVC DI config --- samples/misc/LatencyTest/Program.cs | 8 +- .../Configuration/Configuration.cs | 91 ------------------- .../Configuration/NodeServicesFactory.cs | 43 +++++++++ .../Configuration/NodeServicesOptions.cs | 32 ++++++- ...NodeServicesServiceCollectionExtensions.cs | 41 +++++++++ .../HostingModels/HttpNodeInstance.cs | 2 +- .../HostingModels/OutOfProcessNodeInstance.cs | 6 +- .../HostingModels/SocketNodeInstance.cs | 2 +- .../README.md | 52 +++++------ .../Prerendering/PrerenderTagHelper.cs | 5 +- .../Webpack/WebpackDevMiddleware.cs | 9 +- 11 files changed, 150 insertions(+), 141 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs create mode 100644 src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs create mode 100644 src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs diff --git a/samples/misc/LatencyTest/Program.cs b/samples/misc/LatencyTest/Program.cs index 9dc3ce81..e435d374 100755 --- a/samples/misc/LatencyTest/Program.cs +++ b/samples/misc/LatencyTest/Program.cs @@ -15,10 +15,10 @@ public class Program public static void Main(string[] args) { // Set up the DI system var services = new ServiceCollection(); - services.AddNodeServices(new NodeServicesOptions { - HostingModel = NodeServicesOptions.DefaultNodeHostingModel, - ProjectPath = Directory.GetCurrentDirectory(), - WatchFileExtensions = new string[] {} // Don't watch anything + services.AddNodeServices(options => { + options.HostingModel = NodeServicesOptions.DefaultNodeHostingModel; + options.ProjectPath = Directory.GetCurrentDirectory(); + options.WatchFileExtensions = new string[] {}; // Don't watch anything }); var serviceProvider = services.BuildServiceProvider(); diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs deleted file mode 100644 index 4cbba695..00000000 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.NodeServices.HostingModels; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.NodeServices -{ - public static class Configuration - { - const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; - - public static void AddNodeServices(this IServiceCollection serviceCollection) - => AddNodeServices(serviceCollection, new NodeServicesOptions()); - - public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) - { - serviceCollection.AddSingleton( - typeof(INodeServices), - serviceProvider => CreateNodeServices(serviceProvider, options)); - } - - public static INodeServices CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options) - { - return new NodeServicesImpl(() => CreateNodeInstance(serviceProvider, options)); - } - - private static INodeInstance CreateNodeInstance(IServiceProvider serviceProvider, NodeServicesOptions options) - { - 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 - { - // Otherwise we'll construct the type of INodeInstance specified by the HostingModel property - // (which itself has a useful default value), plus obtain config information from the DI system. - var projectPath = options.ProjectPath; - var envVars = options.EnvironmentVariables == null - ? new Dictionary() - : new Dictionary(options.EnvironmentVariables); - - var hostEnv = serviceProvider.GetService(); - if (hostEnv != null) - { - // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few - // things that you'd otherwise have to specify manually - if (string.IsNullOrEmpty(projectPath)) - { - projectPath = hostEnv.ContentRootPath; - } - - // Similarly, we can determine the 'is development' value from the hosting environment - if (!envVars.ContainsKey("NODE_ENV")) - { - // These strings are a de-facto standard in Node - envVars["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; - } - } - - // If no logger was specified explicitly, we should use the one from DI. - // If it doesn't provide one, we'll set up a default one. - var logger = options.NodeInstanceOutputLogger; - if (logger == null) - { - var loggerFactory = serviceProvider.GetService(); - logger = loggerFactory != null - ? loggerFactory.CreateLogger(LogCategoryName) - : new ConsoleLogger(LogCategoryName, null, false); - } - - switch (options.HostingModel) - { - case NodeHostingModel.Http: - return new HttpNodeInstance(projectPath, options.WatchFileExtensions, logger, - envVars, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); - case NodeHostingModel.Socket: - var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string - return new SocketNodeInstance(projectPath, options.WatchFileExtensions, pipeName, logger, - envVars, options.LaunchWithDebugging, options.DebuggingPort); - default: - throw new ArgumentException("Unknown hosting model: " + options.HostingModel); - } - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs new file mode 100644 index 00000000..fc038f2c --- /dev/null +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.AspNetCore.NodeServices.HostingModels; + +namespace Microsoft.AspNetCore.NodeServices +{ + public static class NodeServicesFactory + { + public static INodeServices CreateNodeServices(NodeServicesOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof (options)); + } + + return new NodeServicesImpl(() => CreateNodeInstance(options)); + } + + private static INodeInstance CreateNodeInstance(NodeServicesOptions options) + { + 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.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.LaunchWithDebugging, options.DebuggingPort); + default: + throw new ArgumentException("Unknown hosting model: " + options.HostingModel); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index ddc96862..edf9ae2c 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -2,21 +2,45 @@ using System.Collections.Generic; using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging.Console; namespace Microsoft.AspNetCore.NodeServices { public class NodeServicesOptions { public const NodeHostingModel DefaultNodeHostingModel = NodeHostingModel.Http; - + private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; private static readonly string[] DefaultWatchFileExtensions = { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" }; - public NodeServicesOptions() + public NodeServicesOptions(IServiceProvider serviceProvider) { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof (serviceProvider)); + } + + EnvironmentVariables = new Dictionary(); HostingModel = DefaultNodeHostingModel; WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); + + // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few + // things that you'd otherwise have to specify manually + var hostEnv = serviceProvider.GetService(); + if (hostEnv != null) + { + ProjectPath = hostEnv.ContentRootPath; + EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node + } + + // If the DI system gives us a logger, use it. Otherwise, set up a default one. + var loggerFactory = serviceProvider.GetService(); + NodeInstanceOutputLogger = loggerFactory != null + ? loggerFactory.CreateLogger(LogCategoryName) + : new ConsoleLogger(LogCategoryName, null, false); } - public Action OnBeforeStartExternalProcess { get; set; } + public NodeHostingModel HostingModel { get; set; } public Func NodeInstanceFactory { get; set; } public string ProjectPath { get; set; } @@ -24,6 +48,6 @@ public NodeServicesOptions() public ILogger NodeInstanceOutputLogger { get; set; } public bool LaunchWithDebugging { get; set; } public IDictionary EnvironmentVariables { get; set; } - public int? DebuggingPort { get; set; } + public int DebuggingPort { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs new file mode 100644 index 00000000..e2f516e0 --- /dev/null +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.AspNetCore.NodeServices; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for setting up NodeServices in an . + /// + public static class NodeServicesServiceCollectionExtensions + { + public static void AddNodeServices(this IServiceCollection serviceCollection) + => AddNodeServices(serviceCollection, _ => {}); + + [Obsolete("Use the AddNodeServices(Action setupAction) overload instead.")] + public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) + { + serviceCollection.AddSingleton(typeof (INodeServices), _ => + { + return NodeServicesFactory.CreateNodeServices(options); + }); + } + + public static void AddNodeServices(this IServiceCollection serviceCollection, Action setupAction) + { + if (setupAction == null) + { + throw new ArgumentNullException(nameof (setupAction)); + } + + serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider => + { + // First we let NodeServicesOptions take its defaults from the IServiceProvider, + // then we let the developer override those options + var options = new NodeServicesOptions(serviceProvider); + setupAction(options); + + return NodeServicesFactory.CreateNodeServices(options); + }); + } + } +} diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 9400d6e1..9fd72a4d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -35,7 +35,7 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private int _portNumber; public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, - IDictionary environmentVars, bool launchWithDebugging, int? debuggingPort, int port = 0) + IDictionary environmentVars, bool launchWithDebugging, int debuggingPort, int port = 0) : base( EmbeddedResourceReader.Read( typeof(HttpNodeInstance), diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 73dcedc0..641c098d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -49,7 +49,7 @@ public OutOfProcessNodeInstance( ILogger nodeOutputLogger, IDictionary environmentVars, bool launchWithDebugging, - int? debuggingPort) + int debuggingPort) { if (nodeOutputLogger == null) { @@ -101,12 +101,12 @@ public void Dispose() // This method is virtual, as it provides a way to override the NODE_PATH or the path to node.exe protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( string entryPointFilename, string projectPath, string commandLineArguments, - IDictionary environmentVars, bool launchWithDebugging, int? debuggingPort) + IDictionary environmentVars, bool launchWithDebugging, int debuggingPort) { string debuggingArgs; if (launchWithDebugging) { - debuggingArgs = debuggingPort.HasValue ? $"--debug={debuggingPort.Value} " : "--debug "; + debuggingArgs = debuggingPort != default(int) ? $"--debug={debuggingPort} " : "--debug "; _nodeDebuggingPort = debuggingPort; } else diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 46115f69..1a1c74e3 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -40,7 +40,7 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, ILogger nodeInstanceOutputLogger, IDictionary environmentVars, - bool launchWithDebugging, int? debuggingPort) + bool launchWithDebugging, int debuggingPort) : base( EmbeddedResourceReader.Read( typeof(SocketNodeInstance), diff --git a/src/Microsoft.AspNetCore.NodeServices/README.md b/src/Microsoft.AspNetCore.NodeServices/README.md index 6cf57ccc..1a20a8f5 100644 --- a/src/Microsoft.AspNetCore.NodeServices/README.md +++ b/src/Microsoft.AspNetCore.NodeServices/README.md @@ -95,7 +95,9 @@ In other types of .NET Core app, where you don't have ASP.NET supplying an `ISer ```csharp // Remember to add 'using Microsoft.AspNetCore.NodeServices;' at the top of your file var services = new ServiceCollection(); -services.AddNodeServices(new NodeServicesOptions { /* your options here */ }); +services.AddNodeServices(options => { + // Set any properties that you want on 'options' here +}); ``` Now you can ask it to supply the shared `INodeServices` instance: @@ -108,8 +110,8 @@ var nodeServices = serviceProvider.GetRequiredService(); Or, if you want to obtain a separate (non-shared) `INodeServices` instance: ```csharp -var options = new NodeServicesOptions { /* your options here */ }; -var nodeServices = Microsoft.AspNetCore.NodeServices.Configuration.CreateNodeServices(serviceProvider, options); +var options = new NodeServicesOptions(serviceProvider) { /* Assign/override any other options here */ }; +var nodeServices = NodeServicesFactory.CreateNodeServices(options); ``` Besides this, the usage is the same as described for ASP.NET above, so you can now call `nodeServices.InvokeAsync(...)` etc. @@ -126,7 +128,7 @@ NodeServices instances are thread-safe - you can call `InvokeAsync` simultane ```csharp AddNodeServices() -AddNodeServices(NodeServicesOptions options) +AddNodeServices(Action setupAction) ``` This is an extension method on `IServiceCollection`. It registers NodeServices with ASP.NET Core's DI system. Typically you should call this from the `ConfigureServices` method in your `Startup.cs` file. @@ -148,23 +150,21 @@ services.AddNodeServices(); Or, specifying options: ```csharp -services.AddNodeServices(new NodeServicesOptions +services.AddNodeServices(options => { - WatchFileExtensions = new[] { ".coffee", ".sass" }, + options.WatchFileExtensions = new[] { ".coffee", ".sass" }; // ... etc. - see other properties below }); ``` **Parameters** - * `options` - type: `NodeServicesOptions` - * Optional. If specified, configures how the `NodeServices` instances will work. - * Properties: + * `setupAction` - type: `Action` + * Optional. If not specified, defaults will be used. + * Properties on `NodeServicesOptions`: * `HostingModel` - an `NodeHostingModel` enum value. See: [hosting models](#hosting-models) * `ProjectPath` - if specified, controls the working directory used when launching Node instances. This affects, for example, the location that `require` statements resolve relative paths against. If not specified, your application root directory is used. - * `WatchFileExtensions` - if specified, the launched Node instance will watch for changes to any files with these extensions, and auto-restarts when any are changed. - -If no `options` is passed, the default `WatchFileExtensions` array includes `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, and `.html`. + * `WatchFileExtensions` - if specified, the launched Node instance will watch for changes to any files with these extensions, and auto-restarts when any are changed. The default array includes `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, and `.html`. **Return type**: None. But once you've done this, you can get `NodeServices` instances out of ASP.NET's DI system. Typically it will be a singleton instance. @@ -173,22 +173,19 @@ If no `options` is passed, the default `WatchFileExtensions` array includes `.js **Signature:** ```csharp -CreateNodeServices(IServiceProvider serviceProvider, NodeServicesOptions options) +CreateNodeServices(NodeServicesOptions options) ``` -Supplies a new (non-shared) instance of `NodeServices`. It takes configuration from the .NET DI system (hence requiring an `IServiceProvider`), though some aspects of configuration can be overridden via the `options` parameter. +Supplies a new (non-shared) instance of `NodeServices`. **Example** ```csharp -var nodeServices = Configuration.CreateNodeServices(serviceProvider, new NodeServicesOptions { - HostingModel = NodeHostingModel.Socket -}); +var options = new NodeServicesOptions(serviceProvider); // Obtains default options from DI config +var nodeServices = NodeServicesFactory.CreateNodeServices(options); ``` **Parameters** - * `serviceProvider` - type: `IServiceProvider` - * An instance of .NET's standard DI service provider. You can get an instance of this by calling `BuildServiceProvider` on an `IServiceCollection` object. See the example usage of `CreateNodeServices` earlier in this document. * `options` - type: `NodeServicesOptions`. * Configures the returned `NodeServices` instance. * Properties: @@ -343,12 +340,12 @@ People have asked about using [VroomJS](https://github.com/fogzot/vroomjs) as a ### 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 passing an `options` parameter to `AddNodeServices` or `CreateNodeServices`, and populate its `HostingModel` property. Example: +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: ```csharp -services.AddNodeServices(new NodeServicesOptions +services.AddNodeServices(options => { - HostingModel = NodeHostingModel.Socket + options.HostingModel = NodeHostingModel.Socket; }); ``` @@ -365,12 +362,11 @@ The default transport may change from `Http` to `Socket` in the near future, bec ### Custom hosting models -If you implement a custom hosting model (by implementing `INodeInstance`), then you can cause it to be used by populating `NodeInstanceFactory` on a `NodeServicesOptions`: +If you implement a custom hosting model (by implementing `INodeInstance`), then you can cause it to be used by populating `NodeInstanceFactory` on your options: ```csharp -var options = new NodeServicesOptions { - NodeInstanceFactory = () => new MyCustomNodeInstance() -}; +services.AddNodeServices(options => +{ + options.NodeInstanceFactory = () => new MyCustomNodeInstance(); +}); ``` - -Now you can pass this `options` object to [`AddNodeServices`](#addnodeservices) or [`CreateNodeServices`](#createnodeservices). diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index 8ee24847..b91c2a6a 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -33,9 +33,8 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) // in your startup file, but then again it might be confusing that you don't need to. if (_nodeServices == null) { - _nodeServices = _fallbackNodeServices = Configuration.CreateNodeServices( - serviceProvider, - new NodeServicesOptions()); + _nodeServices = _fallbackNodeServices = NodeServicesFactory.CreateNodeServices( + new NodeServicesOptions(serviceProvider)); } } diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dc7de6ed..dcc5d4fc 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -40,12 +40,9 @@ public static void UseWebpackDevMiddleware( // because it must *not* restart when files change (if it did, you'd lose all the benefits of Webpack // middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't // as fast as some theoretical future alternative. - var nodeServices = Configuration.CreateNodeServices( - appBuilder.ApplicationServices, - new NodeServicesOptions - { - WatchFileExtensions = new string[] { } // Don't watch anything - }); + var nodeServicesOptions = new NodeServicesOptions(appBuilder.ApplicationServices); + nodeServicesOptions.WatchFileExtensions = new string[] {}; // Don't watch anything + var nodeServices = NodeServicesFactory.CreateNodeServices(nodeServicesOptions); // Get a filename matching the middleware Node script var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware), From 03dcae24071968349b0eeeb8e03018cf562075f4 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 1 Sep 2016 17:52:26 +0100 Subject: [PATCH 043/791] Simplify docs --- src/Microsoft.AspNetCore.NodeServices/README.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/README.md b/src/Microsoft.AspNetCore.NodeServices/README.md index 1a20a8f5..c2c6bbc3 100644 --- a/src/Microsoft.AspNetCore.NodeServices/README.md +++ b/src/Microsoft.AspNetCore.NodeServices/README.md @@ -43,15 +43,9 @@ In that case, you don't need to use NodeServices directly (or install it manuall ## For ASP.NET Core apps -ASP.NET Core has a built-in dependency injection (DI) system. NodeServices is designed to work with this, so you don't have to manage the creation or disposal of instances. +.NET Core has a built-in dependency injection (DI) system. NodeServices is designed to work with this, so you don't have to manage the creation or disposal of instances. -Enable NodeServices in your application by first adding the following to the top of your `Startup.cs` file: - -```csharp -using Microsoft.AspNetCore.NodeServices; -``` - -... and then add to your `ConfigureServices` method in that file: +Enable NodeServices in your application by first adding the following to your `ConfigureServices` method in `Startup.cs`: ```csharp public void ConfigureServices(IServiceCollection services) @@ -93,7 +87,6 @@ If you want to put `addNumber.js` inside a subfolder rather than the root of you In other types of .NET Core app, where you don't have ASP.NET supplying an `IServiceCollection` to you, you'll need to instantiate your own DI container. For example, add a reference to the .NET package `Microsoft.Extensions.DependencyInjection`, and then you can construct an `IServiceCollection`, then register NodeServices as usual: ```csharp -// Remember to add 'using Microsoft.AspNetCore.NodeServices;' at the top of your file var services = new ServiceCollection(); services.AddNodeServices(options => { // Set any properties that you want on 'options' here @@ -133,10 +126,10 @@ AddNodeServices(Action setupAction) This is an extension method on `IServiceCollection`. It registers NodeServices with ASP.NET Core's DI system. Typically you should call this from the `ConfigureServices` method in your `Startup.cs` file. -To access this extension method, you'll need to add the following namespace import to the top of your file: +To access this extension method, you'll need to add the following namespace import to the top of your file, if it isn't already there: ```csharp -using Microsoft.AspNetCore.NodeServices; +using Microsoft.Extensions.DependencyInjection; ``` **Examples** From 377401b5e6b8668f6782a210bc81cc7a766b167e Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 7 Sep 2016 14:00:09 +0100 Subject: [PATCH 044/791] Update README to clarify that you need .NET Core 1.0 RTM --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93224181..287a5fe1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This repo contains: * A Yeoman generator that creates preconfigured app starting points ([guide](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/)) * Samples and docs -Everything here is cross-platform, and works with .NET Core 1.0 RC2 or later on Windows, Linux, or OS X. +Everything here is cross-platform, and works with .NET Core 1.0 (RTM) or later on Windows, Linux, or OS X. ## Creating new applications From 465d0c8d15820c95fd8fc927eacf59df9c338f1a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 7 Sep 2016 17:11:57 +0100 Subject: [PATCH 045/791] Design review: Explicitly disable TypeNameHandling in all Json.NET usage --- .../HostingModels/HttpNodeInstance.cs | 9 +++++---- .../HostingModels/SocketNodeInstance.cs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 9fd72a4d..0ab27825 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -25,9 +25,10 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private static readonly Regex PortMessageRegex = new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$"); - private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings + private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), + TypeNameHandling = TypeNameHandling.None }; private readonly HttpClient _client; @@ -58,7 +59,7 @@ private static string MakeCommandLineOptions(int port) protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) { - var payloadJson = JsonConvert.SerializeObject(invocationInfo, JsonSerializerSettings); + var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json"); var response = await _client.PostAsync("http://localhost/" + _portNumber, payload); @@ -85,7 +86,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat case "application/json": var responseJson = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseJson); + return JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings); case "application/octet-stream": // Streamed responses have to be received as System.IO.Stream instances diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 1a1c74e3..2acfb783 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -29,7 +29,8 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance { private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), + TypeNameHandling = TypeNameHandling.None }; private readonly SemaphoreSlim _connectionCreationSemaphore = new SemaphoreSlim(1); From f358d8e2b2b5913b01aade75b40a2b2e05418fd2 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 7 Sep 2016 17:59:13 +0100 Subject: [PATCH 046/791] In HttpNodeInstance, correctly report response serialisation errors back to .NET (previously, it just timed out) --- .../Content/Node/entrypoint-http.js | 23 ++++++++++++------- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 23 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index 76a00ef7..9d231f3b 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -69,18 +69,21 @@ if (!hasSentResult) { hasSentResult = true; if (errorValue) { - res.statusCode = 500; - if (errorValue.stack) { - res.end(errorValue.stack); - } - else { - res.end(errorValue.toString()); - } + respondWithError(res, errorValue); } else if (typeof successValue !== 'string') { // Arbitrary object/number/etc - JSON-serialize it + var successValueJson = void 0; + try { + successValueJson = JSON.stringify(successValue); + } + catch (ex) { + // JSON serialization error - pass it back to .NET + respondWithError(res, ex); + return; + } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValue)); + res.end(JSON.stringify(successValueJson)); } else { // String - can bypass JSON-serialization altogether @@ -129,6 +132,10 @@ .on('data', function (chunk) { requestBodyAsString += chunk; }) .on('end', function () { callback(JSON.parse(requestBodyAsString)); }); } + function respondWithError(res, errorValue) { + res.statusCode = 500; + res.end(errorValue.stack || errorValue.toString()); + } /***/ }, diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index 431810e6..5bb8623d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -17,17 +17,19 @@ const server = http.createServer((req, res) => { if (!hasSentResult) { hasSentResult = true; if (errorValue) { - res.statusCode = 500; - - if (errorValue.stack) { - res.end(errorValue.stack); - } else { - res.end(errorValue.toString()); - } + respondWithError(res, errorValue); } else if (typeof successValue !== 'string') { // Arbitrary object/number/etc - JSON-serialize it + let successValueJson: string; + try { + successValueJson = JSON.stringify(successValue); + } catch (ex) { + // JSON serialization error - pass it back to .NET + respondWithError(res, ex); + return; + } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValue)); + res.end(JSON.stringify(successValueJson)); } else { // String - can bypass JSON-serialization altogether res.setHeader('Content-Type', 'text/plain'); @@ -82,3 +84,8 @@ function readRequestBodyAsJson(request, callback) { .on('data', chunk => { requestBodyAsString += chunk; }) .on('end', () => { callback(JSON.parse(requestBodyAsString)); }); } + +function respondWithError(res: http.ServerResponse, errorValue: any) { + res.statusCode = 500; + res.end(errorValue.stack || errorValue.toString()); +} From 279986129659faae8a110ca62cb4cf3965e0604f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 10:56:50 +0100 Subject: [PATCH 047/791] Support cancellation of NodeServices invocations --- .../HostingModels/HttpNodeInstance.cs | 15 ++++++---- .../HostingModels/INodeInstance.cs | 3 +- .../HostingModels/OutOfProcessNodeInstance.cs | 16 ++++++---- .../HostingModels/SocketNodeInstance.cs | 25 +++++++++------- .../INodeServices.cs | 4 +++ .../NodeServicesImpl.cs | 19 +++++++++--- .../Util/TaskExtensions.cs | 30 +++++++++++++++++++ 7 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 0ab27825..0c113718 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -57,15 +58,17 @@ private static string MakeCommandLineOptions(int port) return $"--port {port}"; } - protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) + protected override async Task InvokeExportAsync( + NodeInvocationInfo invocationInfo, CancellationToken cancellationToken) { var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings); var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync("http://localhost/" + _portNumber, payload); + var response = await _client.PostAsync("http://localhost/" + _portNumber, payload, cancellationToken); if (!response.IsSuccessStatusCode) { - var responseErrorString = await response.Content.ReadAsStringAsync(); + // Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled + var responseErrorString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); throw new Exception("Call to Node module failed with error: " + responseErrorString); } @@ -81,11 +84,11 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat typeof(T).FullName); } - var responseString = await response.Content.ReadAsStringAsync(); + var responseString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); return (T)(object)responseString; case "application/json": - var responseJson = await response.Content.ReadAsStringAsync(); + var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); return JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings); case "application/octet-stream": @@ -97,7 +100,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat typeof(T).FullName + ". Instead you must use the generic type System.IO.Stream."); } - return (T)(object)(await response.Content.ReadAsStreamAsync()); + return (T)(object)(await response.Content.ReadAsStreamAsync().OrThrowOnCancellation(cancellationToken)); default: throw new InvalidOperationException("Unexpected response content type: " + responseContentType.MediaType); diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs index cac69f2a..68a43195 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs @@ -1,10 +1,11 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.NodeServices.HostingModels { public interface INodeInstance : IDisposable { - Task InvokeExportAsync(string moduleName, string exportNameOrNull, params object[] args); + Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 641c098d..9a583d3a 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -67,7 +68,8 @@ public OutOfProcessNodeInstance( ConnectToInputOutputStreams(); } - public async Task InvokeExportAsync(string moduleName, string exportNameOrNull, params object[] args) + public async Task InvokeExportAsync( + CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args) { if (_nodeProcess.HasExited || _nodeProcessNeedsRestart) { @@ -79,15 +81,17 @@ public async Task InvokeExportAsync(string moduleName, string exportNameOr throw new NodeInvocationException(message, null, nodeInstanceUnavailable: true); } - // Wait until the connection is established. This will throw if the connection fails to initialize. - await _connectionIsReadySource.Task; + // Wait until the connection is established. This will throw if the connection fails to initialize, + // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" + // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. + await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); return await InvokeExportAsync(new NodeInvocationInfo { ModuleName = moduleName, ExportedFunctionName = exportNameOrNull, Args = args - }); + }, cancellationToken); } public void Dispose() @@ -96,7 +100,9 @@ public void Dispose() GC.SuppressFinalize(this); } - protected abstract Task InvokeExportAsync(NodeInvocationInfo invocationInfo); + protected abstract Task InvokeExportAsync( + NodeInvocationInfo invocationInfo, + CancellationToken cancellationToken); // This method is virtual, as it provides a way to override the NODE_PATH or the path to node.exe protected virtual ProcessStartInfo PrepareNodeProcessStartInfo( diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 2acfb783..2acfc0ed 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -57,7 +57,7 @@ public SocketNodeInstance(string projectPath, string[] watchFileExtensions, stri _socketAddress = socketAddress; } - protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo) + protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo, CancellationToken cancellationToken) { if (_connectionHasFailed) { @@ -70,7 +70,12 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat if (_virtualConnectionClient == null) { - await EnsureVirtualConnectionClientCreated(); + // Although we could pass the cancellationToken into EnsureVirtualConnectionClientCreated and + // have it signal cancellations upstream, that would be a bad thing to do, because all callers + // 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 + // callers. So, each caller just independently stops awaiting connection if that call is cancelled. + await EnsureVirtualConnectionClientCreated().OrThrowOnCancellation(cancellationToken); } // For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new @@ -83,7 +88,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat virtualConnection = _virtualConnectionClient.OpenVirtualConnection(); // Send request - await WriteJsonLineAsync(virtualConnection, invocationInfo); + await WriteJsonLineAsync(virtualConnection, invocationInfo, cancellationToken); // Determine what kind of response format is expected if (typeof(T) == typeof(Stream)) @@ -96,7 +101,7 @@ protected override async Task InvokeExportAsync(NodeInvocationInfo invocat else { // Parse and return non-streamed JSON response - var response = await ReadJsonAsync>(virtualConnection); + var response = await ReadJsonAsync>(virtualConnection, cancellationToken); if (response.ErrorMessage != null) { throw new NodeInvocationException(response.ErrorMessage, response.ErrorDetails); @@ -163,27 +168,27 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - private static async Task WriteJsonLineAsync(Stream stream, object serializableObject) + private static async Task WriteJsonLineAsync(Stream stream, object serializableObject, CancellationToken cancellationToken) { var json = JsonConvert.SerializeObject(serializableObject, jsonSerializerSettings); var bytes = Encoding.UTF8.GetBytes(json + '\n'); - await stream.WriteAsync(bytes, 0, bytes.Length); + await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); } - private static async Task ReadJsonAsync(Stream stream) + private static async Task ReadJsonAsync(Stream stream, CancellationToken cancellationToken) { - var json = Encoding.UTF8.GetString(await ReadAllBytesAsync(stream)); + var json = Encoding.UTF8.GetString(await ReadAllBytesAsync(stream, cancellationToken)); return JsonConvert.DeserializeObject(json, jsonSerializerSettings); } - private static async Task ReadAllBytesAsync(Stream input) + private static async Task ReadAllBytesAsync(Stream input, CancellationToken cancellationToken) { byte[] buffer = new byte[16 * 1024]; using (var ms = new MemoryStream()) { int read; - while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0) + while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) { ms.Write(buffer, 0, read); } diff --git a/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs b/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs index 3aa09e3b..fbc32d04 100644 --- a/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs +++ b/src/Microsoft.AspNetCore.NodeServices/INodeServices.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.NodeServices @@ -6,8 +7,11 @@ namespace Microsoft.AspNetCore.NodeServices public interface INodeServices : IDisposable { Task InvokeAsync(string moduleName, params object[] args); + Task InvokeAsync(CancellationToken cancellationToken, string moduleName, params object[] args); Task InvokeExportAsync(string moduleName, string exportedFunctionName, params object[] args); + Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportedFunctionName, params object[] args); + [Obsolete("Use InvokeAsync instead")] Task Invoke(string moduleName, params object[] args); diff --git a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs index 037cc96d..38d30558 100644 --- a/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs +++ b/src/Microsoft.AspNetCore.NodeServices/NodeServicesImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.NodeServices.HostingModels; @@ -34,19 +35,29 @@ public Task InvokeAsync(string moduleName, params object[] args) return InvokeExportAsync(moduleName, null, args); } + public Task InvokeAsync(CancellationToken cancellationToken, string moduleName, params object[] args) + { + return InvokeExportAsync(cancellationToken, moduleName, null, args); + } + public Task InvokeExportAsync(string moduleName, string exportedFunctionName, params object[] args) { - return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, allowRetry: true); + return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ true, CancellationToken.None); + } + + public Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportedFunctionName, params object[] args) + { + return InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ true, cancellationToken); } - public async Task InvokeExportWithPossibleRetryAsync(string moduleName, string exportedFunctionName, object[] args, bool allowRetry) + public async Task InvokeExportWithPossibleRetryAsync(string moduleName, string exportedFunctionName, object[] args, bool allowRetry, CancellationToken cancellationToken) { ThrowAnyOutstandingDelayedDisposalException(); var nodeInstance = GetOrCreateCurrentNodeInstance(); try { - return await nodeInstance.InvokeExportAsync(moduleName, exportedFunctionName, args); + return await nodeInstance.InvokeExportAsync(cancellationToken, moduleName, exportedFunctionName, args); } catch (NodeInvocationException ex) { @@ -69,7 +80,7 @@ public async Task InvokeExportWithPossibleRetryAsync(string moduleName, st // One the next call, don't allow retries, because we could get into an infinite retry loop, or a long retry // loop that masks an underlying problem. A newly-created Node instance should be able to accept invocations, // or something more serious must be wrong. - return await InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, allowRetry: false); + return await InvokeExportWithPossibleRetryAsync(moduleName, exportedFunctionName, args, /* allowRetry */ false, cancellationToken); } else { diff --git a/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs b/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs new file mode 100644 index 00000000..75cfdb16 --- /dev/null +++ b/src/Microsoft.AspNetCore.NodeServices/Util/TaskExtensions.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.NodeServices +{ + internal static class TaskExtensions + { + public static Task OrThrowOnCancellation(this 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); + } + + public static Task OrThrowOnCancellation(this 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( + t => t.Result, // If the task completes, pass through its result + cancellationToken, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + } +} \ No newline at end of file From 041d173f56692a0b8e624a1db34c4560fe14e6df Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:08:42 +0100 Subject: [PATCH 048/791] All NodeServices invocations now have a default timeout, plus a descriptive exception if that happens --- .../Configuration/NodeServicesFactory.cs | 4 +- .../Configuration/NodeServicesOptions.cs | 4 + .../HostingModels/HttpNodeInstance.cs | 4 +- .../HostingModels/OutOfProcessNodeInstance.cs | 80 ++++++++++++++++--- .../HostingModels/SocketNodeInstance.cs | 3 +- 5 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs index fc038f2c..d6660c0d 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs @@ -29,11 +29,11 @@ private static INodeInstance CreateNodeInstance(NodeServicesOptions options) { case NodeHostingModel.Http: return new HttpNodeInstance(options.ProjectPath, options.WatchFileExtensions, options.NodeInstanceOutputLogger, - options.EnvironmentVariables, options.LaunchWithDebugging, options.DebuggingPort, /* port */ 0); + 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.LaunchWithDebugging, options.DebuggingPort); + options.EnvironmentVariables, options.InvocationTimeoutMilliseconds, options.LaunchWithDebugging, options.DebuggingPort); default: throw new ArgumentException("Unknown hosting model: " + options.HostingModel); } diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs index edf9ae2c..dbf1ce60 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs +++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.NodeServices public class NodeServicesOptions { public const NodeHostingModel DefaultNodeHostingModel = NodeHostingModel.Http; + internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds); + private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000; private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices"; private static readonly string[] DefaultWatchFileExtensions = { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" }; @@ -22,6 +24,7 @@ public NodeServicesOptions(IServiceProvider serviceProvider) } EnvironmentVariables = new Dictionary(); + InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds; HostingModel = DefaultNodeHostingModel; WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); @@ -49,5 +52,6 @@ public NodeServicesOptions(IServiceProvider serviceProvider) public bool LaunchWithDebugging { get; set; } public IDictionary EnvironmentVariables { get; set; } public int DebuggingPort { get; set; } + public int InvocationTimeoutMilliseconds { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs index 0c113718..1ab4c137 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs @@ -37,7 +37,8 @@ internal class HttpNodeInstance : OutOfProcessNodeInstance private int _portNumber; public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogger nodeInstanceOutputLogger, - IDictionary environmentVars, bool launchWithDebugging, int debuggingPort, int port = 0) + IDictionary environmentVars, int invocationTimeoutMilliseconds, bool launchWithDebugging, + int debuggingPort, int port = 0) : base( EmbeddedResourceReader.Read( typeof(HttpNodeInstance), @@ -47,6 +48,7 @@ public HttpNodeInstance(string projectPath, string[] watchFileExtensions, ILogge MakeCommandLineOptions(port), nodeInstanceOutputLogger, environmentVars, + invocationTimeoutMilliseconds, launchWithDebugging, debuggingPort) { diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs index 9a583d3a..3957cb08 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs @@ -37,6 +37,7 @@ npm install -g node-inspector private bool _disposed; private readonly StringAsTempFile _entryPointScript; private FileSystemWatcher _fileSystemWatcher; + private int _invocationTimeoutMilliseconds; private readonly Process _nodeProcess; private int? _nodeDebuggingPort; private bool _nodeProcessNeedsRestart; @@ -49,6 +50,7 @@ public OutOfProcessNodeInstance( string commandLineArguments, ILogger nodeOutputLogger, IDictionary environmentVars, + int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort) { @@ -59,6 +61,7 @@ public OutOfProcessNodeInstance( OutputLogger = nodeOutputLogger; _entryPointScript = new StringAsTempFile(entryPointScript); + _invocationTimeoutMilliseconds = invocationTimeoutMilliseconds; var startInfo = PrepareNodeProcessStartInfo(_entryPointScript.FileName, projectPath, commandLineArguments, environmentVars, launchWithDebugging, debuggingPort); @@ -81,17 +84,74 @@ public async Task InvokeExportAsync( throw new NodeInvocationException(message, null, nodeInstanceUnavailable: true); } - // Wait until the connection is established. This will throw if the connection fails to initialize, - // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" - // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. - await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); - - return await InvokeExportAsync(new NodeInvocationInfo + // Construct a new cancellation token that combines the supplied token with the configured invocation + // timeout. Technically we could avoid wrapping the cancellationToken if no timeout is configured, + // but that's not really a major use case, since timeouts are enabled by default. + using (var timeoutSource = new CancellationTokenSource()) + using (var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token)) { - ModuleName = moduleName, - ExportedFunctionName = exportNameOrNull, - Args = args - }, cancellationToken); + if (_invocationTimeoutMilliseconds > 0) + { + timeoutSource.CancelAfter(_invocationTimeoutMilliseconds); + } + + // By overwriting the supplied cancellation token, we ensure that it isn't accidentally used + // below. We only want to pass through the token that respects timeouts. + cancellationToken = combinedCancellationTokenSource.Token; + var connectionDidSucceed = false; + + try + { + // Wait until the connection is established. This will throw if the connection fails to initialize, + // or if cancellation is requested first. Note that we can't really cancel the "establishing connection" + // task because that's shared with all callers, but we can stop waiting for it if this call is cancelled. + await _connectionIsReadySource.Task.OrThrowOnCancellation(cancellationToken); + connectionDidSucceed = true; + + return await InvokeExportAsync(new NodeInvocationInfo + { + ModuleName = moduleName, + ExportedFunctionName = exportNameOrNull, + Args = args + }, cancellationToken); + } + catch (TaskCanceledException) + { + if (timeoutSource.IsCancellationRequested) + { + // It was very common for developers to report 'TaskCanceledException' when encountering almost any + // trouble when using NodeServices. Now we have a default invocation timeout, and attempt to give + // a more descriptive exception message if it happens. + if (!connectionDidSucceed) + { + // This is very unlikely, but for debugging, it's still useful to differentiate it from the + // case below. + throw new NodeInvocationException( + $"Attempt to connect to Node timed out after {_invocationTimeoutMilliseconds}ms.", + string.Empty); + } + else + { + // Developers encounter this fairly often (if their Node code fails without invoking the callback, + // all that the .NET side knows is that the invocation eventually times out). Previously, this surfaced + // as a TaskCanceledException, but this led to a lot of issue reports. Now we throw the following + // descriptive error. + throw new NodeInvocationException( + $"The Node invocation timed out after {_invocationTimeoutMilliseconds}ms.", + $"You can change the timeout duration by setting the {NodeServicesOptions.TimeoutConfigPropertyName} " + + $"property on {nameof(NodeServicesOptions)}.\n\n" + + "The first debugging step is to ensure that your Node.js function always invokes the supplied " + + "callback (or throws an exception synchronously), even if it encounters an error. Otherwise, " + + "the .NET code has no way to know that it is finished or has failed." + ); + } + } + else + { + throw; + } + } + } } public void Dispose() diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs index 2acfc0ed..2ee2aa4e 100644 --- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs +++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs @@ -41,7 +41,7 @@ internal class SocketNodeInstance : OutOfProcessNodeInstance public SocketNodeInstance(string projectPath, string[] watchFileExtensions, string socketAddress, ILogger nodeInstanceOutputLogger, IDictionary environmentVars, - bool launchWithDebugging, int debuggingPort) + int invocationTimeoutMilliseconds, bool launchWithDebugging, int debuggingPort) : base( EmbeddedResourceReader.Read( typeof(SocketNodeInstance), @@ -51,6 +51,7 @@ public SocketNodeInstance(string projectPath, string[] watchFileExtensions, stri MakeNewCommandLineOptions(socketAddress), nodeInstanceOutputLogger, environmentVars, + invocationTimeoutMilliseconds, launchWithDebugging, debuggingPort) { From 411100478a31bb9640afb339343575bed8184fc6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:14:45 +0100 Subject: [PATCH 049/791] Fix double-encoding typo --- .../Content/Node/entrypoint-http.js | 2 +- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index 9d231f3b..cfa15bcf 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -83,7 +83,7 @@ return; } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValueJson)); + res.end(successValueJson); } else { // String - can bypass JSON-serialization altogether diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index 5bb8623d..f8e619d2 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -29,7 +29,7 @@ const server = http.createServer((req, res) => { return; } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(successValueJson)); + res.end(successValueJson); } else { // String - can bypass JSON-serialization altogether res.setHeader('Content-Type', 'text/plain'); From 4ca1669db130970b7ce9f05d8e71de4e28e5a4e0 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:56:05 +0100 Subject: [PATCH 050/791] Prerendering imposes its own (overridable) timeout with descriptive error --- .../Prerendering/PrerenderTagHelper.cs | 7 +++- .../Prerendering/Prerenderer.cs | 6 ++- .../aspnet-prerendering/src/Prerendering.ts | 39 ++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index b91c2a6a..de02c90f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -18,6 +18,7 @@ public class PrerenderTagHelper : TagHelper private const string PrerenderExportAttributeName = "asp-prerender-export"; private const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config"; private const string PrerenderDataAttributeName = "asp-prerender-data"; + private const string PrerenderTimeoutAttributeName = "asp-prerender-timeout"; private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI private readonly string _applicationBasePath; @@ -50,6 +51,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider) [HtmlAttributeName(PrerenderDataAttributeName)] public object CustomDataParameter { get; set; } + [HtmlAttributeName(PrerenderTimeoutAttributeName)] + public int TimeoutMillisecondsParameter { get; set; } + [HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; } @@ -79,7 +83,8 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu }, unencodedAbsoluteUrl, unencodedPathAndQuery, - CustomDataParameter); + CustomDataParameter, + TimeoutMillisecondsParameter); output.Content.SetHtmlContent(result.Html); // Also attach any specified globals to the 'window' object. This is useful for transferring diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs index 40286d14..9d8724e4 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs @@ -23,7 +23,8 @@ public static Task RenderToString( JavaScriptModuleExport bootModule, string requestAbsoluteUrl, string requestPathAndQuery, - object customDataParameter) + object customDataParameter, + int timeoutMilliseconds) { return nodeServices.InvokeExportAsync( NodeScript.Value.FileName, @@ -32,7 +33,8 @@ public static Task RenderToString( bootModule, requestAbsoluteUrl, requestPathAndQuery, - customDataParameter); + customDataParameter, + timeoutMilliseconds); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts index 4d9269b2..3a14f94f 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -5,6 +5,8 @@ import * as domain from 'domain'; import { run as domainTaskRun } from 'domain-task/main'; import { baseUrl } from 'domain-task/fetch'; +const defaultTimeoutMilliseconds = 30 * 1000; + export interface RenderToStringCallback { (error: any, result: RenderToStringResult): void; } @@ -33,7 +35,7 @@ export interface BootModuleInfo { webpackConfig?: string; } -export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any) { +export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) { findBootFunc(applicationBasePath, bootModule, (findBootFuncError, bootFunc) => { if (findBootFuncError) { callback(findBootFuncError, null); @@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context baseUrl(absoluteRequestUrl); + // Begin rendering, and apply a timeout + const bootFuncPromise = bootFunc(params); + if (!bootFuncPromise || typeof bootFuncPromise.then !== 'function') { + callback(`Prerendering failed because the boot function in ${bootModule.moduleName} did not return a promise.`, null); + return; + } + const timeoutMilliseconds = overrideTimeoutMilliseconds || defaultTimeoutMilliseconds; // e.g., pass -1 to override as 'never time out' + const bootFuncPromiseWithTimeout = timeoutMilliseconds > 0 + ? wrapWithTimeout(bootFuncPromise, timeoutMilliseconds, + `Prerendering timed out after ${timeoutMilliseconds}ms because the boot function in '${bootModule.moduleName}' ` + + 'returned a promise that did not resolve or reject. Make sure that your boot function always resolves or ' + + 'rejects its promise. You can change the timeout value using the \'asp-prerender-timeout\' tag helper.') + : bootFuncPromise; + // Actually perform the rendering - bootFunc(params).then(successResult => { + bootFuncPromiseWithTimeout.then(successResult => { callback(null, { html: successResult.html, globals: successResult.globals }); }, error => { callback(error, null); @@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase }); } +function wrapWithTimeout(promise: Promise, timeoutMilliseconds: number, timeoutRejectionValue: any): Promise { + return new Promise((resolve, reject) => { + const timeoutTimer = setTimeout(() => { + reject(timeoutRejectionValue); + }, timeoutMilliseconds); + + promise.then( + resolvedValue => { + clearTimeout(timeoutTimer); + resolve(resolvedValue); + }, + rejectedValue => { + clearTimeout(timeoutTimer); + reject(rejectedValue); + } + ) + }); +} + function findBootModule(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, foundModule: T) => void) { const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName); if (bootModule.webpackConfig) { From 1f2168949d54ea3cdb7573fecff7a7dc0932dbfc Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 12:56:46 +0100 Subject: [PATCH 051/791] Publish updated aspnet-prerendering NPM package --- .../npm/aspnet-prerendering/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index 156a6f1e..4fc59a56 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-prerendering", - "version": "1.0.4", + "version": "1.0.5", "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { From 5fcce843bad20262d91bf9be31544382e17eb920 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 14:32:25 +0100 Subject: [PATCH 052/791] Workaround to fix #235 (add placeholder inside node_modules) --- .gitignore | 9 ++++++++- templates/Angular2Spa/node_modules/_placeholder.txt | 7 +++++++ templates/Angular2Spa/template_gitignore | 5 ++++- templates/KnockoutSpa/node_modules/_placeholder.txt | 7 +++++++ templates/KnockoutSpa/template_gitignore | 5 ++++- templates/ReactReduxSpa/node_modules/_placeholder.txt | 7 +++++++ templates/ReactReduxSpa/template_gitignore | 5 ++++- templates/ReactSpa/node_modules/_placeholder.txt | 7 +++++++ templates/ReactSpa/template_gitignore | 5 ++++- .../WebApplicationBasic/node_modules/_placeholder.txt | 7 +++++++ templates/WebApplicationBasic/template_gitignore | 5 ++++- 11 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 templates/Angular2Spa/node_modules/_placeholder.txt create mode 100644 templates/KnockoutSpa/node_modules/_placeholder.txt create mode 100644 templates/ReactReduxSpa/node_modules/_placeholder.txt create mode 100644 templates/ReactSpa/node_modules/_placeholder.txt create mode 100644 templates/WebApplicationBasic/node_modules/_placeholder.txt diff --git a/.gitignore b/.gitignore index 06cc11a3..6df4b42c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,13 @@ npm-debug.log # files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore # file which gets renamed after the files are copied. And so any files that need to be excluded in the source # repo have to be excluded here. -/templates/*/node_modules/ + +# Note that we need to exclude node_modules/** (i.e., subdirs, not the whole of node_modules) because we do need to +# include the _placeholder.txt files, and can't do that using gitignore exclusion because developers aren't promoted to +# commit files included that way. This is all a workaround for Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/templates/*/node_modules/** /templates/*/wwwroot/dist/ .vscode/ + +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +!/templates/*/node_modules/_placeholder.txt diff --git a/templates/Angular2Spa/node_modules/_placeholder.txt b/templates/Angular2Spa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/Angular2Spa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +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. diff --git a/templates/Angular2Spa/template_gitignore b/templates/Angular2Spa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/Angular2Spa/template_gitignore +++ b/templates/Angular2Spa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/KnockoutSpa/node_modules/_placeholder.txt b/templates/KnockoutSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/KnockoutSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +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. diff --git a/templates/KnockoutSpa/template_gitignore b/templates/KnockoutSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/KnockoutSpa/template_gitignore +++ b/templates/KnockoutSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/ReactReduxSpa/node_modules/_placeholder.txt b/templates/ReactReduxSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/ReactReduxSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +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. diff --git a/templates/ReactReduxSpa/template_gitignore b/templates/ReactReduxSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/ReactReduxSpa/template_gitignore +++ b/templates/ReactReduxSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/ReactSpa/node_modules/_placeholder.txt b/templates/ReactSpa/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/ReactSpa/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +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. diff --git a/templates/ReactSpa/template_gitignore b/templates/ReactSpa/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/ReactSpa/template_gitignore +++ b/templates/ReactSpa/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ diff --git a/templates/WebApplicationBasic/node_modules/_placeholder.txt b/templates/WebApplicationBasic/node_modules/_placeholder.txt new file mode 100644 index 00000000..09ae8c0c --- /dev/null +++ b/templates/WebApplicationBasic/node_modules/_placeholder.txt @@ -0,0 +1,7 @@ +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. diff --git a/templates/WebApplicationBasic/template_gitignore b/templates/WebApplicationBasic/template_gitignore index d410b8bb..2ee8e59c 100644 --- a/templates/WebApplicationBasic/template_gitignore +++ b/templates/WebApplicationBasic/template_gitignore @@ -184,9 +184,12 @@ ClientBin/ *.dbproj.schemaview *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 +/node_modules/** +!/node_modules/_placeholder.txt + # RIA/Silverlight projects Generated_Code/ From 5750c4aab70749882e0c8fb99fa315835315e00a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 14:44:37 +0100 Subject: [PATCH 053/791] Publish updated generator-aspnetcore-spa package (0.2.5) --- templates/package-builder/src/yeoman/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 82025189..f68b9aeb 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.4", + "version": "0.2.5", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 0bcf4b0700c7bf4cb1de6b4124f0ee87427351b5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 8 Sep 2016 15:58:47 +0100 Subject: [PATCH 054/791] Further work on fix for #235 (solving additional problem that 'npm publish' is hardcoded to exclude node_modules dirs) --- .gitignore | 9 +-------- ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 ...lder.txt => template_nodemodules_placeholder.txt} | 0 templates/package-builder/src/build/build.ts | 6 +++++- templates/package-builder/src/yeoman/app/index.ts | 12 ++++++++++++ templates/package-builder/src/yeoman/package.json | 2 +- 9 files changed, 19 insertions(+), 10 deletions(-) rename templates/Angular2Spa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/KnockoutSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/ReactReduxSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/ReactSpa/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) rename templates/WebApplicationBasic/{node_modules/_placeholder.txt => template_nodemodules_placeholder.txt} (100%) diff --git a/.gitignore b/.gitignore index 6df4b42c..06cc11a3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,13 +34,6 @@ npm-debug.log # files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore # file which gets renamed after the files are copied. And so any files that need to be excluded in the source # repo have to be excluded here. - -# Note that we need to exclude node_modules/** (i.e., subdirs, not the whole of node_modules) because we do need to -# include the _placeholder.txt files, and can't do that using gitignore exclusion because developers aren't promoted to -# commit files included that way. This is all a workaround for Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 -/templates/*/node_modules/** +/templates/*/node_modules/ /templates/*/wwwroot/dist/ .vscode/ - -# Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 -!/templates/*/node_modules/_placeholder.txt diff --git a/templates/Angular2Spa/node_modules/_placeholder.txt b/templates/Angular2Spa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/Angular2Spa/node_modules/_placeholder.txt rename to templates/Angular2Spa/template_nodemodules_placeholder.txt diff --git a/templates/KnockoutSpa/node_modules/_placeholder.txt b/templates/KnockoutSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/KnockoutSpa/node_modules/_placeholder.txt rename to templates/KnockoutSpa/template_nodemodules_placeholder.txt diff --git a/templates/ReactReduxSpa/node_modules/_placeholder.txt b/templates/ReactReduxSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/ReactReduxSpa/node_modules/_placeholder.txt rename to templates/ReactReduxSpa/template_nodemodules_placeholder.txt diff --git a/templates/ReactSpa/node_modules/_placeholder.txt b/templates/ReactSpa/template_nodemodules_placeholder.txt similarity index 100% rename from templates/ReactSpa/node_modules/_placeholder.txt rename to templates/ReactSpa/template_nodemodules_placeholder.txt diff --git a/templates/WebApplicationBasic/node_modules/_placeholder.txt b/templates/WebApplicationBasic/template_nodemodules_placeholder.txt similarity index 100% rename from templates/WebApplicationBasic/node_modules/_placeholder.txt rename to templates/WebApplicationBasic/template_nodemodules_placeholder.txt diff --git a/templates/package-builder/src/build/build.ts b/templates/package-builder/src/build/build.ts index 546ff891..a0433bd4 100644 --- a/templates/package-builder/src/build/build.ts +++ b/templates/package-builder/src/build/build.ts @@ -110,7 +110,11 @@ function buildDotNetNewNuGetPackage() { const projectGuid = '00000000-0000-0000-0000-000000000000'; const filenameReplacements = [ { from: /.*\.xproj$/, to: `${sourceProjectName}.xproj` }, - { from: /\btemplate_gitignore$/, to: '.gitignore' } + { from: /\btemplate_gitignore$/, to: '.gitignore' }, + + // Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 + // For details, see the comment in ../yeoman/app/index.ts + { from: /\btemplate_nodemodules_placeholder.txt$/, to: 'node_modules/_placeholder.txt' } ]; const contentReplacements = [ { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: `${projectGuid}` }, diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index ecb53667..04687a08 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -52,6 +52,18 @@ class MyGenerator extends yeoman.Base { outputFn = path.join(path.dirname(fn), '.gitignore'); } + // Likewise, output template_nodemodules_placeholder.txt as node_modules/_placeholder.txt + // This is a workaround for https://github.com/aspnet/JavaScriptServices/issues/235. We need the new project + // to have a nonempty node_modules dir as far as *source control* is concerned. So, there's a gitignore + // rule that explicitly causes node_modules/_placeholder.txt to be tracked in source control. But how + // does that file get there in the first place? It's not enough for such a file to exist when the + // generator-aspnetcore-spa NPM package is published, because NPM doesn't allow any directories called + // node_modules to exist in the package. So we have a file with at a different location, and move it + // to node_modules as part of executing the template. + if (path.basename(fn) === 'template_nodemodules_placeholder.txt') { + outputFn = path.join(path.dirname(fn), 'node_modules', '_placeholder.txt'); + } + this.fs.copyTpl( path.join(templateRoot, fn), this.destinationPath(outputFn), diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index f68b9aeb..49302f90 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.5", + "version": "0.2.6", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 874575ba92ee4438a9510141c989b298324c6ec3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 9 Sep 2016 09:44:16 +0100 Subject: [PATCH 055/791] Fix instructions for running samples. Fixes #301 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 287a5fe1..09881eb6 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,9 @@ Also in this repo, [the `samples` directory](https://github.com/aspnet/JavaScrip **To run the samples:** * Clone this repo - * Change directory to the same you want to run (e.g., `cd samples/angular/MusicStore`) - * Restore dependencies (run `dotnet restore` and `npm install`). + * At the repo's root directory (the one containing `src`, `samples`, etc.), run `dotnet restore` + * Change directory to the sample you want to run (e.g., `cd samples/angular/MusicStore`) + * Restore Node dependencies by running `npm install` * If you're trying to run the Angular 2 "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this. * Run the application (`dotnet run`) * Browse to [http://localhost:5000](http://localhost:5000) From c2c45b04df4ecff721c407ff6fc57d082fe92712 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:06:36 +0100 Subject: [PATCH 056/791] In preparation for supporting redirections, aspnet-prerendering now passes through all boot func resolution props to .NET code --- .../npm/aspnet-prerendering/package.json | 2 +- .../npm/aspnet-prerendering/src/Prerendering.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index 4fc59a56..5c188ebd 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-prerendering", - "version": "1.0.5", + "version": "1.0.6", "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts index 3a14f94f..6740cc93 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -84,7 +84,7 @@ export function renderToString(callback: RenderToStringCallback, applicationBase // Actually perform the rendering bootFuncPromiseWithTimeout.then(successResult => { - callback(null, { html: successResult.html, globals: successResult.globals }); + callback(null, successResult); }, error => { callback(error, null); }); From 1be9102aea7f0746a9b1219191c2abad70804f49 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:09:44 +0100 Subject: [PATCH 057/791] Prerendering server-side code can now issue redirections. Fixes #280 --- .../Prerendering/PrerenderTagHelper.cs | 9 +++++++++ .../Prerendering/RenderToStringResult.cs | 1 + 2 files changed, 10 insertions(+) diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs index de02c90f..7bce95c6 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs @@ -85,6 +85,15 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu unencodedPathAndQuery, CustomDataParameter, TimeoutMillisecondsParameter); + + if (!string.IsNullOrEmpty(result.RedirectUrl)) + { + // It's a redirection + ViewContext.HttpContext.Response.Redirect(result.RedirectUrl); + return; + } + + // It's some HTML to inject output.Content.SetHtmlContent(result.Html); // Also attach any specified globals to the 'window' object. This is useful for transferring diff --git a/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs index 1d5b482e..cae8d631 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Prerendering/RenderToStringResult.cs @@ -6,5 +6,6 @@ public class RenderToStringResult { public JObject Globals { get; set; } public string Html { get; set; } + public string RedirectUrl { get; set; } } } \ No newline at end of file From 28550784edb6c9c453a8983285dc0d6a9aaa12a9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:12:59 +0100 Subject: [PATCH 058/791] ReactReduxSpa's boot-server now supports redirections issued by react-router --- templates/ReactReduxSpa/ClientApp/boot-server.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/templates/ReactReduxSpa/ClientApp/boot-server.tsx b/templates/ReactReduxSpa/ClientApp/boot-server.tsx index fa3289ea..62aefcfa 100644 --- a/templates/ReactReduxSpa/ClientApp/boot-server.tsx +++ b/templates/ReactReduxSpa/ClientApp/boot-server.tsx @@ -5,15 +5,22 @@ import { match, RouterContext } from 'react-router'; import createMemoryHistory from 'history/lib/createMemoryHistory'; import routes from './routes'; import configureStore from './configureStore'; +type BootResult = { html?: string, globals?: { [key: string]: any }, redirectUrl?: string}; export default function (params: any): Promise<{ html: string }> { - return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => { + return new Promise((resolve, reject) => { // Match the incoming request against the list of client-side routes match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => { if (error) { throw error; } + // If there's a redirection, just send this information back to the host application + if (redirectLocation) { + resolve({ redirectUrl: redirectLocation.pathname }); + return; + } + // If it didn't match any route, renderProps will be undefined if (!renderProps) { throw new Error(`The location '${ params.url }' doesn't match any route configured in react-router.`); From b4bec30b0f1610f759bb5dd369a24bd26484ba86 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:31:36 +0100 Subject: [PATCH 059/791] Clean up dependencies vs devDependencies in templates --- templates/Angular2Spa/package.json | 32 +++++++++++++--------------- templates/KnockoutSpa/package.json | 4 +--- templates/ReactReduxSpa/package.json | 24 ++++++++++----------- templates/ReactSpa/package.json | 12 +++++------ 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 37970041..04fab270 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,23 +1,6 @@ { "name": "WebApplicationBasic", "version": "0.0.0", - "devDependencies": { - "aspnet-webpack": "^1.0.6", - "bootstrap": "^3.3.6", - "css-loader": "^0.23.1", - "expose-loader": "^0.7.1", - "extendify": "^1.0.0", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.8.5", - "jquery": "^2.2.1", - "raw-loader": "^0.5.1", - "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", - "url-loader": "^0.5.7", - "webpack": "^1.12.14", - "webpack-hot-middleware": "^2.10.0" - }, "dependencies": { "@angular/common": "2.0.0-rc.4", "@angular/compiler": "2.0.0-rc.4", @@ -29,12 +12,27 @@ "@angular/router": "3.0.0-beta.2", "angular2-universal": "^0.104.5", "aspnet-prerendering": "^1.0.2", + "aspnet-webpack": "^1.0.6", + "bootstrap": "^3.3.6", "css": "^2.2.1", + "css-loader": "^0.23.1", "es6-shim": "^0.35.1", + "expose-loader": "^0.7.1", + "extendify": "^1.0.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.8.5", "isomorphic-fetch": "^2.2.1", + "jquery": "^2.2.1", "preboot": "^2.0.10", + "raw-loader": "^0.5.1", "rxjs": "5.0.0-beta.6", + "style-loader": "^0.13.0", + "ts-loader": "^0.8.1", + "typescript": "^1.8.2", + "url-loader": "^0.5.7", + "webpack": "^1.12.14", "webpack-externals-plugin": "^1.0.0", + "webpack-hot-middleware": "^2.10.0", "zone.js": "^0.6.12" } } diff --git a/templates/KnockoutSpa/package.json b/templates/KnockoutSpa/package.json index 96a49bd7..bff49e01 100644 --- a/templates/KnockoutSpa/package.json +++ b/templates/KnockoutSpa/package.json @@ -19,9 +19,7 @@ "ts-loader": "^0.8.1", "typescript": "^1.8.2", "url-loader": "^0.5.7", - "webpack": "^1.12.14" - }, - "dependencies": { + "webpack": "^1.12.14", "webpack-hot-middleware": "^2.10.0" } } diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index 82fb4918..6ac0feeb 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -1,29 +1,21 @@ { "name": "WebApplicationBasic", "version": "0.0.0", - "devDependencies": { + "dependencies": { + "aspnet-prerendering": "^1.0.2", "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.2", + "babel-core": "^6.5.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "bootstrap": "^3.3.6", "css-loader": "^0.23.1", + "domain-task": "^2.0.0", "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", "jquery": "^2.2.1", - "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", - "url-loader": "^0.5.7", - "webpack": "^1.12.14", - "webpack-hot-middleware": "^2.10.0" - }, - "dependencies": { - "aspnet-prerendering": "^1.0.2", - "babel-core": "^6.5.2", - "domain-task": "^2.0.0", "react": "^15.0.1", "react-dom": "^15.0.1", "react-redux": "^4.4.4", @@ -32,6 +24,12 @@ "redux": "^3.4.0", "redux-thunk": "^2.0.1", "redux-typed": "^1.0.0", - "webpack-externals-plugin": "^1.0.0" + "style-loader": "^0.13.0", + "ts-loader": "^0.8.1", + "typescript": "^1.8.2", + "url-loader": "^0.5.7", + "webpack-externals-plugin": "^1.0.0", + "webpack": "^1.12.14", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json index 9e07d1b7..5d2c44b9 100644 --- a/templates/ReactSpa/package.json +++ b/templates/ReactSpa/package.json @@ -4,6 +4,7 @@ "devDependencies": { "aspnet-webpack": "^1.0.6", "aspnet-webpack-react": "^1.0.2", + "babel-core": "^6.5.2", "babel-loader": "^6.2.3", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", @@ -12,19 +13,16 @@ "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", + "isomorphic-fetch": "^2.2.1", "jquery": "^2.2.1", + "react": "^15.0.1", + "react-dom": "^15.0.1", + "react-router": "^2.1.1", "style-loader": "^0.13.0", "ts-loader": "^0.8.1", "typescript": "^1.8.2", "url-loader": "^0.5.7", "webpack": "^1.12.14", "webpack-hot-middleware": "^2.10.0" - }, - "dependencies": { - "babel-core": "^6.5.2", - "isomorphic-fetch": "^2.2.1", - "react": "^15.0.1", - "react-dom": "^15.0.1", - "react-router": "^2.1.1" } } From da662c55fadacc4ec75167d9d56c9c8dcd756be0 Mon Sep 17 00:00:00 2001 From: Erik Medina Date: Sat, 27 Aug 2016 03:42:37 -0700 Subject: [PATCH 060/791] Make webpack dev dependency a peer dependency in aspnet-webpack. Moving webpack from a dev dependency to a peer dependency makes the dependency soft and allows the webpack-dev-middleware to pickup the version of webpack being used by the consumer of the package. --- .../npm/aspnet-webpack/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 327d65d5..1f9a584d 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -14,12 +14,14 @@ "connect": "^3.4.1", "memory-fs": "^0.3.0", "require-from-string": "^1.1.0", - "webpack": "^1.12.14", - "webpack-dev-middleware": "^1.5.1", + "webpack-dev-middleware": "^1.6.1", "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { "rimraf": "^2.5.4", "typescript": "^1.8.10" + }, + "peerDependencies": { + "webpack": "^1.13.2" } } From 67f7e7450f868b25f138f15c209bba00ea09796e Mon Sep 17 00:00:00 2001 From: Erik Medina Date: Sat, 27 Aug 2016 04:03:23 -0700 Subject: [PATCH 061/791] Adding tsd to dev dependencies in aspnet-webpack. Adding tsd to aspnet-webpack's dev dependencies to allow the package's npm prepublish script to succeed without a global tsd install. --- .../npm/aspnet-webpack/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 1f9a584d..5fa571a5 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -18,6 +18,7 @@ "webpack-externals-plugin": "^1.0.0" }, "devDependencies": { + "tsd": "0.6.5", "rimraf": "^2.5.4", "typescript": "^1.8.10" }, From 605090e909e53e655ab1ad66be34cc75f2afffed Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 11:37:57 +0100 Subject: [PATCH 062/791] Publish updated version of aspnet-webpack as 1.0.10 --- .../npm/aspnet-webpack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 5fa571a5..e58c2b46 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.9", + "version": "1.0.10", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { From bc2de2ad598d90c29e19f1b585afbf074384a93c Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 13:21:31 +0100 Subject: [PATCH 063/791] In aspnet-webpack HMR, don't rely on assumption that entry point is called 'main'. Fixes #289. --- .../npm/aspnet-webpack/package.json | 2 +- .../src/WebpackDevMiddleware.ts | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index e58c2b46..077e6ff0 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.10", + "version": "1.0.11", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts index f34eefd8..ff47855d 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts @@ -43,12 +43,26 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option const listener = app.listen(suggestedHMRPortOrZero, () => { // Build the final Webpack config based on supplied options if (enableHotModuleReplacement) { - // TODO: Stop assuming there's an entry point called 'main' - if (typeof webpackConfig.entry['main'] === 'string') { - webpackConfig.entry['main'] = ['webpack-hot-middleware/client', webpackConfig.entry['main']]; - } else { - webpackConfig.entry['main'].unshift('webpack-hot-middleware/client'); + // For this, we only support the key/value config format, not string or string[], since + // those ones don't clearly indicate what the resulting bundle name will be + const entryPoints = webpackConfig.entry; + const isObjectStyleConfig = entryPoints + && typeof entryPoints === 'object' + && !(entryPoints instanceof Array); + if (!isObjectStyleConfig) { + callback('To use HotModuleReplacement, your webpack config must specify an \'entry\' value as a key-value object (e.g., "entry: { main: \'ClientApp/boot-client.ts\' }")', null); + return; } + + // Augment all entry points so they support HMR + Object.getOwnPropertyNames(entryPoints).forEach(entryPointName => { + if (typeof entryPoints[entryPointName] === 'string') { + entryPoints[entryPointName] = ['webpack-hot-middleware/client', entryPoints[entryPointName]]; + } else { + entryPoints[entryPointName].unshift('webpack-hot-middleware/client'); + } + }); + webpackConfig.plugins.push( new webpack.HotModuleReplacementPlugin() ); From f071590fcefa137919d3d500c0180bf385c93248 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 16:31:15 +0100 Subject: [PATCH 064/791] Webpack HMR EventSource requests are now proxied (rather than redirected) to the local HMR server. Fixes #271. --- .../Webpack/ConditionalProxyMiddleware.cs | 1 + .../ConditionalProxyMiddlewareOptions.cs | 6 +++- .../Webpack/WebpackDevMiddleware.cs | 36 +++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs index 72b66c30..5a56c81b 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs @@ -30,6 +30,7 @@ public ConditionalProxyMiddleware( _pathPrefix = pathPrefix; _options = options; _httpClient = new HttpClient(new HttpClientHandler()); + _httpClient.Timeout = _options.RequestTimeout; } public async Task Invoke(HttpContext context) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs index 56540075..2c3311aa 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs @@ -1,16 +1,20 @@ +using System; + namespace Microsoft.AspNetCore.SpaServices.Webpack { internal class ConditionalProxyMiddlewareOptions { - public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) + public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout) { Scheme = scheme; Host = host; Port = port; + RequestTimeout = requestTimeout; } public string Scheme { get; } public string Host { get; } public string Port { get; } + public TimeSpan RequestTimeout { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dcc5d4fc..d046c284 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; +using System.Threading; // Putting in this namespace so it's always available whenever MapRoute is @@ -14,8 +15,6 @@ namespace Microsoft.AspNetCore.Builder { public static class WebpackDevMiddleware { - private const string WebpackDevMiddlewareScheme = "http"; - private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; public static void UseWebpackDevMiddleware( @@ -62,30 +61,27 @@ public static void UseWebpackDevMiddleware( JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener + // Anything under / (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). + appBuilder.UseProxyToLocalWebpackDevMiddleware(devServerInfo.PublicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); + appBuilder.UseProxyToLocalWebpackDevMiddleware("/__webpack_hmr", 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 // 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 // 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 // able to make outbound requests to it from here. - var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, - "localhost", devServerInfo.Port.ToString()); - appBuilder.UseMiddleware(devServerInfo.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); - }); - }); + // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS, + // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic + // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have + // the necessary certificate). + var proxyOptions = new ConditionalProxyMiddlewareOptions( + "http", "localhost", proxyToPort.ToString(), requestTimeout); + appBuilder.UseMiddleware(publicPath, proxyOptions); } #pragma warning disable CS0649 From 80f740a9ed33d5fe2589af1ee5d620cb45d0dcd6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 12:51:23 +0100 Subject: [PATCH 065/791] Revert "Webpack HMR EventSource requests are now proxied (rather than redirected) to the local HMR server" because of 'ECANCELED'/'EPIPE broken pipe' issues. Awaiting feedback from Kestrel team. --- .../Webpack/ConditionalProxyMiddleware.cs | 1 - .../ConditionalProxyMiddlewareOptions.cs | 6 +--- .../Webpack/WebpackDevMiddleware.cs | 36 ++++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs index 5a56c81b..72b66c30 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs @@ -30,7 +30,6 @@ public ConditionalProxyMiddleware( _pathPrefix = pathPrefix; _options = options; _httpClient = new HttpClient(new HttpClientHandler()); - _httpClient.Timeout = _options.RequestTimeout; } public async Task Invoke(HttpContext context) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs index 2c3311aa..56540075 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs @@ -1,20 +1,16 @@ -using System; - namespace Microsoft.AspNetCore.SpaServices.Webpack { internal class ConditionalProxyMiddlewareOptions { - public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout) + public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) { Scheme = scheme; Host = host; Port = port; - RequestTimeout = requestTimeout; } public string Scheme { get; } public string Host { get; } public string Port { get; } - public TimeSpan RequestTimeout { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index d046c284..dcc5d4fc 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; -using System.Threading; // Putting in this namespace so it's always available whenever MapRoute is @@ -15,6 +14,8 @@ namespace Microsoft.AspNetCore.Builder { public static class WebpackDevMiddleware { + private const string WebpackDevMiddlewareScheme = "http"; + private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; public static void UseWebpackDevMiddleware( @@ -61,27 +62,30 @@ public static void UseWebpackDevMiddleware( JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener - // Anything under / (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). - appBuilder.UseProxyToLocalWebpackDevMiddleware(devServerInfo.PublicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); - appBuilder.UseProxyToLocalWebpackDevMiddleware("/__webpack_hmr", 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 // 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 // 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 // able to make outbound requests to it from here. - // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS, - // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic - // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have - // the necessary certificate). - var proxyOptions = new ConditionalProxyMiddlewareOptions( - "http", "localhost", proxyToPort.ToString(), requestTimeout); - appBuilder.UseMiddleware(publicPath, proxyOptions); + var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, + "localhost", devServerInfo.Port.ToString()); + appBuilder.UseMiddleware(devServerInfo.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 From 7f841ff8404f886385659a203c75a57baca010b3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 13:44:51 +0100 Subject: [PATCH 066/791] In Yeoman generator, support passing args from command line (e.g., --framework=angular-2) --- templates/package-builder/src/yeoman/app/index.ts | 7 ++++++- templates/package-builder/src/yeoman/package.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index 04687a08..0d6af2d9 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -5,6 +5,9 @@ import * as glob from 'glob'; const yosay = require('yosay'); const toPascalCase = require('to-pascal-case'); +type YeomanPrompt = (opt: yeoman.IPromptOptions | yeoman.IPromptOptions[], callback: (answers: any) => void) => void; +const optionOrPrompt: YeomanPrompt = require('yeoman-option-or-prompt'); + const templates = [ { value: 'angular-2', name: 'Angular 2' }, { value: 'knockout', name: 'Knockout' }, @@ -14,16 +17,18 @@ const templates = [ class MyGenerator extends yeoman.Base { private _answers: any; + private _optionOrPrompt: YeomanPrompt; constructor(args: string | string[], options: any) { super(args, options); + this._optionOrPrompt = optionOrPrompt; this.log(yosay('Welcome to the ASP.NET Core Single-Page App generator!')); } prompting() { const done = this.async(); - this.prompt([{ + this._optionOrPrompt([{ type: 'list', name: 'framework', message: 'Framework', diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 49302f90..2b1305c5 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.6", + "version": "0.2.7", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", @@ -15,6 +15,7 @@ "node-uuid": "^1.4.7", "to-pascal-case": "^1.0.0", "yeoman-generator": "^0.20.2", + "yeoman-option-or-prompt": "^1.0.2", "yosay": "^1.1.1" } } From b72435c5cc30a4fedd207e2f6092777f4ea84b12 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 13 Sep 2016 13:57:23 +0100 Subject: [PATCH 067/791] Yeoman generator support for optional --projectguid=... CLI argument --- templates/package-builder/src/yeoman/app/index.ts | 3 ++- templates/package-builder/src/yeoman/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index 0d6af2d9..c10038b2 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -28,6 +28,7 @@ class MyGenerator extends yeoman.Base { prompting() { const done = this.async(); + this.option('projectguid'); this._optionOrPrompt([{ type: 'list', name: 'framework', @@ -41,7 +42,7 @@ class MyGenerator extends yeoman.Base { }], answers => { this._answers = answers; this._answers.namePascalCase = toPascalCase(answers.name); - this._answers.projectGuid = uuid.v4(); + this._answers.projectGuid = this.options['projectguid'] || uuid.v4(); done(); }); } diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index 2b1305c5..c26fd162 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.7", + "version": "0.2.8", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 7c316d5c744c5ec1a378ececd89f7eb46a1ba3c7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 14 Sep 2016 11:36:53 +0100 Subject: [PATCH 068/791] Update to ASP.NET Core 1.0.1. Fixes #309 --- src/Microsoft.AspNetCore.AngularServices/project.json | 2 +- src/Microsoft.AspNetCore.ReactServices/project.json | 2 +- src/Microsoft.AspNetCore.SpaServices/project.json | 2 +- templates/Angular2Spa/project.json | 6 +++--- templates/KnockoutSpa/project.json | 6 +++--- templates/ReactReduxSpa/project.json | 6 +++--- templates/ReactSpa/project.json | 6 +++--- templates/WebApplicationBasic/project.json | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNetCore.AngularServices/project.json b/src/Microsoft.AspNetCore.AngularServices/project.json index 8b64b73e..99aa0697 100644 --- a/src/Microsoft.AspNetCore.AngularServices/project.json +++ b/src/Microsoft.AspNetCore.AngularServices/project.json @@ -9,7 +9,7 @@ "defaultNamespace": "Microsoft.AspNetCore.AngularServices" }, "dependencies": { - "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", + "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.SpaServices": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNetCore.ReactServices/project.json b/src/Microsoft.AspNetCore.ReactServices/project.json index ca1ae6ff..1da09985 100644 --- a/src/Microsoft.AspNetCore.ReactServices/project.json +++ b/src/Microsoft.AspNetCore.ReactServices/project.json @@ -9,7 +9,7 @@ "defaultNamespace": "Microsoft.AspNetCore.ReactServices" }, "dependencies": { - "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", + "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.1", "Microsoft.AspNetCore.SpaServices": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNetCore.SpaServices/project.json b/src/Microsoft.AspNetCore.SpaServices/project.json index 1c55cd23..38ababb7 100644 --- a/src/Microsoft.AspNetCore.SpaServices/project.json +++ b/src/Microsoft.AspNetCore.SpaServices/project.json @@ -8,7 +8,7 @@ "Microsoft" ], "dependencies": { - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.NodeServices": "1.0.0-*" }, "frameworks": { diff --git a/templates/Angular2Spa/project.json b/templates/Angular2Spa/project.json index 845fe680..c9066fc5 100755 --- a/templates/Angular2Spa/project.json +++ b/templates/Angular2Spa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.AngularServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/KnockoutSpa/project.json b/templates/KnockoutSpa/project.json index d3e2777a..f76bf2a7 100755 --- a/templates/KnockoutSpa/project.json +++ b/templates/KnockoutSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.SpaServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/ReactReduxSpa/project.json b/templates/ReactReduxSpa/project.json index 0794a38c..4b1ebfa3 100755 --- a/templates/ReactReduxSpa/project.json +++ b/templates/ReactReduxSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/ReactSpa/project.json b/templates/ReactSpa/project.json index 0794a38c..4b1ebfa3 100755 --- a/templates/ReactSpa/project.json +++ b/templates/ReactSpa/project.json @@ -1,18 +1,18 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.ReactServices": "1.0.0-*", "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", diff --git a/templates/WebApplicationBasic/project.json b/templates/WebApplicationBasic/project.json index 4464a43f..106ade4d 100755 --- a/templates/WebApplicationBasic/project.json +++ b/templates/WebApplicationBasic/project.json @@ -1,17 +1,17 @@ { "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0", + "version": "1.0.1", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", From d76b013a561594ad0f6b258fd35c524f2db594d7 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Wed, 14 Sep 2016 12:04:15 +0100 Subject: [PATCH 069/791] WebpackDevMiddleware now uses ProjectPath option consistently. Fixes #307 --- .../Webpack/WebpackDevMiddleware.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dcc5d4fc..e6a65cbf 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -42,6 +42,11 @@ public static void UseWebpackDevMiddleware( // as fast as some theoretical future alternative. var nodeServicesOptions = new NodeServicesOptions(appBuilder.ApplicationServices); nodeServicesOptions.WatchFileExtensions = new string[] {}; // Don't watch anything + if (!string.IsNullOrEmpty(options.ProjectPath)) + { + nodeServicesOptions.ProjectPath = options.ProjectPath; + } + var nodeServices = NodeServicesFactory.CreateNodeServices(nodeServicesOptions); // Get a filename matching the middleware Node script @@ -50,11 +55,9 @@ public static void UseWebpackDevMiddleware( var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit // Tell Node to start the server hosting webpack-dev-middleware - var hostEnv = (IHostingEnvironment)appBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var projectPath = options.ProjectPath ?? hostEnv.ContentRootPath; var devServerOptions = new { - webpackConfigPath = Path.Combine(projectPath, options.ConfigFile ?? DefaultConfigFile), + webpackConfigPath = Path.Combine(nodeServicesOptions.ProjectPath, options.ConfigFile ?? DefaultConfigFile), suppliedOptions = options }; var devServerInfo = From 7a80d905b8af277972201ff7a4fb249733a86e2d Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:07:49 +0100 Subject: [PATCH 070/791] In Angular 2 template, include reflect-metadata and zone.js in vendor bundle --- templates/Angular2Spa/webpack.config.vendor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index cd2bbe00..9636722f 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -29,6 +29,8 @@ module.exports = { '@angular/platform-browser-dynamic', '@angular/router', '@angular/platform-server', + 'reflect-metadata', + 'zone.js', ] }, output: { From 06ad36f8309c08aeed9d78cc12076f516855ffa3 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:32:01 +0100 Subject: [PATCH 071/791] In Angular 2 template, include prebuilt wwwroot/dist/* files to support VS and "dotnet new" templates (which can't run post-project-creation actions) --- templates/package-builder/src/build/build.ts | 34 ++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/templates/package-builder/src/build/build.ts b/templates/package-builder/src/build/build.ts index a0433bd4..9c48a763 100644 --- a/templates/package-builder/src/build/build.ts +++ b/templates/package-builder/src/build/build.ts @@ -11,8 +11,8 @@ const isWindows = /^win/.test(process.platform); const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj']; const yeomanGeneratorSource = './src/yeoman'; -const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string } } = { - 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2' }, +const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string, forceInclusion?: RegExp } } = { + 'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2', forceInclusion: /^wwwroot\/dist\// }, 'knockout': { dir: '../../templates/KnockoutSpa/', dotNetNewId: 'Knockout', displayName: 'Knockout.js' }, 'react-redux': { dir: '../../templates/ReactReduxSpa/', dotNetNewId: 'ReactRedux', displayName: 'React.js and Redux' }, 'react': { dir: '../../templates/ReactSpa/', dotNetNewId: 'React', displayName: 'React.js' } @@ -28,7 +28,7 @@ function writeFileEnsuringDirExists(root: string, filename: string, contents: st fs.writeFileSync(fullPath, contents); } -function listFilesExcludingGitignored(root: string): string[] { +function listFilesExcludingGitignored(root: string, forceInclusion: RegExp): string[] { // Note that the gitignore files, prior to be written by the generator, are called 'template_gitignore' // instead of '.gitignore'. This is a workaround for Yeoman doing strange stuff with .gitignore files // (it renames them to .npmignore, which is not helpful). @@ -37,11 +37,11 @@ function listFilesExcludingGitignored(root: string): string[] { ? gitignore.compile(fs.readFileSync(gitIgnorePath, 'utf8')) : { accepts: () => true }; return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) - .filter(fn => gitignoreEvaluator.accepts(fn)); + .filter(fn => gitignoreEvaluator.accepts(fn) || (forceInclusion && forceInclusion.test(fn))); } -function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[]) { - listFilesExcludingGitignored(sourceRoot).forEach(fn => { +function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[], forceInclusion: RegExp) { + listFilesExcludingGitignored(sourceRoot, forceInclusion).forEach(fn => { let sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); // For text files, replace hardcoded values with template tags @@ -89,7 +89,7 @@ function buildYeomanNpmPackage() { ]; _.forEach(templates, (templateConfig, templateName) => { const outputDir = path.join(outputTemplatesRoot, templateName); - writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements); + writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements, templateConfig.forceInclusion); }); // Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) @@ -125,7 +125,7 @@ function buildDotNetNewNuGetPackage() { _.forEach(templates, (templateConfig, templateName) => { const templateOutputDir = path.join(outputRoot, 'templates', templateName); const templateOutputProjectDir = path.join(templateOutputDir, sourceProjectName); - writeTemplate(templateConfig.dir, templateOutputProjectDir, contentReplacements, filenameReplacements); + writeTemplate(templateConfig.dir, templateOutputProjectDir, contentReplacements, filenameReplacements, templateConfig.forceInclusion); // Add a .netnew.json file fs.writeFileSync(path.join(templateOutputDir, '.netnew.json'), JSON.stringify({ @@ -145,7 +145,7 @@ function buildDotNetNewNuGetPackage() { const yeomanPackageVersion = JSON.parse(fs.readFileSync(path.join(yeomanGeneratorSource, 'package.json'), 'utf8')).version; writeTemplate('./src/dotnetnew', outputRoot, [ { from: /\{version\}/g, to: yeomanPackageVersion }, - ], []); + ], [], null); const nugetExe = path.join(process.cwd(), './bin/NuGet.exe'); const nugetStartInfo = { cwd: outputRoot, stdio: 'inherit' }; if (isWindows) { @@ -160,5 +160,21 @@ function buildDotNetNewNuGetPackage() { rimraf.sync('./tmp'); } +// TODO: Instead of just showing this warning, improve build script so it actually does build them +// in the correct format. Can do this once we've moved away from using ASPNETCORE_ENVIRONMENT to +// control the build output mode. The templates we warn about here are the ones where we ship some +// files that wouldn't normally be under source control (e.g., /wwwroot/dist/*). +const templatesWithForceIncludes = Object.getOwnPropertyNames(templates) + .filter(templateName => !!templates[templateName].forceInclusion); +if (templatesWithForceIncludes.length > 0) { + console.warn(` +--- +WARNING: Ensure that the following templates are already built in the configuration desired for publishing. +For example, build the dist files in debug mode. +TEMPLATES: ${templatesWithForceIncludes.join(', ')} +--- +`); +} + buildYeomanNpmPackage(); buildDotNetNewNuGetPackage(); From 591d548de750e002334e131965f8abbc42aca616 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 12:34:24 +0100 Subject: [PATCH 072/791] Publish new Yeoman templates (0.2.9) --- templates/package-builder/src/yeoman/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package-builder/src/yeoman/package.json b/templates/package-builder/src/yeoman/package.json index c26fd162..164fa30a 100644 --- a/templates/package-builder/src/yeoman/package.json +++ b/templates/package-builder/src/yeoman/package.json @@ -1,6 +1,6 @@ { "name": "generator-aspnetcore-spa", - "version": "0.2.8", + "version": "0.2.9", "description": "Single-Page App templates for ASP.NET Core", "author": "Microsoft", "license": "Apache-2.0", From 2ee0078cfd8e2936a51dd6ecfb813d83b03a6ce5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 14:15:03 +0100 Subject: [PATCH 073/791] Fix HttpNodeInstanceEntryPoint to match latest NPM modules --- .../Content/Node/entrypoint-http.js | 5 ++--- .../TypeScript/HttpNodeInstanceEntryPoint.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js index cfa15bcf..b085a36e 100644 --- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js +++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js @@ -128,9 +128,8 @@ ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid)); function readRequestBodyAsJson(request, callback) { var requestBodyAsString = ''; - request - .on('data', function (chunk) { requestBodyAsString += chunk; }) - .on('end', function () { callback(JSON.parse(requestBodyAsString)); }); + request.on('data', function (chunk) { requestBodyAsString += chunk; }); + request.on('end', function () { callback(JSON.parse(requestBodyAsString)); }); } function respondWithError(res, errorValue) { res.statusCode = 500; diff --git a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts index f8e619d2..eb7fc28a 100644 --- a/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts +++ b/src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts @@ -80,9 +80,8 @@ exitWhenParentExits(parseInt(parsedArgs.parentPid)); function readRequestBodyAsJson(request, callback) { let requestBodyAsString = ''; - request - .on('data', chunk => { requestBodyAsString += chunk; }) - .on('end', () => { callback(JSON.parse(requestBodyAsString)); }); + request.on('data', chunk => { requestBodyAsString += chunk; }); + request.on('end', () => { callback(JSON.parse(requestBodyAsString)); }); } function respondWithError(res: http.ServerResponse, errorValue: any) { From b71d139eb5f04d0a04494bcf8cfd7384513d3835 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Sep 2016 14:36:42 +0100 Subject: [PATCH 074/791] Update xproj files to reference dotnet build tooling --- templates/Angular2Spa/Angular2Spa.xproj | 8 ++++---- templates/KnockoutSpa/KnockoutSpa.xproj | 12 ++++++------ templates/ReactReduxSpa/ReactReduxSpa.xproj | 8 ++++---- templates/ReactSpa/ReactSpa.xproj | 8 ++++---- .../WebApplicationBasic/WebApplicationBasic.xproj | 8 ++++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/templates/Angular2Spa/Angular2Spa.xproj b/templates/Angular2Spa/Angular2Spa.xproj index d4c5f075..3f02ad9d 100644 --- a/templates/Angular2Spa/Angular2Spa.xproj +++ b/templates/Angular2Spa/Angular2Spa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + 8f5cb8a9-3086-4b49-a1c2-32a9f89bca11 Angular2Spa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/KnockoutSpa/KnockoutSpa.xproj b/templates/KnockoutSpa/KnockoutSpa.xproj index 7ce39c5c..9a6e512c 100644 --- a/templates/KnockoutSpa/KnockoutSpa.xproj +++ b/templates/KnockoutSpa/KnockoutSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + 85231b41-6998-49ae-abd2-5124c83dbef2 KnockoutSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) - ..\..\..\JavaScriptServices.sln\artifacts\bin\$(MSBuildProjectName)\ + .\obj + .\bin\ + v4.5.2 2.0 - 2018 - - + + \ No newline at end of file diff --git a/templates/ReactReduxSpa/ReactReduxSpa.xproj b/templates/ReactReduxSpa/ReactReduxSpa.xproj index 0996bb60..f3701ba6 100644 --- a/templates/ReactReduxSpa/ReactReduxSpa.xproj +++ b/templates/ReactReduxSpa/ReactReduxSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + dbfc6db0-a6d1-4694-a108-1c604b988da3 ReactReduxSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/ReactSpa/ReactSpa.xproj b/templates/ReactSpa/ReactSpa.xproj index abe5a589..4a9c25be 100644 --- a/templates/ReactSpa/ReactSpa.xproj +++ b/templates/ReactSpa/ReactSpa.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + e9d1a695-f0e6-46f2-b5e3-72f4af805387 ReactSpa - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file diff --git a/templates/WebApplicationBasic/WebApplicationBasic.xproj b/templates/WebApplicationBasic/WebApplicationBasic.xproj index b0c23a00..40c6b3ab 100644 --- a/templates/WebApplicationBasic/WebApplicationBasic.xproj +++ b/templates/WebApplicationBasic/WebApplicationBasic.xproj @@ -5,16 +5,16 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - + cb4398d6-b7f1-449a-ae02-828769679232 WebApplicationBasic - ..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName) + .\obj .\bin\ + v4.5.2 2.0 - 2018 - + \ No newline at end of file From ce0d2089d2487e52e288ebc956a9a2277346545a Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Mon, 19 Sep 2016 09:12:03 +0100 Subject: [PATCH 075/791] ng2 2.0, Universal 2.0, TS 2.0, Preboot 4.* --- .../Angular2Spa/ClientApp/boot-client.ts | 33 +- .../Angular2Spa/ClientApp/boot-server.ts | 98 ++- .../ClientApp/components/app/app.ts | 8 +- .../ClientApp/components/counter/counter.ts | 4 +- .../components/fetch-data/fetch-data.ts | 4 +- .../ClientApp/components/home/home.ts | 4 +- .../Angular2Spa/ClientApp/components/index.ts | 8 + .../ClientApp/components/nav-menu/nav-menu.ts | 8 +- .../Angular2Spa/ClientApp/main.browser.ts | 55 ++ templates/Angular2Spa/ClientApp/main.node.ts | 55 ++ templates/Angular2Spa/ClientApp/routes.ts | 9 +- templates/Angular2Spa/package.json | 55 +- templates/Angular2Spa/tsconfig.json | 17 +- templates/Angular2Spa/tsd.json | 15 - ...rl-workaround.d.ts => custom-typings.d.ts} | 0 .../typings/es6-shim/es6-shim.d.ts | 668 ------------------ .../typings/requirejs/require.d.ts | 397 ----------- templates/Angular2Spa/typings/tsd.d.ts | 3 - 18 files changed, 274 insertions(+), 1167 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/components/index.ts create mode 100644 templates/Angular2Spa/ClientApp/main.browser.ts create mode 100644 templates/Angular2Spa/ClientApp/main.node.ts delete mode 100644 templates/Angular2Spa/tsd.json rename templates/Angular2Spa/typings/{url-workaround.d.ts => custom-typings.d.ts} (100%) delete mode 100644 templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts delete mode 100644 templates/Angular2Spa/typings/requirejs/require.d.ts delete mode 100644 templates/Angular2Spa/typings/tsd.d.ts diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index d19ff24e..0889ed5c 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,21 +1,30 @@ +// the polyfills must be the first thing imported +import 'angular2-universal-polyfills'; + import 'es6-shim'; require('zone.js'); import 'bootstrap'; import 'reflect-metadata'; import './styles/site.css'; -import { bootstrap } from '@angular/platform-browser-dynamic'; -import { FormBuilder } from '@angular/common'; -import { provideRouter } from '@angular/router'; -import { HTTP_PROVIDERS } from '@angular/http'; -import { App } from './components/app/app'; -import { routes } from './routes'; - -bootstrap(App, [ - ...HTTP_PROVIDERS, - FormBuilder, - provideRouter(routes) -]); +// Angular 2 +import { enableProdMode} from '@angular/core'; +import { platformUniversalDynamic } from 'angular2-universal'; + +// enable prod for faster renders +enableProdMode(); + +import { MainModule } from './main.browser'; + +const platformRef = platformUniversalDynamic(); + +// on document ready bootstrap Angular 2 +document.addEventListener('DOMContentLoaded', () => { + + platformRef.bootstrapModule(MainModule); + +}); + // Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time // you modify source files. This will not preserve any application state other than the URL. diff --git a/templates/Angular2Spa/ClientApp/boot-server.ts b/templates/Angular2Spa/ClientApp/boot-server.ts index 3549263b..de006549 100644 --- a/templates/Angular2Spa/ClientApp/boot-server.ts +++ b/templates/Angular2Spa/ClientApp/boot-server.ts @@ -1,36 +1,76 @@ -import 'angular2-universal/polyfills'; -import * as ngCore from '@angular/core'; -import { APP_BASE_HREF } from '@angular/common'; -import { provideRouter } from '@angular/router'; -import * as ngUniversal from 'angular2-universal'; -import { BASE_URL, ORIGIN_URL, REQUEST_URL } from 'angular2-universal/common'; -import { App } from './components/app/app'; +// the polyfills must be the first thing imported in node.js +import 'angular2-universal-polyfills'; + +// Angular 2 +import { enableProdMode } from '@angular/core'; +// Angular2 Universal +import { platformNodeDynamic } from 'angular2-universal'; + +// Application imports +import { MainModule } from './main.node'; +import { App } from './components'; import { routes } from './routes'; -const bootloader = ngUniversal.bootloader({ - async: true, - preboot: false, - platformProviders: [ - ngCore.provide(APP_BASE_HREF, { useValue: '/' }), - ] -}); - -export default function (params: any): Promise<{ html: string, globals?: any }> { - const config: ngUniversal.AppConfig = { - directives: [App], - providers: [ - ngCore.provide(ORIGIN_URL, { useValue: params.origin }), - ngCore.provide(REQUEST_URL, { useValue: params.url }), - ...ngUniversal.NODE_HTTP_PROVIDERS, - provideRouter(routes), - ...ngUniversal.NODE_LOCATION_PROVIDERS, - ], - // TODO: Render just the component instead of wrapping it inside an extra HTML document - // Waiting on https://github.com/angular/universal/issues/347 - template: '\n' +// enable prod for faster renders +enableProdMode(); + +declare var Zone: any; + +export default function (params: any) : Promise<{ html: string, globals?: any }> { + + const doc = ` + \n + + + + + + + `; + + // hold platform reference + var platformRef = platformNodeDynamic(); + + var platformConfig = { + ngModule: MainModule, + document: doc, + preboot: false, + baseUrl: '/', + requestUrl: params.url, + originUrl: params.origin }; - return bootloader.serializeApplication(config).then(html => { + // defaults + var cancel = false; + + const _config = Object.assign({ + get cancel() { return cancel; }, + cancelHandler() { return Zone.current.get('cancel') } + }, platformConfig); + + // for each user + const zone = Zone.current.fork({ + name: 'UNIVERSAL request', + properties: _config + }); + + + return Promise.resolve( + zone.run(() => { + return platformRef.serializeModule(Zone.current.get('ngModule')) + }) + ).then(html => { + + if (typeof html !== 'string' ) { + return { html : doc }; + } return { html }; + + }).catch(err => { + + console.log(err); + return { html : doc }; + }); + } diff --git a/templates/Angular2Spa/ClientApp/components/app/app.ts b/templates/Angular2Spa/ClientApp/components/app/app.ts index d6fc8b96..4ef528ee 100644 --- a/templates/Angular2Spa/ClientApp/components/app/app.ts +++ b/templates/Angular2Spa/ClientApp/components/app/app.ts @@ -1,11 +1,9 @@ -import * as ng from '@angular/core'; -import { ROUTER_DIRECTIVES } from '@angular/router'; +import { Component } from '@angular/core'; import { NavMenu } from '../nav-menu/nav-menu'; -@ng.Component({ +@Component({ selector: 'app', - template: require('./app.html'), - directives: [...ROUTER_DIRECTIVES, NavMenu] + template: require('./app.html') }) export class App { } diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.ts b/templates/Angular2Spa/ClientApp/components/counter/counter.ts index 00b4b952..4687dd2d 100644 --- a/templates/Angular2Spa/ClientApp/components/counter/counter.ts +++ b/templates/Angular2Spa/ClientApp/components/counter/counter.ts @@ -1,6 +1,6 @@ -import * as ng from '@angular/core'; +import { Component } from '@angular/core'; -@ng.Component({ +@Component({ selector: 'counter', template: require('./counter.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts index cede7ebc..ada8d432 100644 --- a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts @@ -1,7 +1,7 @@ -import * as ng from '@angular/core'; +import { Component } from '@angular/core'; import { Http } from '@angular/http'; -@ng.Component({ +@Component({ selector: 'fetch-data', template: require('./fetch-data.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/home/home.ts b/templates/Angular2Spa/ClientApp/components/home/home.ts index 656f7573..1d41bfd4 100644 --- a/templates/Angular2Spa/ClientApp/components/home/home.ts +++ b/templates/Angular2Spa/ClientApp/components/home/home.ts @@ -1,6 +1,6 @@ -import * as ng from '@angular/core'; +import { Component }from '@angular/core'; -@ng.Component({ +@Component({ selector: 'home', template: require('./home.html') }) diff --git a/templates/Angular2Spa/ClientApp/components/index.ts b/templates/Angular2Spa/ClientApp/components/index.ts new file mode 100644 index 00000000..08cee111 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/components/index.ts @@ -0,0 +1,8 @@ +// Here we can create "Barrels" so that it's easier to import everything +// within /components + +export * from './app/app'; +export * from './counter/counter'; +export * from './fetch-data/fetch-data'; +export * from './home/home'; +export * from './nav-menu/nav-menu'; \ No newline at end of file diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts b/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts index 9c525ec5..d57ed47c 100644 --- a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts +++ b/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts @@ -1,10 +1,8 @@ -import * as ng from '@angular/core'; -import { ROUTER_DIRECTIVES } from '@angular/router'; +import { Component } from '@angular/core'; -@ng.Component({ +@Component({ selector: 'nav-menu', - template: require('./nav-menu.html'), - directives: [...ROUTER_DIRECTIVES] + template: require('./nav-menu.html') }) export class NavMenu { } diff --git a/templates/Angular2Spa/ClientApp/main.browser.ts b/templates/Angular2Spa/ClientApp/main.browser.ts new file mode 100644 index 00000000..bf2f53ec --- /dev/null +++ b/templates/Angular2Spa/ClientApp/main.browser.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; + +import { + App, + Counter, + FetchData, + Home, + NavMenu +} from './components'; + +import { routes } from './routes'; + +/* NOTE : + + This file and `main.node.ts` are identical, at the moment(!) + By splitting these, you're able to create logic, imports, etc + that are "Platform" specific. + + If you want your code to be completely Universal and don't need that + You can also just have 1 file, that is imported into both + * boot-client + * boot-server + +*/ + +// ** Top-level NgModule "container" ** +@NgModule({ + + // Root App Component + bootstrap: [ App ], + + // Our Components + declarations: [ + App, Counter, FetchData, Home, NavMenu + ], + + imports: [ + + // * NOTE: Needs to be your first import (!) + UniversalModule, + // * ^ BrowserModule, HttpModule, and JsonpModule are included here + + // Your other imports can go here : + FormsModule, + + // App Routing + RouterModule.forRoot(routes) + ] +}) +export class MainModule { + +} diff --git a/templates/Angular2Spa/ClientApp/main.node.ts b/templates/Angular2Spa/ClientApp/main.node.ts new file mode 100644 index 00000000..8a5edbe4 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/main.node.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; + +import { + App, + Counter, + FetchData, + Home, + NavMenu +} from './components'; + +import { routes } from './routes'; + +/* NOTE : + + This file and `main.browser.ts` are identical, at the moment(!) + By splitting these, you're able to create logic, imports, etc + that are "Platform" specific. + + If you want your code to be completely Universal and don't need that + You can also just have 1 file, that is imported into both + * boot-client + * boot-server + +*/ + +// ** Top-level NgModule "container" ** +@NgModule({ + + // Root App Component + bootstrap: [ App ], + + // Our Components + declarations: [ + App, Counter, FetchData, Home, NavMenu + ], + + imports: [ + + // * NOTE: Needs to be your first import (!) + UniversalModule, + // ^ NodeModule, NodeHttpModule, NodeJsonpModule are included for server + + // Your other imports can go here: + FormsModule, + + // App Routing + RouterModule.forRoot(routes) + ] +}) +export class MainModule { + +} diff --git a/templates/Angular2Spa/ClientApp/routes.ts b/templates/Angular2Spa/ClientApp/routes.ts index c0a45b73..160ae4f7 100644 --- a/templates/Angular2Spa/ClientApp/routes.ts +++ b/templates/Angular2Spa/ClientApp/routes.ts @@ -1,9 +1,8 @@ -import { RouterConfig } from '@angular/router'; -import { Home } from './components/home/home'; -import { FetchData } from './components/fetch-data/fetch-data'; -import { Counter } from './components/counter/counter'; +import { Routes } from '@angular/router'; -export const routes: RouterConfig = [ +import { Home, FetchData, Counter } from './components'; + +export const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: Home }, { path: 'counter', component: Counter }, diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 04fab270..966d07a8 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,38 +1,53 @@ { - "name": "WebApplicationBasic", + "name": "Angular2Spa", "version": "0.0.0", + "devDependencies": { + "@types/body-parser": "0.0.29", + "@types/compression": "0.0.29", + "@types/cookie-parser": "^1.3.29", + "@types/express": "^4.0.32", + "@types/express-serve-static-core": "^4.0.33", + "@types/hammerjs": "^2.0.32", + "@types/mime": "0.0.28", + "@types/node": "^6.0.38", + "@types/serve-static": "^1.7.27" + }, "dependencies": { - "@angular/common": "2.0.0-rc.4", - "@angular/compiler": "2.0.0-rc.4", - "@angular/core": "2.0.0-rc.4", - "@angular/http": "2.0.0-rc.4", - "@angular/platform-browser": "2.0.0-rc.4", - "@angular/platform-browser-dynamic": "2.0.0-rc.4", - "@angular/platform-server": "2.0.0-rc.4", - "@angular/router": "3.0.0-beta.2", - "angular2-universal": "^0.104.5", - "aspnet-prerendering": "^1.0.2", - "aspnet-webpack": "^1.0.6", - "bootstrap": "^3.3.6", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "@angular/platform-server": "2.0.0", + "@angular/router": "3.0.0", + "angular2-platform-node": "~2.0.7", + "angular2-universal": "~2.0.7", + "angular2-universal-polyfills": "~2.0.7", + "angular2-express-engine": "~2.0.7", + "aspnet-prerendering": "^1.0.6", + "aspnet-webpack": "^1.0.11", + "bootstrap": "^3.3.7", "css": "^2.2.1", - "css-loader": "^0.23.1", + "css-loader": "^0.25.0", "es6-shim": "^0.35.1", "expose-loader": "^0.7.1", "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.8.5", + "file-loader": "^0.9.0", "isomorphic-fetch": "^2.2.1", "jquery": "^2.2.1", - "preboot": "^2.0.10", + "preboot": "^4.5.2", "raw-loader": "^0.5.1", - "rxjs": "5.0.0-beta.6", + "rxjs": "5.0.0-beta.12", "style-loader": "^0.13.0", - "ts-loader": "^0.8.1", - "typescript": "^1.8.2", + "ts-loader": "^0.8.2", + "typescript": "^2.0.0", "url-loader": "^0.5.7", "webpack": "^1.12.14", "webpack-externals-plugin": "^1.0.0", "webpack-hot-middleware": "^2.10.0", - "zone.js": "^0.6.12" + "zone.js": "^0.6.21" } } diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json index e141587c..0f72b020 100644 --- a/templates/Angular2Spa/tsconfig.json +++ b/templates/Angular2Spa/tsconfig.json @@ -5,10 +5,23 @@ "sourceMap": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "skipDefaultLibCheck": true + "skipDefaultLibCheck": true, + "lib": ["es6", "dom"], + "types": [ + "body-parser", + "compression", + "cookie-parser", + "express", + "express-serve-static-core", + "mime", + "node", + "serve-static", + "hammerjs" + ] }, "exclude": [ "bin", "node_modules" - ] + ], + "atom": { "rewriteTsconfig": false } } diff --git a/templates/Angular2Spa/tsd.json b/templates/Angular2Spa/tsd.json deleted file mode 100644 index b39437eb..00000000 --- a/templates/Angular2Spa/tsd.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "typings", - "bundle": "typings/tsd.d.ts", - "installed": { - "requirejs/require.d.ts": { - "commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7" - }, - "es6-shim/es6-shim.d.ts": { - "commit": "c0d876601e0f8236fd6b57626eb62c4e485f1563" - } - } -} diff --git a/templates/Angular2Spa/typings/url-workaround.d.ts b/templates/Angular2Spa/typings/custom-typings.d.ts similarity index 100% rename from templates/Angular2Spa/typings/url-workaround.d.ts rename to templates/Angular2Spa/typings/custom-typings.d.ts diff --git a/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts b/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts deleted file mode 100644 index 41f22997..00000000 --- a/templates/Angular2Spa/typings/es6-shim/es6-shim.d.ts +++ /dev/null @@ -1,668 +0,0 @@ -// Type definitions for es6-shim v0.31.2 -// Project: https://github.com/paulmillr/es6-shim -// Definitions by: Ron Buckton -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -declare type PropertyKey = string | number | symbol; - -interface IteratorResult { - done: boolean; - value?: T; -} - -interface IterableShim { - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): Iterator; -} - -interface Iterator { - next(value?: any): IteratorResult; - return?(value?: any): IteratorResult; - throw?(e?: any): IteratorResult; -} - -interface IterableIteratorShim extends IterableShim, Iterator { - /** - * Shim for an ES6 iterable iterator. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface StringConstructor { - /** - * Return the String value whose elements are, in order, the elements in the List elements. - * If length is 0, the empty string is returned. - */ - fromCodePoint(...codePoints: number[]): string; - - /** - * String.raw is intended for use as a tag function of a Tagged Template String. When called - * as such the first argument will be a well formed template call site object and the rest - * parameter will contain the substitution values. - * @param template A well-formed template string call site representation. - * @param substitutions A set of substitution values. - */ - raw(template: TemplateStringsArray, ...substitutions: any[]): string; -} - -interface String { - /** - * Returns a nonnegative integer Number less than 1114112 (0x110000) that is the code point - * value of the UTF-16 encoded code point starting at the string element at position pos in - * the String resulting from converting this object to a String. - * If there is no element at that position, the result is undefined. - * If a valid UTF-16 surrogate pair does not begin at pos, the result is the code unit at pos. - */ - codePointAt(pos: number): number; - - /** - * Returns true if searchString appears as a substring of the result of converting this - * object to a String, at one or more positions that are - * greater than or equal to position; otherwise, returns false. - * @param searchString search string - * @param position If position is undefined, 0 is assumed, so as to search all of the String. - */ - includes(searchString: string, position?: number): boolean; - - /** - * Returns true if the sequence of elements of searchString converted to a String is the - * same as the corresponding elements of this object (converted to a String) starting at - * endPosition – length(this). Otherwise returns false. - */ - endsWith(searchString: string, endPosition?: number): boolean; - - /** - * Returns a String value that is made from count copies appended together. If count is 0, - * T is the empty String is returned. - * @param count number of copies to append - */ - repeat(count: number): string; - - /** - * Returns true if the sequence of elements of searchString converted to a String is the - * same as the corresponding elements of this object (converted to a String) starting at - * position. Otherwise returns false. - */ - startsWith(searchString: string, position?: number): boolean; - - /** - * Returns an HTML anchor element and sets the name attribute to the text value - * @param name - */ - anchor(name: string): string; - - /** Returns a HTML element */ - big(): string; - - /** Returns a HTML element */ - blink(): string; - - /** Returns a HTML element */ - bold(): string; - - /** Returns a HTML element */ - fixed(): string - - /** Returns a HTML element and sets the color attribute value */ - fontcolor(color: string): string - - /** Returns a HTML element and sets the size attribute value */ - fontsize(size: number): string; - - /** Returns a HTML element and sets the size attribute value */ - fontsize(size: string): string; - - /** Returns an HTML element */ - italics(): string; - - /** Returns an HTML element and sets the href attribute value */ - link(url: string): string; - - /** Returns a HTML element */ - small(): string; - - /** Returns a HTML element */ - strike(): string; - - /** Returns a HTML element */ - sub(): string; - - /** Returns a HTML element */ - sup(): string; - - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface ArrayConstructor { - /** - * Creates an array from an array-like object. - * @param arrayLike An array-like object to convert to an array. - * @param mapfn A mapping function to call on every element of the array. - * @param thisArg Value of 'this' used to invoke the mapfn. - */ - from(arrayLike: ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): Array; - - /** - * Creates an array from an iterable object. - * @param iterable An iterable object to convert to an array. - * @param mapfn A mapping function to call on every element of the array. - * @param thisArg Value of 'this' used to invoke the mapfn. - */ - from(iterable: IterableShim, mapfn: (v: T, k: number) => U, thisArg?: any): Array; - - /** - * Creates an array from an array-like object. - * @param arrayLike An array-like object to convert to an array. - */ - from(arrayLike: ArrayLike): Array; - - /** - * Creates an array from an iterable object. - * @param iterable An iterable object to convert to an array. - */ - from(iterable: IterableShim): Array; - - /** - * Returns a new array from a set of elements. - * @param items A set of elements to include in the new array object. - */ - of(...items: T[]): Array; -} - -interface Array { - /** - * Returns the value of the first element in the array where predicate is true, and undefined - * otherwise. - * @param predicate find calls predicate once for each element of the array, in ascending - * order, until it finds one where predicate returns true. If such an element is found, find - * immediately returns that element value. Otherwise, find returns undefined. - * @param thisArg If provided, it will be used as the this value for each invocation of - * predicate. If it is not provided, undefined is used instead. - */ - find(predicate: (value: T, index: number, obj: Array) => boolean, thisArg?: any): T; - - /** - * Returns the index of the first element in the array where predicate is true, and undefined - * otherwise. - * @param predicate find calls predicate once for each element of the array, in ascending - * order, until it finds one where predicate returns true. If such an element is found, find - * immediately returns that element value. Otherwise, find returns undefined. - * @param thisArg If provided, it will be used as the this value for each invocation of - * predicate. If it is not provided, undefined is used instead. - */ - findIndex(predicate: (value: T) => boolean, thisArg?: any): number; - - /** - * Returns the this object after filling the section identified by start and end with value - * @param value value to fill array section with - * @param start index to start filling the array at. If start is negative, it is treated as - * length+start where length is the length of the array. - * @param end index to stop filling the array at. If end is negative, it is treated as - * length+end. - */ - fill(value: T, start?: number, end?: number): T[]; - - /** - * Returns the this object after copying a section of the array identified by start and end - * to the same array starting at position target - * @param target If target is negative, it is treated as length+target where length is the - * length of the array. - * @param start If start is negative, it is treated as length+start. If end is negative, it - * is treated as length+end. - * @param end If not specified, length of the this object is used as its default value. - */ - copyWithin(target: number, start: number, end?: number): T[]; - - /** - * Returns an array of key, value pairs for every entry in the array - */ - entries(): IterableIteratorShim<[number, T]>; - - /** - * Returns an list of keys in the array - */ - keys(): IterableIteratorShim; - - /** - * Returns an list of values in the array - */ - values(): IterableIteratorShim; - - /** - * Shim for an ES6 iterable. Not intended for direct use by user code. - */ - "_es6-shim iterator_"(): IterableIteratorShim; -} - -interface NumberConstructor { - /** - * The value of Number.EPSILON is the difference between 1 and the smallest value greater than 1 - * that is representable as a Number value, which is approximately: - * 2.2204460492503130808472633361816 x 10‍−‍16. - */ - EPSILON: number; - - /** - * Returns true if passed value is finite. - * Unlike the global isFininte, Number.isFinite doesn't forcibly convert the parameter to a - * number. Only finite values of the type number, result in true. - * @param number A numeric value. - */ - isFinite(number: number): boolean; - - /** - * Returns true if the value passed is an integer, false otherwise. - * @param number A numeric value. - */ - isInteger(number: number): boolean; - - /** - * Returns a Boolean value that indicates whether a value is the reserved value NaN (not a - * number). Unlike the global isNaN(), Number.isNaN() doesn't forcefully convert the parameter - * to a number. Only values of the type number, that are also NaN, result in true. - * @param number A numeric value. - */ - isNaN(number: number): boolean; - - /** - * Returns true if the value passed is a safe integer. - * @param number A numeric value. - */ - isSafeInteger(number: number): boolean; - - /** - * The value of the largest integer n such that n and n + 1 are both exactly representable as - * a Number value. - * The value of Number.MIN_SAFE_INTEGER is 9007199254740991 2^53 − 1. - */ - MAX_SAFE_INTEGER: number; - - /** - * The value of the smallest integer n such that n and n − 1 are both exactly representable as - * a Number value. - * The value of Number.MIN_SAFE_INTEGER is −9007199254740991 (−(2^53 − 1)). - */ - MIN_SAFE_INTEGER: number; - - /** - * Converts a string to a floating-point number. - * @param string A string that contains a floating-point number. - */ - parseFloat(string: string): number; - - /** - * Converts A string to an integer. - * @param s A string to convert into a number. - * @param radix A value between 2 and 36 that specifies the base of the number in numString. - * If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. - * All other strings are considered decimal. - */ - parseInt(string: string, radix?: number): number; -} - -interface ObjectConstructor { - /** - * Copy the values of all of the enumerable own properties from one or more source objects to a - * target object. Returns the target object. - * @param target The target object to copy to. - * @param sources One or more source objects to copy properties from. - */ - assign(target: any, ...sources: any[]): any; - - /** - * Returns true if the values are the same value, false otherwise. - * @param value1 The first value. - * @param value2 The second value. - */ - is(value1: any, value2: any): boolean; - - /** - * Sets the prototype of a specified object o to object proto or null. Returns the object o. - * @param o The object to change its prototype. - * @param proto The value of the new prototype or null. - * @remarks Requires `__proto__` support. - */ - setPrototypeOf(o: any, proto: any): any; -} - -interface RegExp { - /** - * Returns a string indicating the flags of the regular expression in question. This field is read-only. - * The characters in this string are sequenced and concatenated in the following order: - * - * - "g" for global - * - "i" for ignoreCase - * - "m" for multiline - * - "u" for unicode - * - "y" for sticky - * - * If no flags are set, the value is the empty string. - */ - flags: string; -} - -interface Math { - /** - * Returns the number of leading zero bits in the 32-bit binary representation of a number. - * @param x A numeric expression. - */ - clz32(x: number): number; - - /** - * Returns the result of 32-bit multiplication of two numbers. - * @param x First number - * @param y Second number - */ - imul(x: number, y: number): number; - - /** - * Returns the sign of the x, indicating whether x is positive, negative or zero. - * @param x The numeric expression to test - */ - sign(x: number): number; - - /** - * Returns the base 10 logarithm of a number. - * @param x A numeric expression. - */ - log10(x: number): number; - - /** - * Returns the base 2 logarithm of a number. - * @param x A numeric expression. - */ - log2(x: number): number; - - /** - * Returns the natural logarithm of 1 + x. - * @param x A numeric expression. - */ - log1p(x: number): number; - - /** - * Returns the result of (e^x - 1) of x (e raised to the power of x, where e is the base of - * the natural logarithms). - * @param x A numeric expression. - */ - expm1(x: number): number; - - /** - * Returns the hyperbolic cosine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - cosh(x: number): number; - - /** - * Returns the hyperbolic sine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - sinh(x: number): number; - - /** - * Returns the hyperbolic tangent of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - tanh(x: number): number; - - /** - * Returns the inverse hyperbolic cosine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - acosh(x: number): number; - - /** - * Returns the inverse hyperbolic sine of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - asinh(x: number): number; - - /** - * Returns the inverse hyperbolic tangent of a number. - * @param x A numeric expression that contains an angle measured in radians. - */ - atanh(x: number): number; - - /** - * Returns the square root of the sum of squares of its arguments. - * @param values Values to compute the square root for. - * If no arguments are passed, the result is +0. - * If there is only one argument, the result is the absolute value. - * If any argument is +Infinity or -Infinity, the result is +Infinity. - * If any argument is NaN, the result is NaN. - * If all arguments are either +0 or −0, the result is +0. - */ - hypot(...values: number[]): number; - - /** - * Returns the integral part of the a numeric expression, x, removing any fractional digits. - * If x is already an integer, the result is x. - * @param x A numeric expression. - */ - trunc(x: number): number; - - /** - * Returns the nearest single precision float representation of a number. - * @param x A numeric expression. - */ - fround(x: number): number; - - /** - * Returns an implementation-dependent approximation to the cube root of number. - * @param x A numeric expression. - */ - cbrt(x: number): number; -} - -interface PromiseLike { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike; -} - -/** - * Represents the completion of an asynchronous operation - */ -interface Promise { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): Promise; - then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): Promise; - - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: (reason: any) => T | PromiseLike): Promise; - catch(onrejected?: (reason: any) => void): Promise; -} - -interface PromiseConstructor { - /** - * A reference to the prototype. - */ - prototype: Promise; - - /** - * Creates a new Promise. - * @param executor A callback used to initialize the promise. This callback is passed two arguments: - * a resolve callback used resolve the promise with a value or the result of another promise, - * and a reject callback used to reject the promise with a provided reason or error. - */ - new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; - - /** - * Creates a Promise that is resolved with an array of results when all of the provided Promises - * resolve, or rejected when any Promise is rejected. - * @param values An array of Promises. - * @returns A new Promise. - */ - all(values: IterableShim>): Promise; - - /** - * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved - * or rejected. - * @param values An array of Promises. - * @returns A new Promise. - */ - race(values: IterableShim>): Promise; - - /** - * Creates a new rejected promise for the provided reason. - * @param reason The reason the promise was rejected. - * @returns A new rejected Promise. - */ - reject(reason: any): Promise; - - /** - * Creates a new rejected promise for the provided reason. - * @param reason The reason the promise was rejected. - * @returns A new rejected Promise. - */ - reject(reason: any): Promise; - - /** - * Creates a new resolved promise for the provided value. - * @param value A promise. - * @returns A promise whose internal state matches the provided promise. - */ - resolve(value: T | PromiseLike): Promise; - - /** - * Creates a new resolved promise . - * @returns A resolved promise. - */ - resolve(): Promise; -} - -declare var Promise: PromiseConstructor; - -interface Map { - clear(): void; - delete(key: K): boolean; - forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): Map; - size: number; - entries(): IterableIteratorShim<[K, V]>; - keys(): IterableIteratorShim; - values(): IterableIteratorShim; -} - -interface MapConstructor { - new (): Map; - new (iterable: IterableShim<[K, V]>): Map; - prototype: Map; -} - -declare var Map: MapConstructor; - -interface Set { - add(value: T): Set; - clear(): void; - delete(value: T): boolean; - forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; - has(value: T): boolean; - size: number; - entries(): IterableIteratorShim<[T, T]>; - keys(): IterableIteratorShim; - values(): IterableIteratorShim; -} - -interface SetConstructor { - new (): Set; - new (iterable: IterableShim): Set; - prototype: Set; -} - -declare var Set: SetConstructor; - -interface WeakMap { - delete(key: K): boolean; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): WeakMap; -} - -interface WeakMapConstructor { - new (): WeakMap; - new (iterable: IterableShim<[K, V]>): WeakMap; - prototype: WeakMap; -} - -declare var WeakMap: WeakMapConstructor; - -interface WeakSet { - add(value: T): WeakSet; - delete(value: T): boolean; - has(value: T): boolean; -} - -interface WeakSetConstructor { - new (): WeakSet; - new (iterable: IterableShim): WeakSet; - prototype: WeakSet; -} - -declare var WeakSet: WeakSetConstructor; - -declare module Reflect { - function apply(target: Function, thisArgument: any, argumentsList: ArrayLike): any; - function construct(target: Function, argumentsList: ArrayLike): any; - function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; - function deleteProperty(target: any, propertyKey: PropertyKey): boolean; - function enumerate(target: any): IterableIteratorShim; - function get(target: any, propertyKey: PropertyKey, receiver?: any): any; - function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor; - function getPrototypeOf(target: any): any; - function has(target: any, propertyKey: PropertyKey): boolean; - function isExtensible(target: any): boolean; - function ownKeys(target: any): Array; - function preventExtensions(target: any): boolean; - function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean; - function setPrototypeOf(target: any, proto: any): boolean; -} - -declare module "es6-shim" { - var String: StringConstructor; - var Array: ArrayConstructor; - var Number: NumberConstructor; - var Math: Math; - var Object: ObjectConstructor; - var Map: MapConstructor; - var Set: SetConstructor; - var WeakMap: WeakMapConstructor; - var WeakSet: WeakSetConstructor; - var Promise: PromiseConstructor; - module Reflect { - function apply(target: Function, thisArgument: any, argumentsList: ArrayLike): any; - function construct(target: Function, argumentsList: ArrayLike): any; - function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; - function deleteProperty(target: any, propertyKey: PropertyKey): boolean; - function enumerate(target: any): Iterator; - function get(target: any, propertyKey: PropertyKey, receiver?: any): any; - function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor; - function getPrototypeOf(target: any): any; - function has(target: any, propertyKey: PropertyKey): boolean; - function isExtensible(target: any): boolean; - function ownKeys(target: any): Array; - function preventExtensions(target: any): boolean; - function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean; - function setPrototypeOf(target: any, proto: any): boolean; - } -} diff --git a/templates/Angular2Spa/typings/requirejs/require.d.ts b/templates/Angular2Spa/typings/requirejs/require.d.ts deleted file mode 100644 index e9cdb3e8..00000000 --- a/templates/Angular2Spa/typings/requirejs/require.d.ts +++ /dev/null @@ -1,397 +0,0 @@ -// Type definitions for RequireJS 2.1.20 -// Project: http://requirejs.org/ -// Definitions by: Josh Baldwin -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -/* -require-2.1.8.d.ts may be freely distributed under the MIT license. - -Copyright (c) 2013 Josh Baldwin https://github.com/jbaldwin/require.d.ts - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. -*/ - -declare module 'module' { - var mod: { - config: () => any; - id: string; - uri: string; - } - export = mod; -} - -interface RequireError extends Error { - - /** - * The error ID that maps to an ID on a web page. - **/ - requireType: string; - - /** - * Required modules. - **/ - requireModules: string[]; - - /** - * The original error, if there is one (might be null). - **/ - originalError: Error; -} - -interface RequireShim { - - /** - * List of dependencies. - **/ - deps?: string[]; - - /** - * Name the module will be exported as. - **/ - exports?: string; - - /** - * Initialize function with all dependcies passed in, - * if the function returns a value then that value is used - * as the module export value instead of the object - * found via the 'exports' string. - * @param dependencies - * @return - **/ - init?: (...dependencies: any[]) => any; -} - -interface RequireConfig { - - // The root path to use for all module lookups. - baseUrl?: string; - - // Path mappings for module names not found directly under - // baseUrl. - paths?: { [key: string]: any; }; - - - // Dictionary of Shim's. - // does not cover case of key->string[] - shim?: { [key: string]: RequireShim; }; - - /** - * For the given module prefix, instead of loading the - * module with the given ID, substitude a different - * module ID. - * - * @example - * requirejs.config({ - * map: { - * 'some/newmodule': { - * 'foo': 'foo1.2' - * }, - * 'some/oldmodule': { - * 'foo': 'foo1.0' - * } - * } - * }); - **/ - map?: { - [id: string]: { - [id: string]: string; - }; - }; - - /** - * Allows pointing multiple module IDs to a module ID that contains a bundle of modules. - * - * @example - * requirejs.config({ - * bundles: { - * 'primary': ['main', 'util', 'text', 'text!template.html'], - * 'secondary': ['text!secondary.html'] - * } - * }); - **/ - bundles?: { [key: string]: string[]; }; - - /** - * AMD configurations, use module.config() to access in - * define() functions - **/ - config?: { [id: string]: {}; }; - - /** - * Configures loading modules from CommonJS packages. - **/ - packages?: {}; - - /** - * The number of seconds to wait before giving up on loading - * a script. The default is 7 seconds. - **/ - waitSeconds?: number; - - /** - * A name to give to a loading context. This allows require.js - * to load multiple versions of modules in a page, as long as - * each top-level require call specifies a unique context string. - **/ - context?: string; - - /** - * An array of dependencies to load. - **/ - deps?: string[]; - - /** - * A function to pass to require that should be require after - * deps have been loaded. - * @param modules - **/ - callback?: (...modules: any[]) => void; - - /** - * If set to true, an error will be thrown if a script loads - * that does not call define() or have shim exports string - * value that can be checked. - **/ - enforceDefine?: boolean; - - /** - * If set to true, document.createElementNS() will be used - * to create script elements. - **/ - xhtml?: boolean; - - /** - * Extra query string arguments appended to URLs that RequireJS - * uses to fetch resources. Most useful to cache bust when - * the browser or server is not configured correctly. - * - * @example - * urlArgs: "bust= + (new Date()).getTime() - **/ - urlArgs?: string; - - /** - * Specify the value for the type="" attribute used for script - * tags inserted into the document by RequireJS. Default is - * "text/javascript". To use Firefox's JavasScript 1.8 - * features, use "text/javascript;version=1.8". - **/ - scriptType?: string; - - /** - * If set to true, skips the data-main attribute scanning done - * to start module loading. Useful if RequireJS is embedded in - * a utility library that may interact with other RequireJS - * library on the page, and the embedded version should not do - * data-main loading. - **/ - skipDataMain?: boolean; - - /** - * Allow extending requirejs to support Subresource Integrity - * (SRI). - **/ - onNodeCreated?: (node: HTMLScriptElement, config: RequireConfig, moduleName: string, url: string) => void; -} - -// todo: not sure what to do with this guy -interface RequireModule { - - /** - * - **/ - config(): {}; - -} - -/** -* -**/ -interface RequireMap { - - /** - * - **/ - prefix: string; - - /** - * - **/ - name: string; - - /** - * - **/ - parentMap: RequireMap; - - /** - * - **/ - url: string; - - /** - * - **/ - originalName: string; - - /** - * - **/ - fullName: string; -} - -interface Require { - - /** - * Configure require.js - **/ - config(config: RequireConfig): Require; - - /** - * CommonJS require call - * @param module Module to load - * @return The loaded module - */ - (module: string): any; - - /** - * Start the main app logic. - * Callback is optional. - * Can alternatively use deps and callback. - * @param modules Required modules to load. - **/ - (modules: string[]): void; - - /** - * @see Require() - * @param ready Called when required modules are ready. - **/ - (modules: string[], ready: Function): void; - - /** - * @see http://requirejs.org/docs/api.html#errbacks - * @param ready Called when required modules are ready. - **/ - (modules: string[], ready: Function, errback: Function): void; - - /** - * Generate URLs from require module - * @param module Module to URL - * @return URL string - **/ - toUrl(module: string): string; - - /** - * Returns true if the module has already been loaded and defined. - * @param module Module to check - **/ - defined(module: string): boolean; - - /** - * Returns true if the module has already been requested or is in the process of loading and should be available at some point. - * @param module Module to check - **/ - specified(module: string): boolean; - - /** - * On Error override - * @param err - **/ - onError(err: RequireError, errback?: (err: RequireError) => void): void; - - /** - * Undefine a module - * @param module Module to undefine. - **/ - undef(module: string): void; - - /** - * Semi-private function, overload in special instance of undef() - **/ - onResourceLoad(context: Object, map: RequireMap, depArray: RequireMap[]): void; -} - -interface RequireDefine { - - /** - * Define Simple Name/Value Pairs - * @param config Dictionary of Named/Value pairs for the config. - **/ - (config: { [key: string]: any; }): void; - - /** - * Define function. - * @param func: The function module. - **/ - (func: () => any): void; - - /** - * Define function with dependencies. - * @param deps List of dependencies module IDs. - * @param ready Callback function when the dependencies are loaded. - * callback param deps module dependencies - * callback return module definition - **/ - (deps: string[], ready: Function): void; - - /** - * Define module with simplified CommonJS wrapper. - * @param ready - * callback require requirejs instance - * callback exports exports object - * callback module module - * callback return module definition - **/ - (ready: (require: Require, exports: { [key: string]: any; }, module: RequireModule) => any): void; - - /** - * Define a module with a name and dependencies. - * @param name The name of the module. - * @param deps List of dependencies module IDs. - * @param ready Callback function when the dependencies are loaded. - * callback deps module dependencies - * callback return module definition - **/ - (name: string, deps: string[], ready: Function): void; - - /** - * Define a module with a name. - * @param name The name of the module. - * @param ready Callback function when the dependencies are loaded. - * callback return module definition - **/ - (name: string, ready: Function): void; - - /** - * Used to allow a clear indicator that a global define function (as needed for script src browser loading) conforms - * to the AMD API, any global define function SHOULD have a property called "amd" whose value is an object. - * This helps avoid conflict with any other existing JavaScript code that could have defined a define() function - * that does not conform to the AMD API. - * define.amd.jQuery is specific to jQuery and indicates that the loader is able to account for multiple version - * of jQuery being loaded simultaneously. - */ - amd: Object; -} - -// Ambient declarations for 'require' and 'define' -declare var requirejs: Require; -declare var require: Require; -declare var define: RequireDefine; diff --git a/templates/Angular2Spa/typings/tsd.d.ts b/templates/Angular2Spa/typings/tsd.d.ts deleted file mode 100644 index c5cfdc2b..00000000 --- a/templates/Angular2Spa/typings/tsd.d.ts +++ /dev/null @@ -1,3 +0,0 @@ - -/// -/// From 1e08548aa0f9654ac3698962116282b268ae9b98 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 10:33:02 +0100 Subject: [PATCH 076/791] Remove now-redundant 'typings' dir and custom-typings.d.ts --- templates/Angular2Spa/typings/custom-typings.d.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 templates/Angular2Spa/typings/custom-typings.d.ts diff --git a/templates/Angular2Spa/typings/custom-typings.d.ts b/templates/Angular2Spa/typings/custom-typings.d.ts deleted file mode 100644 index 9d167602..00000000 --- a/templates/Angular2Spa/typings/custom-typings.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file is a workaround for angular2-universal-preview version 0.84.2 relying on the declaration of -// Node's 'url' module. Ideally it would not declare dependencies on Node APIs except where it also supplies -// the definitions itself. - -declare module 'url' { - export interface Url {} -} From 243a9b4ef6dcf4cffe4780708ba89ea45e5bc271 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 11:05:44 +0100 Subject: [PATCH 077/791] Add @types/node to avoid intellisense errors for "require" statements --- templates/Angular2Spa/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 966d07a8..4a899278 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -22,10 +22,11 @@ "@angular/platform-browser-dynamic": "2.0.0", "@angular/platform-server": "2.0.0", "@angular/router": "3.0.0", + "@types/node": "^6.0.38", + "angular2-express-engine": "~2.0.7", "angular2-platform-node": "~2.0.7", "angular2-universal": "~2.0.7", "angular2-universal-polyfills": "~2.0.7", - "angular2-express-engine": "~2.0.7", "aspnet-prerendering": "^1.0.6", "aspnet-webpack": "^1.0.11", "bootstrap": "^3.3.7", From 8f550c5706158dfb36a99c713b377acedbba8c47 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 12:44:25 +0100 Subject: [PATCH 078/791] Simplify Angular 2 template where possible --- .../Angular2Spa/ClientApp/app/app.module.ts | 31 +++++++ .../components/app/app.component.html} | 0 .../app/components/app/app.component.ts | 8 ++ .../counter/counter.component.html} | 0 .../components/counter/counter.component.ts} | 4 +- .../fetchdata/fetchdata.component.html} | 0 .../fetchdata/fetchdata.component.ts} | 6 +- .../components/home/home.component.html} | 0 .../app/components/home/home.component.ts | 8 ++ .../navmenu/navmenu.component.html} | 0 .../components/navmenu/navmenu.component.ts} | 4 +- .../Angular2Spa/ClientApp/boot-client.ts | 21 +---- .../Angular2Spa/ClientApp/boot-server.ts | 82 ++++--------------- .../ClientApp/components/app/app.ts | 9 -- .../ClientApp/components/home/home.ts | 8 -- .../Angular2Spa/ClientApp/components/index.ts | 8 -- .../Angular2Spa/ClientApp/main.browser.ts | 55 ------------- templates/Angular2Spa/ClientApp/main.node.ts | 55 ------------- templates/Angular2Spa/ClientApp/routes.ts | 11 --- templates/Angular2Spa/tsconfig.json | 19 +---- 20 files changed, 77 insertions(+), 252 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/app/app.module.ts rename templates/Angular2Spa/ClientApp/{components/app/app.html => app/components/app/app.component.html} (100%) create mode 100644 templates/Angular2Spa/ClientApp/app/components/app/app.component.ts rename templates/Angular2Spa/ClientApp/{components/counter/counter.html => app/components/counter/counter.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/counter/counter.ts => app/components/counter/counter.component.ts} (69%) rename templates/Angular2Spa/ClientApp/{components/fetch-data/fetch-data.html => app/components/fetchdata/fetchdata.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/fetch-data/fetch-data.ts => app/components/fetchdata/fetchdata.component.ts} (80%) rename templates/Angular2Spa/ClientApp/{components/home/home.html => app/components/home/home.component.html} (100%) create mode 100644 templates/Angular2Spa/ClientApp/app/components/home/home.component.ts rename templates/Angular2Spa/ClientApp/{components/nav-menu/nav-menu.html => app/components/navmenu/navmenu.component.html} (100%) rename templates/Angular2Spa/ClientApp/{components/nav-menu/nav-menu.ts => app/components/navmenu/navmenu.component.ts} (51%) delete mode 100644 templates/Angular2Spa/ClientApp/components/app/app.ts delete mode 100644 templates/Angular2Spa/ClientApp/components/home/home.ts delete mode 100644 templates/Angular2Spa/ClientApp/components/index.ts delete mode 100644 templates/Angular2Spa/ClientApp/main.browser.ts delete mode 100644 templates/Angular2Spa/ClientApp/main.node.ts delete mode 100644 templates/Angular2Spa/ClientApp/routes.ts diff --git a/templates/Angular2Spa/ClientApp/app/app.module.ts b/templates/Angular2Spa/ClientApp/app/app.module.ts new file mode 100644 index 00000000..23dcf581 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/app.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { UniversalModule } from 'angular2-universal'; +import { AppComponent } from './components/app/app.component' +import { NavMenuComponent } from './components/navmenu/navmenu.component'; +import { HomeComponent } from './components/home/home.component'; +import { FetchDataComponent } from './components/fetchdata/fetchdata.component'; +import { CounterComponent } from './components/counter/counter.component'; + +@NgModule({ + bootstrap: [ AppComponent ], + declarations: [ + AppComponent, + NavMenuComponent, + CounterComponent, + FetchDataComponent, + HomeComponent + ], + imports: [ + UniversalModule, + RouterModule.forRoot([ + { path: '', redirectTo: 'home', pathMatch: 'full' }, + { path: 'home', component: HomeComponent }, + { path: 'counter', component: CounterComponent }, + { path: 'fetch-data', component: FetchDataComponent }, + { path: '**', redirectTo: 'home' } + ]) + ] +}) +export class AppModule { +} diff --git a/templates/Angular2Spa/ClientApp/components/app/app.html b/templates/Angular2Spa/ClientApp/app/components/app/app.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/app/app.html rename to templates/Angular2Spa/ClientApp/app/components/app/app.component.html diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts new file mode 100644 index 00000000..f1bd036c --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app', + template: require('./app.component.html') +}) +export class AppComponent { +} diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.html b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/counter/counter.html rename to templates/Angular2Spa/ClientApp/app/components/counter/counter.component.html diff --git a/templates/Angular2Spa/ClientApp/components/counter/counter.ts b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts similarity index 69% rename from templates/Angular2Spa/ClientApp/components/counter/counter.ts rename to templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts index 4687dd2d..03848360 100644 --- a/templates/Angular2Spa/ClientApp/components/counter/counter.ts +++ b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts @@ -2,9 +2,9 @@ import { Component } from '@angular/core'; @Component({ selector: 'counter', - template: require('./counter.html') + template: require('./counter.component.html') }) -export class Counter { +export class CounterComponent { public currentCount = 0; public incrementCounter() { diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.html b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.html rename to templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.html diff --git a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts similarity index 80% rename from templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts rename to templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts index ada8d432..40de3d92 100644 --- a/templates/Angular2Spa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -2,10 +2,10 @@ import { Component } from '@angular/core'; import { Http } from '@angular/http'; @Component({ - selector: 'fetch-data', - template: require('./fetch-data.html') + selector: 'fetchdata', + template: require('./fetchdata.component.html') }) -export class FetchData { +export class FetchDataComponent { public forecasts: WeatherForecast[]; constructor(http: Http) { diff --git a/templates/Angular2Spa/ClientApp/components/home/home.html b/templates/Angular2Spa/ClientApp/app/components/home/home.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/home/home.html rename to templates/Angular2Spa/ClientApp/app/components/home/home.component.html diff --git a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts new file mode 100644 index 00000000..2152f0a5 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'home', + template: require('./home.component.html') +}) +export class HomeComponent { +} diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.html b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html similarity index 100% rename from templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.html rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html diff --git a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts similarity index 51% rename from templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts index d57ed47c..3bcba94e 100644 --- a/templates/Angular2Spa/ClientApp/components/nav-menu/nav-menu.ts +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'nav-menu', - template: require('./nav-menu.html') + template: require('./navmenu.component.html') }) -export class NavMenu { +export class NavMenuComponent { } diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 0889ed5c..ad0d29fa 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,30 +1,15 @@ -// the polyfills must be the first thing imported import 'angular2-universal-polyfills'; - import 'es6-shim'; -require('zone.js'); +import 'zone.js'; import 'bootstrap'; import 'reflect-metadata'; import './styles/site.css'; - -// Angular 2 import { enableProdMode} from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; +import { AppModule } from './app/app.module'; -// enable prod for faster renders enableProdMode(); - -import { MainModule } from './main.browser'; - -const platformRef = platformUniversalDynamic(); - -// on document ready bootstrap Angular 2 -document.addEventListener('DOMContentLoaded', () => { - - platformRef.bootstrapModule(MainModule); - -}); - +platformUniversalDynamic().bootstrapModule(AppModule); // Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time // you modify source files. This will not preserve any application state other than the URL. diff --git a/templates/Angular2Spa/ClientApp/boot-server.ts b/templates/Angular2Spa/ClientApp/boot-server.ts index de006549..0c4c3aa5 100644 --- a/templates/Angular2Spa/ClientApp/boot-server.ts +++ b/templates/Angular2Spa/ClientApp/boot-server.ts @@ -1,76 +1,28 @@ -// the polyfills must be the first thing imported in node.js import 'angular2-universal-polyfills'; - -// Angular 2 +import 'zone.js'; import { enableProdMode } from '@angular/core'; -// Angular2 Universal import { platformNodeDynamic } from 'angular2-universal'; +import { AppModule } from './app/app.module'; -// Application imports -import { MainModule } from './main.node'; -import { App } from './components'; -import { routes } from './routes'; - -// enable prod for faster renders enableProdMode(); - -declare var Zone: any; +const platform = platformNodeDynamic(); export default function (params: any) : Promise<{ html: string, globals?: any }> { - - const doc = ` - \n - - - - - - - `; - - // hold platform reference - var platformRef = platformNodeDynamic(); - - var platformConfig = { - ngModule: MainModule, - document: doc, - preboot: false, - baseUrl: '/', - requestUrl: params.url, - originUrl: params.origin - }; - - // defaults - var cancel = false; - - const _config = Object.assign({ - get cancel() { return cancel; }, - cancelHandler() { return Zone.current.get('cancel') } - }, platformConfig); - - // for each user - const zone = Zone.current.fork({ - name: 'UNIVERSAL request', - properties: _config - }); - - - return Promise.resolve( - zone.run(() => { - return platformRef.serializeModule(Zone.current.get('ngModule')) - }) - ).then(html => { - - if (typeof html !== 'string' ) { - return { html : doc }; + const requestZone = Zone.current.fork({ + name: 'angular-universal request', + properties: { + baseUrl: '/', + requestUrl: params.url, + originUrl: params.origin, + preboot: false, + // TODO: Render just the component instead of wrapping it inside an extra HTML document + // Waiting on https://github.com/angular/universal/issues/347 + document: '' } - return { html }; - - }).catch(err => { - - console.log(err); - return { html : doc }; - }); + return requestZone.run>(() => platform.serializeModule(AppModule)) + .then(html => { + return { html: html }; + }); } diff --git a/templates/Angular2Spa/ClientApp/components/app/app.ts b/templates/Angular2Spa/ClientApp/components/app/app.ts deleted file mode 100644 index 4ef528ee..00000000 --- a/templates/Angular2Spa/ClientApp/components/app/app.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; -import { NavMenu } from '../nav-menu/nav-menu'; - -@Component({ - selector: 'app', - template: require('./app.html') -}) -export class App { -} diff --git a/templates/Angular2Spa/ClientApp/components/home/home.ts b/templates/Angular2Spa/ClientApp/components/home/home.ts deleted file mode 100644 index 1d41bfd4..00000000 --- a/templates/Angular2Spa/ClientApp/components/home/home.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component }from '@angular/core'; - -@Component({ - selector: 'home', - template: require('./home.html') -}) -export class Home { -} diff --git a/templates/Angular2Spa/ClientApp/components/index.ts b/templates/Angular2Spa/ClientApp/components/index.ts deleted file mode 100644 index 08cee111..00000000 --- a/templates/Angular2Spa/ClientApp/components/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Here we can create "Barrels" so that it's easier to import everything -// within /components - -export * from './app/app'; -export * from './counter/counter'; -export * from './fetch-data/fetch-data'; -export * from './home/home'; -export * from './nav-menu/nav-menu'; \ No newline at end of file diff --git a/templates/Angular2Spa/ClientApp/main.browser.ts b/templates/Angular2Spa/ClientApp/main.browser.ts deleted file mode 100644 index bf2f53ec..00000000 --- a/templates/Angular2Spa/ClientApp/main.browser.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { UniversalModule } from 'angular2-universal'; - -import { - App, - Counter, - FetchData, - Home, - NavMenu -} from './components'; - -import { routes } from './routes'; - -/* NOTE : - - This file and `main.node.ts` are identical, at the moment(!) - By splitting these, you're able to create logic, imports, etc - that are "Platform" specific. - - If you want your code to be completely Universal and don't need that - You can also just have 1 file, that is imported into both - * boot-client - * boot-server - -*/ - -// ** Top-level NgModule "container" ** -@NgModule({ - - // Root App Component - bootstrap: [ App ], - - // Our Components - declarations: [ - App, Counter, FetchData, Home, NavMenu - ], - - imports: [ - - // * NOTE: Needs to be your first import (!) - UniversalModule, - // * ^ BrowserModule, HttpModule, and JsonpModule are included here - - // Your other imports can go here : - FormsModule, - - // App Routing - RouterModule.forRoot(routes) - ] -}) -export class MainModule { - -} diff --git a/templates/Angular2Spa/ClientApp/main.node.ts b/templates/Angular2Spa/ClientApp/main.node.ts deleted file mode 100644 index 8a5edbe4..00000000 --- a/templates/Angular2Spa/ClientApp/main.node.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { UniversalModule } from 'angular2-universal'; - -import { - App, - Counter, - FetchData, - Home, - NavMenu -} from './components'; - -import { routes } from './routes'; - -/* NOTE : - - This file and `main.browser.ts` are identical, at the moment(!) - By splitting these, you're able to create logic, imports, etc - that are "Platform" specific. - - If you want your code to be completely Universal and don't need that - You can also just have 1 file, that is imported into both - * boot-client - * boot-server - -*/ - -// ** Top-level NgModule "container" ** -@NgModule({ - - // Root App Component - bootstrap: [ App ], - - // Our Components - declarations: [ - App, Counter, FetchData, Home, NavMenu - ], - - imports: [ - - // * NOTE: Needs to be your first import (!) - UniversalModule, - // ^ NodeModule, NodeHttpModule, NodeJsonpModule are included for server - - // Your other imports can go here: - FormsModule, - - // App Routing - RouterModule.forRoot(routes) - ] -}) -export class MainModule { - -} diff --git a/templates/Angular2Spa/ClientApp/routes.ts b/templates/Angular2Spa/ClientApp/routes.ts deleted file mode 100644 index 160ae4f7..00000000 --- a/templates/Angular2Spa/ClientApp/routes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Routes } from '@angular/router'; - -import { Home, FetchData, Counter } from './components'; - -export const routes: Routes = [ - { path: '', redirectTo: 'home', pathMatch: 'full' }, - { path: 'home', component: Home }, - { path: 'counter', component: Counter }, - { path: 'fetch-data', component: FetchData }, - { path: '**', redirectTo: 'home' } -]; diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/tsconfig.json index 0f72b020..94b22fce 100644 --- a/templates/Angular2Spa/tsconfig.json +++ b/templates/Angular2Spa/tsconfig.json @@ -6,22 +6,9 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipDefaultLibCheck": true, - "lib": ["es6", "dom"], - "types": [ - "body-parser", - "compression", - "cookie-parser", - "express", - "express-serve-static-core", - "mime", - "node", - "serve-static", - "hammerjs" - ] + "lib": [ "es6", "dom" ], + "types": [ "node" ] }, - "exclude": [ - "bin", - "node_modules" - ], + "exclude": [ "bin", "node_modules" ], "atom": { "rewriteTsconfig": false } } From 297b4dbd92930aeff85e16d37b07117eb94d7660 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:09:12 +0100 Subject: [PATCH 079/791] Move more modules to vendor bundle. Remove explicit reflect-metadata reference (no longer needed). --- templates/Angular2Spa/ClientApp/boot-client.ts | 1 - templates/Angular2Spa/webpack.config.vendor.js | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index ad0d29fa..778ba7d3 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -2,7 +2,6 @@ import 'angular2-universal-polyfills'; import 'es6-shim'; import 'zone.js'; import 'bootstrap'; -import 'reflect-metadata'; import './styles/site.css'; import { enableProdMode} from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 9636722f..1cffdfb7 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -16,11 +16,6 @@ module.exports = { }, entry: { vendor: [ - 'bootstrap', - 'bootstrap/dist/css/bootstrap.css', - 'es6-shim', - 'style-loader', - 'jquery', '@angular/common', '@angular/compiler', '@angular/core', @@ -29,7 +24,14 @@ module.exports = { '@angular/platform-browser-dynamic', '@angular/router', '@angular/platform-server', - 'reflect-metadata', + 'angular2-universal', + 'angular2-universal-polyfills', + 'bootstrap', + 'bootstrap/dist/css/bootstrap.css', + 'es6-shim', + 'es6-promise', + 'jquery', + 'style-loader', 'zone.js', ] }, From a1c1bdb1e629837d1c46bbf946d59be0700dfd1f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:18:44 +0100 Subject: [PATCH 080/791] Simplify imports in boot-client.ts --- templates/Angular2Spa/ClientApp/boot-client.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 778ba7d3..6ac7dfb7 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,9 +1,7 @@ -import 'angular2-universal-polyfills'; -import 'es6-shim'; -import 'zone.js'; +import 'angular2-universal-polyfills/browser'; import 'bootstrap'; import './styles/site.css'; -import { enableProdMode} from '@angular/core'; +import { enableProdMode } from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; import { AppModule } from './app/app.module'; From a91b6a6b5cb071732009aa5aa043fbdd331a2061 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 13:50:26 +0100 Subject: [PATCH 081/791] Make HMR work again --- .../Angular2Spa/ClientApp/boot-client.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 6ac7dfb7..75bd9a07 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -1,16 +1,21 @@ import 'angular2-universal-polyfills/browser'; -import 'bootstrap'; -import './styles/site.css'; import { enableProdMode } from '@angular/core'; import { platformUniversalDynamic } from 'angular2-universal'; import { AppModule } from './app/app.module'; -enableProdMode(); -platformUniversalDynamic().bootstrapModule(AppModule); +// Include styles in the bundle +import 'bootstrap'; +import './styles/site.css'; -// Basic hot reloading support. Automatically reloads and restarts the Angular 2 app each time -// you modify source files. This will not preserve any application state other than the URL. -declare var module: any; -if (module.hot) { - module.hot.accept(); +// Enable either Hot Module Reloading or production mode +const hotModuleReplacement = module['hot']; +if (hotModuleReplacement) { + hotModuleReplacement.accept(); + hotModuleReplacement.dispose(() => { platform.destroy(); }); +} else { + enableProdMode(); } + +// Boot the application +const platform = platformUniversalDynamic(); +platform.bootstrapModule(AppModule); From 85dfdd9b50cacc09813335a10575b32d1d9b2043 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:04:00 +0100 Subject: [PATCH 082/791] Move tsconfig into ClientApp dir, since it's not needed at root --- templates/Angular2Spa/{ => ClientApp}/tsconfig.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/Angular2Spa/{ => ClientApp}/tsconfig.json (100%) diff --git a/templates/Angular2Spa/tsconfig.json b/templates/Angular2Spa/ClientApp/tsconfig.json similarity index 100% rename from templates/Angular2Spa/tsconfig.json rename to templates/Angular2Spa/ClientApp/tsconfig.json From 4ea7eb195e1f3c62567daf12ba0a1815d3d12fa6 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:13:20 +0100 Subject: [PATCH 083/791] Simplify webpack config. Eliminate dev/prod override files. --- templates/Angular2Spa/package.json | 1 - templates/Angular2Spa/webpack.config.dev.js | 3 --- templates/Angular2Spa/webpack.config.js | 25 +++++++++---------- templates/Angular2Spa/webpack.config.prod.js | 12 --------- .../Angular2Spa/webpack.config.vendor.js | 6 +---- 5 files changed, 13 insertions(+), 34 deletions(-) delete mode 100644 templates/Angular2Spa/webpack.config.dev.js delete mode 100644 templates/Angular2Spa/webpack.config.prod.js diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 4a899278..65036e4a 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -34,7 +34,6 @@ "css-loader": "^0.25.0", "es6-shim": "^0.35.1", "expose-loader": "^0.7.1", - "extendify": "^1.0.0", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.9.0", "isomorphic-fetch": "^2.2.1", diff --git a/templates/Angular2Spa/webpack.config.dev.js b/templates/Angular2Spa/webpack.config.dev.js deleted file mode 100644 index 719de1fb..00000000 --- a/templates/Angular2Spa/webpack.config.dev.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - devtool: 'inline-source-map' -}; diff --git a/templates/Angular2Spa/webpack.config.js b/templates/Angular2Spa/webpack.config.js index 877b8530..5ed87302 100644 --- a/templates/Angular2Spa/webpack.config.js +++ b/templates/Angular2Spa/webpack.config.js @@ -1,16 +1,14 @@ var path = require('path'); var webpack = require('webpack'); -var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +var isDevBuild = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; var extractCSS = new ExtractTextPlugin('styles.css'); -var devConfig = require('./webpack.config.dev'); -var prodConfig = require('./webpack.config.prod'); -var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development'; -module.exports = merge({ - resolve: { - extensions: [ '', '.js', '.ts' ] - }, +module.exports = { + devtool: isDevBuild ? 'inline-source-map' : null, + resolve: { extensions: [ '', '.js', '.ts' ] }, + entry: { main: ['./ClientApp/boot-client.ts'] }, module: { loaders: [ { test: /\.ts$/, include: /ClientApp/, loader: 'ts-loader?silent=true' }, @@ -18,9 +16,6 @@ module.exports = merge({ { test: /\.css/, loader: extractCSS.extract(['css']) } ] }, - entry: { - main: ['./ClientApp/boot-client.ts'] - }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), filename: '[name].js', @@ -32,5 +27,9 @@ module.exports = merge({ context: __dirname, manifest: require('./wwwroot/dist/vendor-manifest.json') }) - ] -}, isDevelopment ? devConfig : prodConfig); + ].concat(isDevBuild ? [] : [ + // Plugins that apply in production builds only + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ]) +}; diff --git a/templates/Angular2Spa/webpack.config.prod.js b/templates/Angular2Spa/webpack.config.prod.js deleted file mode 100644 index ddc6cf70..00000000 --- a/templates/Angular2Spa/webpack.config.prod.js +++ /dev/null @@ -1,12 +0,0 @@ -var webpack = require('webpack'); - -module.exports = { - plugins: [ - new webpack.optimize.OccurenceOrderPlugin(), - new webpack.optimize.UglifyJsPlugin({ - compress: { warnings: false }, - minimize: true, - mangle: false // Due to https://github.com/angular/angular/issues/6678 - }) - ] -}; diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 1cffdfb7..9499e9c9 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -49,10 +49,6 @@ module.exports = { name: '[name]_[hash]' }) ].concat(isDevelopment ? [] : [ - new webpack.optimize.UglifyJsPlugin({ - compress: { warnings: false }, - minimize: true, - mangle: false // Due to https://github.com/angular/angular/issues/6678 - }) + new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ]) }; From 07a9c1685fd801df7864df9ce17eb846edadc484 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 14:15:13 +0100 Subject: [PATCH 084/791] Remove unnecessary NPM dependencies --- templates/Angular2Spa/package.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 65036e4a..24af3c99 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -1,17 +1,6 @@ { "name": "Angular2Spa", "version": "0.0.0", - "devDependencies": { - "@types/body-parser": "0.0.29", - "@types/compression": "0.0.29", - "@types/cookie-parser": "^1.3.29", - "@types/express": "^4.0.32", - "@types/express-serve-static-core": "^4.0.33", - "@types/hammerjs": "^2.0.32", - "@types/mime": "0.0.28", - "@types/node": "^6.0.38", - "@types/serve-static": "^1.7.27" - }, "dependencies": { "@angular/common": "2.0.0", "@angular/compiler": "2.0.0", From 358ee2261e660146e10aabab36865c6568ce54de Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 15:43:58 +0100 Subject: [PATCH 085/791] Make indentation consistent --- .../ClientApp/app/components/counter/counter.component.ts | 4 ++-- .../ClientApp/app/components/fetchdata/fetchdata.component.ts | 4 ++-- .../ClientApp/app/components/home/home.component.ts | 4 ++-- .../ClientApp/app/components/navmenu/navmenu.component.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts index 03848360..6100a3c9 100644 --- a/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/counter/counter.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'counter', - template: require('./counter.component.html') + selector: 'counter', + template: require('./counter.component.html') }) export class CounterComponent { public currentCount = 0; diff --git a/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts index 40de3d92..266149f0 100644 --- a/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -2,8 +2,8 @@ import { Component } from '@angular/core'; import { Http } from '@angular/http'; @Component({ - selector: 'fetchdata', - template: require('./fetchdata.component.html') + selector: 'fetchdata', + template: require('./fetchdata.component.html') }) export class FetchDataComponent { public forecasts: WeatherForecast[]; diff --git a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts index 2152f0a5..16b817c1 100644 --- a/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/home/home.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'home', - template: require('./home.component.html') + selector: 'home', + template: require('./home.component.html') }) export class HomeComponent { } diff --git a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts index 3bcba94e..5941bf59 100644 --- a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; @Component({ - selector: 'nav-menu', - template: require('./navmenu.component.html') + selector: 'nav-menu', + template: require('./navmenu.component.html') }) export class NavMenuComponent { } From 49a853667983fa5ce566ef5a5fe866c71cdaaf2e Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 15:51:18 +0100 Subject: [PATCH 086/791] Update angular2-universal dependencies (cherry-pick 62dd13b3b) --- templates/Angular2Spa/package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/package.json b/templates/Angular2Spa/package.json index 24af3c99..e58de206 100644 --- a/templates/Angular2Spa/package.json +++ b/templates/Angular2Spa/package.json @@ -12,10 +12,9 @@ "@angular/platform-server": "2.0.0", "@angular/router": "3.0.0", "@types/node": "^6.0.38", - "angular2-express-engine": "~2.0.7", - "angular2-platform-node": "~2.0.7", - "angular2-universal": "~2.0.7", - "angular2-universal-polyfills": "~2.0.7", + "angular2-platform-node": "~2.0.10", + "angular2-universal": "~2.0.10", + "angular2-universal-polyfills": "~2.0.10", "aspnet-prerendering": "^1.0.6", "aspnet-webpack": "^1.0.11", "bootstrap": "^3.3.7", From 494c7b585cfd7450d896d529df44fdee02f6f1d9 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:05:22 +0100 Subject: [PATCH 087/791] Fix trailing whitespace --- templates/Angular2Spa/ClientApp/boot-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 75bd9a07..c46115f9 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -7,8 +7,8 @@ import { AppModule } from './app/app.module'; import 'bootstrap'; import './styles/site.css'; -// Enable either Hot Module Reloading or production mode -const hotModuleReplacement = module['hot']; +// Enable either Hot Module Reloading or production mode +const hotModuleReplacement = module['hot']; if (hotModuleReplacement) { hotModuleReplacement.accept(); hotModuleReplacement.dispose(() => { platform.destroy(); }); From 83cfb59c2da0f80c267f3db1ab9e461e29fd66f5 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:10:57 +0100 Subject: [PATCH 088/791] Add comment about UniversalModule --- templates/Angular2Spa/ClientApp/app/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Angular2Spa/ClientApp/app/app.module.ts b/templates/Angular2Spa/ClientApp/app/app.module.ts index 23dcf581..6d912da0 100644 --- a/templates/Angular2Spa/ClientApp/app/app.module.ts +++ b/templates/Angular2Spa/ClientApp/app/app.module.ts @@ -17,7 +17,7 @@ import { CounterComponent } from './components/counter/counter.component'; HomeComponent ], imports: [ - UniversalModule, + UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too. RouterModule.forRoot([ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, From ca99a2304c07f11c40eb854340d0afaf61ad5b2a Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:53:20 +0100 Subject: [PATCH 089/791] Remove style-loader from Angular2Spa vendor bundle as it's not used at all. Is used by other templates though. --- templates/Angular2Spa/webpack.config.vendor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/Angular2Spa/webpack.config.vendor.js b/templates/Angular2Spa/webpack.config.vendor.js index 9499e9c9..9c03275a 100644 --- a/templates/Angular2Spa/webpack.config.vendor.js +++ b/templates/Angular2Spa/webpack.config.vendor.js @@ -31,7 +31,6 @@ module.exports = { 'es6-shim', 'es6-promise', 'jquery', - 'style-loader', 'zone.js', ] }, From 41f1f6fe825ba32f2a7bb69b135bcd7c80da3863 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 16:56:50 +0100 Subject: [PATCH 090/791] Delay Angular 2 bootstrapping until DOMContentLoaded --- templates/Angular2Spa/ClientApp/boot-client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index c46115f9..37ac3825 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -18,4 +18,6 @@ if (hotModuleReplacement) { // Boot the application const platform = platformUniversalDynamic(); -platform.bootstrapModule(AppModule); +document.addEventListener('DOMContentLoaded', () => { + platform.bootstrapModule(AppModule); +}); From cd18489f005e4eeed52699fcc772b5d0b94b180b Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 19 Sep 2016 17:40:17 +0100 Subject: [PATCH 091/791] Fix HMR again following previous change --- templates/Angular2Spa/ClientApp/boot-client.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/templates/Angular2Spa/ClientApp/boot-client.ts b/templates/Angular2Spa/ClientApp/boot-client.ts index 37ac3825..c46b0fd6 100644 --- a/templates/Angular2Spa/ClientApp/boot-client.ts +++ b/templates/Angular2Spa/ClientApp/boot-client.ts @@ -16,8 +16,11 @@ if (hotModuleReplacement) { enableProdMode(); } -// Boot the application +// Boot the application, either now or when the DOM content is loaded const platform = platformUniversalDynamic(); -document.addEventListener('DOMContentLoaded', () => { - platform.bootstrapModule(AppModule); -}); +const bootApplication = () => { platform.bootstrapModule(AppModule); }; +if (document.readyState === 'complete') { + bootApplication(); +} else { + document.addEventListener('DOMContentLoaded', bootApplication); +} From 19684f2b7d50076e85b98f2c5f80fb6dd0c59d42 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Tue, 20 Sep 2016 10:06:23 +0100 Subject: [PATCH 092/791] In Angular2Spa template, use per-component scoped styles. Fixes common config issues like #234. --- .../app/components/app/app.component.css | 6 ++++ .../app/components/app/app.component.ts | 3 +- .../components/navmenu/navmenu.component.css} | 29 +++++++------------ .../components/navmenu/navmenu.component.html | 2 +- .../components/navmenu/navmenu.component.ts | 3 +- .../Angular2Spa/ClientApp/boot-client.ts | 3 -- .../Angular2Spa/Views/Shared/_Layout.cshtml | 1 - templates/Angular2Spa/webpack.config.js | 9 ++---- 8 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 templates/Angular2Spa/ClientApp/app/components/app/app.component.css rename templates/Angular2Spa/ClientApp/{styles/site.css => app/components/navmenu/navmenu.component.css} (66%) diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.css b/templates/Angular2Spa/ClientApp/app/components/app/app.component.css new file mode 100644 index 00000000..63926006 --- /dev/null +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.css @@ -0,0 +1,6 @@ +@media (max-width: 767px) { + /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ + .body-content { + padding-top: 50px; + } +} diff --git a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts index f1bd036c..01fbf8f2 100644 --- a/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts +++ b/templates/Angular2Spa/ClientApp/app/components/app/app.component.ts @@ -2,7 +2,8 @@ import { Component } from '@angular/core'; @Component({ selector: 'app', - template: require('./app.component.html') + template: require('./app.component.html'), + styles: [require('./app.component.css')] }) export class AppComponent { } diff --git a/templates/Angular2Spa/ClientApp/styles/site.css b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css similarity index 66% rename from templates/Angular2Spa/ClientApp/styles/site.css rename to templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css index 18a995fa..e15c6128 100644 --- a/templates/Angular2Spa/ClientApp/styles/site.css +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.css @@ -1,18 +1,11 @@ -@media (max-width: 767px) { - /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ - .body-content { - padding-top: 50px; - } -} - -.main-nav li .glyphicon { +li .glyphicon { margin-right: 10px; } /* Highlighting rules for nav menu items */ -.main-nav li.link-active a, -.main-nav li.link-active a:hover, -.main-nav li.link-active a:focus { +li.link-active a, +li.link-active a:hover, +li.link-active a:focus { background-color: #4189C7; color: white; } @@ -32,31 +25,31 @@ height: 100%; width: calc(25% - 20px); } - .main-nav .navbar { + .navbar { border-radius: 0px; border-width: 0px; height: 100%; } - .main-nav .navbar-header { + .navbar-header { float: none; } - .main-nav .navbar-collapse { + .navbar-collapse { border-top: 1px solid #444; padding: 0px; } - .main-nav .navbar ul { + .navbar ul { float: none; } - .main-nav .navbar li { + .navbar li { float: none; font-size: 15px; margin: 6px; } - .main-nav .navbar li a { + .navbar li a { padding: 10px 16px; border-radius: 4px; } - .main-nav .navbar a { + .navbar a { /* If a menu item's text is too long, truncate it */ width: 100%; white-space: nowrap; diff --git a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html index 4926cb82..c49d5097 100644 --- a/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html +++ b/templates/Angular2Spa/ClientApp/app/components/navmenu/navmenu.component.html @@ -1,5 +1,5 @@