Rename Microsoft.AspNet.* packages folders to Microsoft.AspNetCore.*

This commit is contained in:
SteveSandersonMS
2016-04-08 16:41:07 +01:00
parent 5a705f6dd6
commit 4a0e4bdf1a
99 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1 @@
/bin/

View File

@@ -0,0 +1,12 @@
// Pass through the invocation to the 'aspnet-prerendering' package, verifying that it can be loaded
module.exports.renderToString = function (callback) {
var aspNetPrerendering;
try {
aspNetPrerendering = require('aspnet-prerendering');
} catch (ex) {
callback('To use prerendering, you must install the \'aspnet-prerendering\' NPM package.');
return;
}
return aspNetPrerendering.renderToString.apply(this, arguments);
};

View File

@@ -0,0 +1,12 @@
// Pass through the invocation to the 'aspnet-webpack' package, verifying that it can be loaded
module.exports.createWebpackDevServer = function (callback) {
var aspNetWebpack;
try {
aspNetWebpack = require('aspnet-webpack');
} catch (ex) {
callback('To use webpack dev middleware, you must install the \'aspnet-webpack\' NPM package.');
return;
}
return aspNetWebpack.createWebpackDevServer.apply(this, arguments);
};

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>4624f728-6dff-44b6-93b5-3c7d9c94bf3f</ProjectGuid>
<RootNamespace>Microsoft.AspNet.SpaServices</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,86 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNet.NodeServices;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json;
namespace Microsoft.AspNet.SpaServices.Prerendering
{
[HtmlTargetElement(Attributes = PrerenderModuleAttributeName)]
public class PrerenderTagHelper : TagHelper
{
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
const string PrerenderModuleAttributeName = "asp-prerender-module";
const string PrerenderExportAttributeName = "asp-prerender-export";
const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config";
[HtmlAttributeName(PrerenderModuleAttributeName)]
public string ModuleName { get; set; }
[HtmlAttributeName(PrerenderExportAttributeName)]
public string ExportName { get; set; }
[HtmlAttributeName(PrerenderWebpackConfigAttributeName)]
public string WebpackConfigPath { get; set; }
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
private string applicationBasePath;
private INodeServices nodeServices;
public PrerenderTagHelper(IServiceProvider serviceProvider)
{
var hostEnv = (IHostingEnvironment)serviceProvider.GetService(typeof (IHostingEnvironment));
this.nodeServices = (INodeServices)serviceProvider.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
this.applicationBasePath = hostEnv.ContentRootPath;
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
// in your startup file, but then again it might be confusing that you don't need to.
if (this.nodeServices == null) {
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(new NodeServicesOptions {
HostingModel = NodeHostingModel.Http,
ProjectPath = this.applicationBasePath
});
}
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var request = this.ViewContext.HttpContext.Request;
var result = await Prerenderer.RenderToString(
applicationBasePath: this.applicationBasePath,
nodeServices: this.nodeServices,
bootModule: new JavaScriptModuleExport(this.ModuleName) {
exportName = this.ExportName,
webpackConfig = this.WebpackConfigPath
},
requestAbsoluteUrl: UriHelper.GetEncodedUrl(request),
requestPathAndQuery: request.Path + request.QueryString.Value);
output.Content.SetHtmlContent(result.Html);
// Also attach any specified globals to the 'window' object. This is useful for transferring
// general state between server and client.
if (result.Globals != null) {
var stringBuilder = new StringBuilder();
foreach (var property in result.Globals.Properties()) {
stringBuilder.AppendFormat("window.{0} = {1};",
property.Name,
property.Value.ToString(Formatting.None));
}
if (stringBuilder.Length > 0) {
output.PostElement.SetHtmlContent($"<script>{ stringBuilder.ToString() }</script>");
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.NodeServices;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SpaServices.Prerendering
{
public static class Prerenderer
{
private static Lazy<StringAsTempFile> nodeScript;
static Prerenderer() {
nodeScript = new Lazy<StringAsTempFile>(() => {
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
return new StringAsTempFile(script); // Will be cleaned up on process exit
});
}
public static async Task<RenderToStringResult> RenderToString(string applicationBasePath, INodeServices nodeServices, JavaScriptModuleExport bootModule, string requestAbsoluteUrl, string requestPathAndQuery) {
return await nodeServices.InvokeExport<RenderToStringResult>(nodeScript.Value.FileName, "renderToString",
applicationBasePath,
bootModule,
requestAbsoluteUrl,
requestPathAndQuery);
}
}
public class JavaScriptModuleExport {
public string moduleName { get; private set; }
public string exportName { get; set; }
public string webpackConfig { get; set; }
public JavaScriptModuleExport(string moduleName) {
this.moduleName = moduleName;
}
}
public class RenderToStringResult {
public string Html;
public JObject Globals;
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNet.SpaServices
{
internal class SpaRouteConstraint : IRouteConstraint
{
private readonly string clientRouteTokenName;
public SpaRouteConstraint(string clientRouteTokenName) {
if (string.IsNullOrEmpty(clientRouteTokenName)) {
throw new ArgumentException("Value cannot be null or empty", "clientRouteTokenName");
}
this.clientRouteTokenName = clientRouteTokenName;
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
var clientRouteValue = (values[this.clientRouteTokenName] as string) ?? string.Empty;
return !HasDotInLastSegment(clientRouteValue);
}
private bool HasDotInLastSegment(string uri)
{
var lastSegmentStartPos = uri.LastIndexOf('/');
return uri.IndexOf('.', lastSegmentStartPos + 1) >= 0;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNet.SpaServices;
// Putting in this namespace so it's always available whenever MapRoute is
namespace Microsoft.AspNetCore.Builder
{
public static class SpaRouteExtensions
{
private const string ClientRouteTokenName = "clientRoute";
public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, object defaults, object constraints = null, object dataTokens = null)
{
MapSpaFallbackRoute(routeBuilder, name, /* templatePrefix */ (string)null, defaults, constraints, dataTokens);
}
public static void MapSpaFallbackRoute(this IRouteBuilder routeBuilder, string name, string templatePrefix, object defaults, object constraints = null, object dataTokens = null)
{
var template = CreateRouteTemplate(templatePrefix);
var constraintsDict = ObjectToDictionary(constraints);
constraintsDict.Add(ClientRouteTokenName, new SpaRouteConstraint(ClientRouteTokenName));
routeBuilder.MapRoute(name, template, defaults, constraintsDict, dataTokens);
}
private static string CreateRouteTemplate(string templatePrefix)
{
templatePrefix = templatePrefix ?? string.Empty;
if (templatePrefix.Contains("?")) {
// TODO: Consider supporting this. The {*clientRoute} part should be added immediately before the '?'
throw new ArgumentException("SPA fallback route templates don't support querystrings");
}
if (templatePrefix.Contains("#")) {
throw new ArgumentException("SPA fallback route templates should not include # characters. The hash part of a URI does not get sent to the server.");
}
if (templatePrefix != string.Empty && !templatePrefix.EndsWith("/")) {
templatePrefix += "/";
}
return templatePrefix + $"{{*{ ClientRouteTokenName }}}";
}
private static IDictionary<string, object> ObjectToDictionary(object value)
{
return value as IDictionary<string, object> ?? new RouteValueDictionary(value);
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNet.SpaServices.Webpack
{
// Based on https://github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNetCore.Proxy/ProxyMiddleware.cs
// Differs in that, if the proxied request returns a 404, we pass through to the next middleware in the chain
// This is useful for Webpack middleware, because it lets you fall back on prebuilt files on disk for
// chunks not exposed by the current Webpack config (e.g., DLL/vendor chunks).
internal class ConditionalProxyMiddleware {
private RequestDelegate next;
private ConditionalProxyMiddlewareOptions options;
private HttpClient httpClient;
private string pathPrefix;
public ConditionalProxyMiddleware(RequestDelegate next, string pathPrefix, ConditionalProxyMiddlewareOptions options)
{
this.next = next;
this.pathPrefix = pathPrefix;
this.options = options;
this.httpClient = new HttpClient(new HttpClientHandler());
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.StartsWithSegments(this.pathPrefix)) {
var didProxyRequest = await PerformProxyRequest(context);
if (didProxyRequest) {
return;
}
}
// Not a request we can proxy
await this.next.Invoke(context);
}
private async Task<bool> PerformProxyRequest(HttpContext context) {
var requestMessage = new HttpRequestMessage();
// Copy the request headers
foreach (var header in context.Request.Headers) {
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null) {
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = options.Host + ":" + options.Port;
var uriString = $"{options.Scheme}://{options.Host}:{options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
requestMessage.RequestUri = new Uri(uriString);
requestMessage.Method = new HttpMethod(context.Request.Method);
using (var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted)) {
if (responseMessage.StatusCode == HttpStatusCode.NotFound) {
// Let some other middleware handle this
return false;
}
// We can handle this
context.Response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers) {
context.Response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers) {
context.Response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
context.Response.Headers.Remove("transfer-encoding");
await responseMessage.Content.CopyToAsync(context.Response.Body);
return true;
}
}
}
internal class ConditionalProxyMiddlewareOptions {
public string Scheme { get; private set; }
public string Host { get; private set; }
public string Port { get; private set; }
public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) {
this.Scheme = scheme;
this.Host = host;
this.Port = port;
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.NodeServices;
using Microsoft.AspNet.SpaServices.Webpack;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json;
// Putting in this namespace so it's always available whenever MapRoute is
namespace Microsoft.AspNetCore.Builder
{
public static class WebpackDevMiddleware
{
const string WebpackDevMiddlewareScheme = "http";
const string WebpackDevMiddlewareHostname = "localhost";
const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr";
const string DefaultConfigFile = "webpack.config.js";
public static void UseWebpackDevMiddleware(this IApplicationBuilder appBuilder, WebpackDevMiddlewareOptions options = null) {
// Validate options
if (options == null) {
options = new WebpackDevMiddlewareOptions();
}
if (options.ReactHotModuleReplacement && !options.HotModuleReplacement) {
throw new ArgumentException("To enable ReactHotModuleReplacement, you must also enable HotModuleReplacement.");
}
// 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 {
HostingModel = NodeHostingModel.Http,
ProjectPath = hostEnv.ContentRootPath,
WatchFileExtensions = new string[] {} // Don't watch anything
});
// Get a filename matching the middleware Node script
var script = EmbeddedResourceReader.Read(typeof (WebpackDevMiddleware), "/Content/Node/webpack-dev-middleware.js");
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
// Tell Node to start the server hosting webpack-dev-middleware
var devServerOptions = new {
webpackConfigPath = Path.Combine(hostEnv.ContentRootPath, options.ConfigFile ?? DefaultConfigFile),
suppliedOptions = options
};
var devServerInfo = nodeServices.InvokeExport<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer", JsonConvert.SerializeObject(devServerOptions)).Result;
// Proxy the corresponding requests through ASP.NET and into the Node listener
var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, WebpackDevMiddlewareHostname, devServerInfo.Port.ToString());
appBuilder.UseMiddleware<ConditionalProxyMiddleware>(devServerInfo.PublicPath, proxyOptions);
// While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream,
// and the Microsoft.Aspnet.Proxy code doesn't handle that entirely - it throws an exception after
// a while. So, just serve a 302 for those.
appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => {
builder.Use(next => async ctx => {
ctx.Response.Redirect($"{ WebpackDevMiddlewareScheme }://{ WebpackDevMiddlewareHostname }:{ devServerInfo.Port.ToString() }{ WebpackHotMiddlewareEndpoint }");
await Task.Yield();
});
});
}
#pragma warning disable CS0649
class WebpackDevServerInfo {
public int Port;
public string PublicPath;
}
#pragma warning restore CS0649
}
}

View File

@@ -0,0 +1,7 @@
namespace Microsoft.AspNet.SpaServices.Webpack {
public class WebpackDevMiddlewareOptions {
public bool HotModuleReplacement { get; set; }
public bool ReactHotModuleReplacement { get; set; }
public string ConfigFile { get; set; }
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
# Not for general use
This NPM package is an internal implementation detail of the `Microsoft.AspNet.SpaServices` NuGet package.
You should not use this package directly in your own applications, because it is not supported, and there are no
guarantees about how its APIs will change in the future.

View File

@@ -0,0 +1,19 @@
{
"name": "aspnet-prerendering",
"version": "1.0.1",
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET projects. Works in conjunction with the Microsoft.AspNet.SpaServices NuGet package.",
"main": "index.js",
"scripts": {
"prepublish": "tsd update && tsc && echo 'Finished building NPM package \"aspnet-prerendering\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Microsoft",
"license": "Apache-2.0",
"dependencies": {
"domain-task": "^1.0.1",
"es6-promise": "^3.1.2"
},
"devDependencies": {
"typescript": "^1.8.10"
}
}

View File

@@ -0,0 +1,151 @@
import 'es6-promise';
import * as url from 'url';
import * as path from 'path';
import * as domain from 'domain';
import { run as domainTaskRun } from 'domain-task/main';
import { baseUrl } from 'domain-task/fetch';
export interface RenderToStringCallback {
(error: any, result: RenderToStringResult): void;
}
export interface RenderToStringResult {
html: string;
globals: { [key: string]: any };
}
export interface BootFunc {
(params: BootFuncParams): Promise<RenderToStringResult>;
}
export interface BootFuncParams {
location: url.Url; // e.g., Location object containing information '/some/path'
origin: string; // e.g., 'https://example.com:1234'
url: string; // e.g., '/some/path'
absoluteUrl: string; // e.g., 'https://example.com:1234/some/path'
domainTasks: Promise<any>;
}
export interface BootModuleInfo {
moduleName: string;
exportName?: string;
webpackConfig?: string;
}
export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string) {
findBootFunc(applicationBasePath, bootModule, (findBootFuncError, bootFunc) => {
if (findBootFuncError) {
callback(findBootFuncError, null);
return;
}
// Prepare a promise that will represent the completion of all domain tasks in this execution context.
// The boot code will wait for this before performing its final render.
let domainTaskCompletionPromiseResolve;
const domainTaskCompletionPromise = new Promise((resolve, reject) => {
domainTaskCompletionPromiseResolve = resolve;
});
const parsedAbsoluteRequestUrl = url.parse(absoluteRequestUrl);
const params: BootFuncParams = {
location: url.parse(requestPathAndQuery),
origin: parsedAbsoluteRequestUrl.protocol + '//' + parsedAbsoluteRequestUrl.host,
url: requestPathAndQuery,
absoluteUrl: absoluteRequestUrl,
domainTasks: domainTaskCompletionPromise
};
// Open a new domain that can track all the async tasks involved in the app's execution
domainTaskRun(/* code to run */ () => {
// Workaround for Node bug where native Promise continuations lose their domain context
// (https://github.com/nodejs/node-v0.x-archive/issues/8648)
// The domain.active property is set by the domain-context module
bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']);
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
baseUrl(absoluteRequestUrl);
// Actually perform the rendering
bootFunc(params).then(successResult => {
callback(null, { html: successResult.html, globals: successResult.globals });
}, error => {
callback(error, null);
});
}, /* completion callback */ errorOrNothing => {
if (errorOrNothing) {
callback(errorOrNothing, null);
} else {
// There are no more ongoing domain tasks (typically data access operations), so we can resolve
// the domain tasks promise which notifies the boot code that it can do its final render.
domainTaskCompletionPromiseResolve();
}
});
});
}
function findBootModule<T>(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, foundModule: T) => void) {
const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
if (bootModule.webpackConfig) {
const webpackConfigFullPath = path.resolve(applicationBasePath, bootModule.webpackConfig);
let aspNetWebpackModule: any;
try {
aspNetWebpackModule = require('aspnet-webpack');
} catch (ex) {
callback('To load your boot module via webpack (i.e., if you specify a \'webpackConfig\' option), you must install the \'aspnet-webpack\' NPM package.', null);
return;
}
aspNetWebpackModule.loadViaWebpack(webpackConfigFullPath, bootModuleNameFullPath, callback);
} else {
callback(null, require(bootModuleNameFullPath));
}
}
function findBootFunc(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, bootFunc: BootFunc) => void) {
// First try to load the module (possibly via Webpack)
findBootModule<any>(applicationBasePath, bootModule, (findBootModuleError, foundBootModule) => {
if (findBootModuleError) {
callback(findBootModuleError, null);
return;
}
// Now try to pick out the function they want us to invoke
let bootFunc: BootFunc;
if (bootModule.exportName) {
// Explicitly-named export
bootFunc = foundBootModule[bootModule.exportName];
} else if (typeof foundBootModule !== 'function') {
// TypeScript-style default export
bootFunc = foundBootModule.default;
} else {
// Native default export
bootFunc = foundBootModule;
}
// Validate the result
if (typeof bootFunc !== 'function') {
if (bootModule.exportName) {
callback(`The module at ${ bootModule.moduleName } has no function export named ${ bootModule.exportName }.`, null);
} else {
callback(`The module at ${ bootModule.moduleName } does not export a default function, and you have not specified which export to invoke.`, null);
}
} else {
callback(null, bootFunc);
}
});
}
function bindPromiseContinuationsToDomain(promise: Promise<any>, domainInstance: domain.Domain) {
const originalThen = promise.then;
promise.then = function then(resolve, reject) {
if (typeof resolve === 'function') {
resolve = domainInstance.bind(resolve);
}
if (typeof reject === 'function') {
reject = domainInstance.bind(reject);
}
return originalThen.call(this, resolve, reject);
};
}

View File

@@ -0,0 +1 @@
export * from './Prerendering';

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": "."
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,18 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"node/node.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"es6-promise/es6-promise.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"whatwg-fetch/whatwg-fetch.d.ts": {
"commit": "dade4414712ce84e3c63393f1aae407e9e7e6af7"
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
# Not for general use
This NPM package is an internal implementation detail of the `Microsoft.AspNet.SpaServices` NuGet package.
You should not use this package directly in your own applications, because it is not supported, and there are no
guarantees about how its APIs will change in the future.

View File

@@ -0,0 +1,25 @@
{
"name": "aspnet-webpack-react",
"version": "1.0.1",
"description": "Helpers for using Webpack with React in ASP.NET projects. Works in conjunction with the Microsoft.AspNet.SpaServices NuGet package.",
"main": "index.js",
"scripts": {
"prepublish": "tsd update && tsc && echo 'Finished building NPM package \"aspnet-webpack-react\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Microsoft",
"license": "Apache-2.0",
"dependencies": {
"babel-core": "^6.7.2",
"babel-loader": "^6.2.4",
"babel-plugin-react-transform": "^2.0.2",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"react": "^0.14.7",
"react-transform-hmr": "^1.0.4",
"webpack": "^1.12.14"
},
"devDependencies": {
"typescript": "^1.8.10"
}
}

View File

@@ -0,0 +1,33 @@
import * as webpack from 'webpack';
export function addReactHotModuleReplacementBabelTransform(webpackConfig: webpack.Configuration) {
webpackConfig.module.loaders.forEach(loaderConfig => {
if (loaderConfig.loader && loaderConfig.loader.match(/\bbabel-loader\b/)) {
// Ensure the babel-loader options includes a 'query'
const query = loaderConfig.query = loaderConfig.query || {};
// Ensure Babel plugins includes 'react-transform'
const plugins = query['plugins'] = query['plugins'] || [];
const hasReactTransform = plugins.some(p => p && p[0] === 'react-transform');
if (!hasReactTransform) {
plugins.push(['react-transform', {}]);
}
// Ensure 'react-transform' plugin is configured to use 'react-transform-hmr'
plugins.forEach(pluginConfig => {
if (pluginConfig && pluginConfig[0] === 'react-transform') {
const pluginOpts = pluginConfig[1] = pluginConfig[1] || {};
const transforms = pluginOpts.transforms = pluginOpts.transforms || [];
const hasReactTransformHmr = transforms.some(t => t.transform === 'react-transform-hmr');
if (!hasReactTransformHmr) {
transforms.push({
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'] // Important for Webpack HMR
});
}
}
});
}
});
}

View File

@@ -0,0 +1 @@
export { addReactHotModuleReplacementBabelTransform } from './HotModuleReplacement';

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": "."
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,18 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"source-map/source-map.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"webpack/webpack.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"uglify-js/uglify-js.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
# Not for general use
This NPM package is an internal implementation detail of the `Microsoft.AspNet.SpaServices` NuGet package.
You should not use this package directly in your own applications, because it is not supported, and there are no
guarantees about how its APIs will change in the future.

View File

@@ -0,0 +1,25 @@
{
"name": "aspnet-webpack",
"version": "1.0.3",
"description": "Helpers for using Webpack in ASP.NET projects. Works in conjunction with the Microsoft.AspNet.SpaServices NuGet package.",
"main": "index.js",
"scripts": {
"prepublish": "rimraf *.d.ts && tsd update && tsc && echo 'Finished building NPM package \"aspnet-webpack\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Microsoft",
"license": "Apache-2.0",
"dependencies": {
"es6-promise": "^3.1.2",
"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-externals-plugin": "^1.0.0"
},
"devDependencies": {
"typescript": "^1.8.10",
"rimraf": "^2.5.2"
}
}

View File

@@ -0,0 +1,87 @@
// When you're using Webpack, it's often convenient to be able to require modules from regular JavaScript
// and have them transformed by Webpack. This is especially useful when doing ASP.NET server-side prerendering,
// because it means your boot module can use whatever source language you like (e.g., TypeScript), and means
// that your loader plugins (e.g., require('./mystyles.less')) work in exactly the same way on the server as
// on the client.
import 'es6-promise';
import * as webpack from 'webpack';
import { requireNewCopy } from './RequireNewCopy';
// Strange import syntax to work around https://github.com/Microsoft/TypeScript/issues/2719
import { webpackexternals } from './typings/webpack-externals-plugin';
import { requirefromstring } from './typings/require-from-string';
import { memoryfs } from './typings/memory-fs';
const ExternalsPlugin = require('webpack-externals-plugin') as typeof webpackexternals.ExternalsPlugin;
const requireFromString = require('require-from-string') as typeof requirefromstring.requireFromString;
const MemoryFS = require('memory-fs') as typeof memoryfs.MemoryFS;
// Ensure we only go through the compile process once per [config, module] pair
const loadViaWebpackPromisesCache: { [key: string]: any } = {};
export interface LoadViaWebpackCallback<T> {
(error: any, result: T): void;
}
export function loadViaWebpack<T>(webpackConfigPath: string, modulePath: string, callback: LoadViaWebpackCallback<T>) {
const cacheKey = JSON.stringify(webpackConfigPath) + JSON.stringify(modulePath);
if (!(cacheKey in loadViaWebpackPromisesCache)) {
loadViaWebpackPromisesCache[cacheKey] = loadViaWebpackNoCache(webpackConfigPath, modulePath);
}
loadViaWebpackPromisesCache[cacheKey].then(result => {
callback(null, result);
}, error => {
callback(error, null);
})
}
function loadViaWebpackNoCache<T>(webpackConfigPath: string, modulePath: string) {
return new Promise<T>((resolve, reject) => {
// Load the Webpack config and make alterations needed for loading the output into Node
const webpackConfig: webpack.Configuration = requireNewCopy(webpackConfigPath);
webpackConfig.entry = modulePath;
webpackConfig.target = 'node';
webpackConfig.output = { path: '/', filename: 'webpack-output.js', libraryTarget: 'commonjs' };
// In Node, we want anything under /node_modules/ to be loaded natively and not bundled into the output
// (partly because it's faster, but also because otherwise there'd be different instances of modules
// depending on how they were loaded, which could lead to errors)
webpackConfig.plugins = webpackConfig.plugins || [];
webpackConfig.plugins.push(new ExternalsPlugin({ type: 'commonjs', include: /node_modules/ }));
// The CommonsChunkPlugin is not compatible with a CommonJS environment like Node, nor is it needed in that case
webpackConfig.plugins = webpackConfig.plugins.filter(plugin => {
return !(plugin instanceof webpack.optimize.CommonsChunkPlugin);
});
// The typical use case for DllReferencePlugin is for referencing vendor modules. In a Node
// environment, it doesn't make sense to load them from a DLL bundle, nor would that even
// work, because then you'd get different module instances depending on whether a module
// was referenced via a normal CommonJS 'require' or via Webpack. So just remove any
// DllReferencePlugin from the config.
// If someone wanted to load their own DLL modules (not an NPM module) via DllReferencePlugin,
// that scenario is not supported today. We would have to add some extra option to the
// asp-prerender tag helper to let you specify a list of DLL bundles that should be evaluated
// in this context. But even then you'd need special DLL builds for the Node environment so that
// external dependencies were fetched via CommonJS requires, so it's unclear how that could work.
// The ultimate escape hatch here is just prebuilding your code as part of the application build
// and *not* using asp-prerender-webpack-config at all, then you can do anything you want.
webpackConfig.plugins = webpackConfig.plugins.filter(plugin => {
// DllReferencePlugin is missing from webpack.d.ts for some reason, hence referencing it
// as a key-value object property
return !(plugin instanceof webpack['DllReferencePlugin']);
});
// Create a compiler instance that stores its output in memory, then load its output
const compiler = webpack(webpackConfig);
compiler.outputFileSystem = new MemoryFS();
compiler.run((err, stats) => {
if (err) {
reject(err);
} else {
const fileContent = compiler.outputFileSystem.readFileSync('/webpack-output.js', 'utf8');
const moduleInstance = requireFromString<T>(fileContent);
resolve(moduleInstance);
}
});
});
}

View File

@@ -0,0 +1,22 @@
export function requireNewCopy(moduleNameOrPath: string): any {
// Store a reference to whatever's in the 'require' cache,
// so we don't permanently destroy it, and then ensure there's
// no cache entry for this module
const resolvedModule = require.resolve(moduleNameOrPath);
const wasCached = resolvedModule in require.cache;
let cachedInstance;
if (wasCached) {
cachedInstance = require.cache[resolvedModule];
delete require.cache[resolvedModule];
}
try {
// Return a new copy
return require(resolvedModule);
} finally {
// Restore the cached entry, if any
if (wasCached) {
require.cache[resolvedModule] = cachedInstance;
}
}
}

View File

@@ -0,0 +1,98 @@
import * as connect from 'connect';
import * as webpack from 'webpack';
import { requireNewCopy } from './RequireNewCopy';
export interface CreateDevServerCallback {
(error: any, result: { Port: number, PublicPath: string }): void;
}
// These are the options passed by WebpackDevMiddleware.cs
interface CreateDevServerOptions {
webpackConfigPath: string;
suppliedOptions: DevServerOptions;
}
// These are the options configured in C# and then JSON-serialized, hence the C#-style naming
interface DevServerOptions {
HotModuleReplacement: boolean;
ReactHotModuleReplacement: boolean;
}
export function createWebpackDevServer(callback: CreateDevServerCallback, optionsJson: string) {
const options: CreateDevServerOptions = JSON.parse(optionsJson);
const webpackConfig: webpack.Configuration = requireNewCopy(options.webpackConfigPath);
const publicPath = (webpackConfig.output.publicPath || '').trim();
if (!publicPath) {
callback('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack.config.', null);
return;
}
const enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
const enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
if (enableReactHotModuleReplacement && !enableHotModuleReplacement) {
callback('To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.', null);
return;
}
const app = connect();
const defaultPort = 0; // 0 means 'choose randomly'. Could allow an explicit value to be supplied instead.
const listener = app.listen(defaultPort, () => {
// 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');
}
webpackConfig.plugins.push(
new webpack.HotModuleReplacementPlugin()
);
// Set up React HMR support if requested. This requires the 'aspnet-webpack-react' package.
if (enableReactHotModuleReplacement) {
let aspNetWebpackReactModule: any;
try {
aspNetWebpackReactModule = require('aspnet-webpack-react');
} catch(ex) {
callback('To use ReactHotModuleReplacement, you must install the NPM package \'aspnet-webpack-react\'.', null);
return;
}
aspNetWebpackReactModule.addReactHotModuleReplacementBabelTransform(webpackConfig);
}
}
// Attach Webpack dev middleware and optional 'hot' middleware
const compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: publicPath
}));
if (enableHotModuleReplacement) {
let webpackHotMiddlewareModule;
try {
webpackHotMiddlewareModule = require('webpack-hot-middleware');
} catch (ex) {
callback('To use HotModuleReplacement, you must install the NPM package \'webpack-hot-middleware\'.', null);
return;
}
app.use(webpackHotMiddlewareModule(compiler));
}
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
callback(null, {
Port: listener.address().port,
PublicPath: removeTrailingSlash(publicPath)
});
});
}
function removeTrailingSlash(str: string) {
if (str.lastIndexOf('/') === str.length - 1) {
str = str.substring(0, str.length - 1);
}
return str;
}

View File

@@ -0,0 +1,2 @@
export { createWebpackDevServer } from './WebpackDevMiddleware';
export { loadViaWebpack } from './LoadViaWebpack';

View File

@@ -0,0 +1,3 @@
export namespace memoryfs {
export class MemoryFS {}
}

View File

@@ -0,0 +1,3 @@
export namespace requirefromstring {
export function requireFromString<T>(fileContent: string): T;
}

View File

@@ -0,0 +1,12 @@
import * as webpack from 'webpack';
export namespace webpackexternals {
export interface ExternalsPluginOptions {
type: string;
include: webpack.LoaderCondition;
}
export class ExternalsPlugin {
constructor(options: ExternalsPluginOptions);
}
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": "."
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,27 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"webpack/webpack.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"node/node.d.ts": {
"commit": "e937b3e64af586d19f2ea29fdf771e9dc4feecc8"
},
"source-map/source-map.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"uglify-js/uglify-js.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"es6-promise/es6-promise.d.ts": {
"commit": "0144ad5a74053f2292424847259c4c8e1d0fecaa"
},
"connect/connect.d.ts": {
"commit": "e937b3e64af586d19f2ea29fdf771e9dc4feecc8"
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
TODO

View File

@@ -0,0 +1,19 @@
{
"name": "domain-task",
"version": "1.0.1",
"description": "Tracks outstanding operations for a logical thread of execution",
"main": "main.js",
"scripts": {
"prepublish": "tsd update && tsc && echo 'Finished building NPM package \"domain-task\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Microsoft",
"license": "Apache-2.0",
"dependencies": {
"domain-context": "^0.5.1",
"isomorphic-fetch": "^2.2.1"
},
"devDependencies": {
"typescript": "^1.8.10"
}
}

View File

@@ -0,0 +1,9 @@
declare module 'domain' {
var active: Domain;
}
declare module 'domain-context' {
function get(key: string): any;
function set(key: string, value: any): void;
function runInNewDomain(code: () => void): void;
}

View File

@@ -0,0 +1,62 @@
import * as url from 'url';
import * as domain from 'domain';
import * as domainContext from 'domain-context';
import { addTask } from './main';
const isomorphicFetch = require('isomorphic-fetch');
const isBrowser: boolean = (new Function('try { return this === window; } catch (e) { return false; }'))();
// Not using a symbol, because this may need to run in a version of Node.js that doesn't support them
const domainTaskStateKey = '__DOMAIN_TASK_INTERNAL_FETCH_BASEURL__DO_NOT_REFERENCE_THIS__';
let noDomainBaseUrl: string;
function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit): Promise<any> {
// Resolve relative URLs
if (baseUrl) {
if (req instanceof Request) {
const reqAsRequest = req as Request;
reqAsRequest.url = url.resolve(baseUrl, reqAsRequest.url);
} else {
req = url.resolve(baseUrl, req as string);
}
} else if (!isBrowser) {
// TODO: Consider only throwing if it's a relative URL, since absolute ones would work fine
throw new Error(`
When running outside the browser (e.g., in Node.js), you must specify a base URL
before invoking domain-task's 'fetch' wrapper.
Example:
import { baseUrl } from 'domain-task/fetch';
baseUrl('http://example.com'); // Relative URLs will be resolved against this
`);
}
// Currently, some part of ASP.NET (perhaps just Kestrel on Mac - unconfirmed) doesn't complete
// its responses if we send 'Connection: close', which is the default. So if no 'Connection' header
// has been specified explicitly, use 'Connection: keep-alive'.
init = init || {};
init.headers = init.headers || {};
if (!init.headers['Connection']) {
init.headers['Connection'] = 'keep-alive';
}
return isomorphicFetch(req, init);
}
export function fetch(url: string | Request, init?: RequestInit): Promise<any> {
const promise = issueRequest(baseUrl(), url, init);
addTask(promise);
return promise;
}
export function baseUrl(url?: string): string {
if (url) {
if (domain.active) {
// There's an active domain (e.g., in Node.js), so associate the base URL with it
domainContext.set(domainTaskStateKey, url);
} else {
// There's no active domain (e.g., in browser), so there's just one shared base URL
noDomainBaseUrl = url;
}
}
return domain.active ? domainContext.get(domainTaskStateKey) : noDomainBaseUrl;
}

View File

@@ -0,0 +1,4 @@
declare module 'isomorphic-fetch' {
var fetch: (url: string | Request, init?: RequestInit) => Promise<any>;
export default fetch;
}

View File

@@ -0,0 +1,64 @@
import * as domain from 'domain';
import * as domainContext from 'domain-context';
const domainTasksStateKey = '__DOMAIN_TASKS';
export function addTask(task: PromiseLike<any>) {
if (task && domain.active) {
const state = domainContext.get(domainTasksStateKey) as DomainTasksState;
if (state) {
state.numRemainingTasks++;
task.then(() => {
// The application may have other listeners chained to this promise *after*
// this listener, which may in turn register further tasks. Since we don't
// want the combined task to complete until all the handlers for child tasks
// have finished, delay the response to give time for more tasks to be added
// synchronously.
setTimeout(() => {
state.numRemainingTasks--;
if (state.numRemainingTasks === 0 && !state.hasIssuedSuccessCallback) {
state.hasIssuedSuccessCallback = true;
setTimeout(() => {
state.completionCallback(/* error */ null);
}, 0);
}
}, 0);
}, (error) => {
state.completionCallback(error);
});
}
}
}
export function run<T>(codeToRun: () => T, completionCallback: (error: any) => void): T {
let synchronousResult: T;
domainContext.runInNewDomain(() => {
const state: DomainTasksState = {
numRemainingTasks: 0,
hasIssuedSuccessCallback: false,
completionCallback: domain.active.bind(completionCallback)
};
try {
domainContext.set(domainTasksStateKey, state);
synchronousResult = codeToRun();
// If no tasks were registered synchronously, then we're done already
if (state.numRemainingTasks === 0 && !state.hasIssuedSuccessCallback) {
state.hasIssuedSuccessCallback = true;
setTimeout(() => {
state.completionCallback(/* error */ null);
}, 0);
}
} catch(ex) {
state.completionCallback(ex);
}
});
return synchronousResult;
}
interface DomainTasksState {
numRemainingTasks: number;
hasIssuedSuccessCallback: boolean;
completionCallback: (error: any) => void;
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": "."
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,18 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"node/node.d.ts": {
"commit": "3030a4be536b6530c06b80081f1333dc0de4d703"
},
"es6-promise/es6-promise.d.ts": {
"commit": "3030a4be536b6530c06b80081f1333dc0de4d703"
},
"whatwg-fetch/whatwg-fetch.d.ts": {
"commit": "3030a4be536b6530c06b80081f1333dc0de4d703"
}
}
}

View File

@@ -0,0 +1,35 @@
{
"version": "1.0.0-*",
"description": "Microsoft.AspNet.SpaServices",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"authors": [
"Microsoft"
],
"tags": [
""
],
"projectUrl": "",
"licenseUrl": "",
"dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.0-*",
"Microsoft.AspNetCore.Routing": "1.0.0-*",
"Microsoft.AspNet.NodeServices": "1.0.0-*"
},
"frameworks": {
"dnx451": {},
"netstandardapp1.5": {
"imports": [
"dnxcore50",
"portable-net451+win8"
],
"dependencies": {
"NETStandard.Library": "1.5.0-*"
}
}
},
"resource": [
"Content/**/*"
]
}