mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-23 01:58:29 +00:00
Normalise trailing whitespace and line endings everywhere
This commit is contained in:
@@ -12,13 +12,13 @@ namespace Microsoft.AspNet.AngularServices
|
||||
public class AngularPrerenderTagHelper : TagHelper
|
||||
{
|
||||
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
|
||||
|
||||
|
||||
const string PrerenderModuleAttributeName = "asp-ng2-prerender-module";
|
||||
const string PrerenderExportAttributeName = "asp-ng2-prerender-export";
|
||||
|
||||
|
||||
[HtmlAttributeName(PrerenderModuleAttributeName)]
|
||||
public string ModuleName { get; set; }
|
||||
|
||||
|
||||
[HtmlAttributeName(PrerenderExportAttributeName)]
|
||||
public string ExportName { get; set; }
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Microsoft.AspNet.AngularServices
|
||||
{
|
||||
this.contextAccessor = contextAccessor;
|
||||
this.nodeServices = (INodeServices)serviceProvider.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
|
||||
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -6,13 +6,13 @@ namespace Microsoft.AspNet.AngularServices
|
||||
public static class AngularRenderer
|
||||
{
|
||||
private static StringAsTempFile nodeScript;
|
||||
|
||||
|
||||
static AngularRenderer() {
|
||||
// Consider populating this lazily
|
||||
var script = EmbeddedResourceReader.Read(typeof (AngularRenderer), "/Content/Node/angular-rendering.js");
|
||||
nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
|
||||
}
|
||||
|
||||
|
||||
public static async Task<string> RenderToString(INodeServices nodeServices, string componentModuleName, string componentExportName, string componentTagName, string requestUrl) {
|
||||
return await nodeServices.InvokeExport<string>(nodeScript.FileName, "renderToString", new {
|
||||
moduleName = componentModuleName,
|
||||
|
||||
@@ -18,7 +18,7 @@ function findAngularComponent(options) {
|
||||
// If exportName is specified explicitly, use it
|
||||
return getExportOrThrow(loadedModule, resolvedPath, options.exportName);
|
||||
} else if (typeof loadedModule === 'function') {
|
||||
// Otherwise, if the module itself is a function, assume that is the component
|
||||
// Otherwise, if the module itself is a function, assume that is the component
|
||||
return loadedModule;
|
||||
} else if (typeof loadedModule.default === 'function') {
|
||||
// Otherwise, if the module has a default export which is a function, assume that is the component
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Microsoft.AspNet.AngularServices {
|
||||
// TODO: Consider deduplicating the PrimeCache 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.
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(url)) {
|
||||
throw new ArgumentException("Value cannot be null or empty", "url");
|
||||
}
|
||||
@@ -41,4 +41,4 @@ namespace Microsoft.AspNet.AngularServices {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ builder.config({
|
||||
var entryPoint = 'dist/Exports';
|
||||
var tasks = [
|
||||
builder.bundle(entryPoint, './bundles/angular2-aspnet.js'),
|
||||
builder.bundle(entryPoint, './bundles/angular2-aspnet.min.js', { minify: true })
|
||||
builder.bundle(entryPoint, './bundles/angular2-aspnet.min.js', { minify: true })
|
||||
];
|
||||
|
||||
Promise.all(tasks)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
If you just want to use this package, then you *don't have to build it*. Instead, just grab the prebuilt package from NPM:
|
||||
|
||||
npm install angular2-aspnet
|
||||
|
||||
|
||||
The rest of this file is notes for anyone contributing to this package itself.
|
||||
|
||||
## How to build
|
||||
@@ -10,7 +10,7 @@ Run the following:
|
||||
|
||||
npm install
|
||||
npm run prepublish
|
||||
|
||||
|
||||
Requirements:
|
||||
|
||||
* Node, NPM
|
||||
@@ -25,16 +25,16 @@ so that developers get a good IDE experience when consuming it.
|
||||
The build process is therefore:
|
||||
|
||||
1. Compile the TypeScript to produce the development-time (.d.ts) and server-side (.js) artifacts
|
||||
|
||||
|
||||
`tsc` reads `tsconfig.json` and is instructed to compile all the `.ts` files in `src/`. It produces a corresponding
|
||||
structure of `.js` and `.d.ts` files in `dist/`.
|
||||
|
||||
|
||||
When a developer consumes the resulting package (via `npm install angular2-aspnet`),
|
||||
|
||||
|
||||
- No additional copy of `angular2` will be installed, because this package's dependency on it is declared as a
|
||||
`peerDependency`. This means it will work with whatever (compatible) version of `angular2` is already installed.
|
||||
- At runtime inside Node.js, the `main` configuration in `package.json` means the developer can use a standard
|
||||
`import` statement to consume this package (i.e., `import * from 'angular2-aspnet';` in either JS or TS files).
|
||||
`import` statement to consume this package (i.e., `import * from 'angular2-aspnet';` in either JS or TS files).
|
||||
- At development time inside an IDE such as Visual Studio Code, the `typings` configuration in `package.json` means
|
||||
the IDE will use the corresponding `.d.ts` file as type metadata for the variable imported that way.
|
||||
|
||||
@@ -43,17 +43,17 @@ The build process is therefore:
|
||||
`build.js` uses the SystemJS Builder API to combine files in `dist/` into `.js` files ready for use in client-side
|
||||
SystemJS environments, and puts them in `bundles/`. The bundle files contain `System.register` calls so that any
|
||||
other part of your client-side code that tries to import `angular2-aspnet` via SystemJS will get that module at runtime.
|
||||
|
||||
|
||||
To make it work in an application:
|
||||
- Set up some build step that copies your chosen bundle file from `bundles/` to some location where it will
|
||||
be served to the client
|
||||
- Below your `<script>` tag that loads SystemJS itself, and above the `<script>` tag that makes the first call to
|
||||
`System.import`, have a `<script>` tag that loads the desired `angular2-aspnet.js` bundle file
|
||||
|
||||
|
||||
For an example, see https://github.com/aspnet/NodeServices/tree/master/samples/angular/MusicStore
|
||||
|
||||
|
||||
Of course, you can also bundle the `angular2-aspnet.js` file into a larger SystemJS bundle if you want to combine
|
||||
it with the rest of the code in your application.
|
||||
|
||||
|
||||
Currently, this build system does *not* attempt to send sourcemaps of the original TypeScript to the client. This
|
||||
could be added if a strong need emerges.
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Connection, ConnectionBackend, Http, XHRBackend, RequestOptions, Reques
|
||||
@Injectable()
|
||||
export class CachePrimedConnectionBackend extends ConnectionBackend {
|
||||
private _preCachedResponses: PreCachedResponses;
|
||||
|
||||
|
||||
constructor(private _underlyingBackend: ConnectionBackend, private _baseResponseOptions: ResponseOptions) {
|
||||
super();
|
||||
this._preCachedResponses = (<any>window).__preCachedResponses || {};
|
||||
}
|
||||
|
||||
|
||||
public createConnection(request: Request): Connection {
|
||||
let cacheKey = request.url;
|
||||
if (request.method === RequestMethod.Get && this._preCachedResponses.hasOwnProperty(cacheKey)) {
|
||||
@@ -28,7 +28,7 @@ class CacheHitConnection implements Connection {
|
||||
constructor (req: Request, cachedResponse: PreCachedResponse, baseResponseOptions: ResponseOptions) {
|
||||
this.request = req;
|
||||
this.readyState = ReadyState.Done;
|
||||
|
||||
|
||||
// Workaround for difficulty consuming CommonJS default exports in TypeScript. Note that it has to be a dynamic
|
||||
// 'require', and not an 'import' statement, because the module isn't available on the server.
|
||||
let obsCtor: any = require('rxjs/Observable').Observable;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ControlGroup } from 'angular2/common';
|
||||
import { Response } from 'angular2/http';
|
||||
|
||||
export class Validation {
|
||||
|
||||
|
||||
public static showValidationErrors(response: ValidationErrorResult | Response, controlGroup: ControlGroup): void {
|
||||
if (response instanceof Response) {
|
||||
var httpResponse = <Response>response;
|
||||
@@ -10,19 +10,19 @@ export class Validation {
|
||||
}
|
||||
|
||||
// It's not yet clear whether this is a legitimate and supported use of the ng.ControlGroup API.
|
||||
// Need feedback from the Angular 2 team on whether there's a better way.
|
||||
// Need feedback from the Angular 2 team on whether there's a better way.
|
||||
var errors = <ValidationErrorResult>response;
|
||||
Object.keys(errors || {}).forEach(key => {
|
||||
errors[key].forEach(errorMessage => {
|
||||
// If there's a specific control for this key, then use it. Otherwise associate the error
|
||||
// with the whole control group.
|
||||
var control = controlGroup.controls[key] || controlGroup;
|
||||
|
||||
|
||||
// This is rough. Need to find out if there's a better way, or if this is even supported.
|
||||
if (!control.errors) {
|
||||
(<any>control)._errors = {};
|
||||
}
|
||||
|
||||
|
||||
control.errors[errorMessage] = true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
HostingModel = NodeHostingModel.Http,
|
||||
WatchFileExtensions = defaultWatchFileExtensions
|
||||
};
|
||||
|
||||
|
||||
public static void AddNodeServices(this IServiceCollection serviceCollection) {
|
||||
AddNodeServices(serviceCollection, defaultOptions);
|
||||
}
|
||||
|
||||
|
||||
public static void AddNodeServices(this IServiceCollection serviceCollection, NodeServicesOptions options) {
|
||||
serviceCollection.AddSingleton(typeof(INodeServices), (serviceProvider) => {
|
||||
var appEnv = serviceProvider.GetRequiredService<IApplicationEnvironment>();
|
||||
@@ -42,7 +42,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
public NodeHostingModel HostingModel { get; set; }
|
||||
public string ProjectPath { get; set; }
|
||||
public string[] WatchFileExtensions { get; set; }
|
||||
|
||||
|
||||
public NodeServicesOptions() {
|
||||
this.HostingModel = NodeHostingModel.Http;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ var server = http.createServer(function(req, res) {
|
||||
hasSentResult = true;
|
||||
if (errorValue) {
|
||||
res.statusCode = 500;
|
||||
|
||||
|
||||
if (errorValue.stack) {
|
||||
res.end(errorValue.stack);
|
||||
} else {
|
||||
@@ -41,19 +41,19 @@ var server = http.createServer(function(req, res) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
func.apply(null, [callback].concat(bodyJson.args));
|
||||
} catch (synchronousException) {
|
||||
callback(synchronousException, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(requestedPortOrZero, 'localhost', function () {
|
||||
// Signal to HttpNodeHost which port it should make its HTTP connections on
|
||||
console.log('[Microsoft.AspNet.NodeServices.HttpNodeHost:Listening on port ' + server.address().port + '\]');
|
||||
|
||||
|
||||
// Signal to the NodeServices base class that we're ready to accept invocations
|
||||
console.log('[Microsoft.AspNet.NodeServices:Listening]');
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ function invocationCallback(errorValue, successValue) {
|
||||
}
|
||||
|
||||
readline.createInterface({ input: process.stdin }).on('line', function (message) {
|
||||
if (message && message.substring(0, invocationPrefix.length) === invocationPrefix) {
|
||||
if (message && message.substring(0, invocationPrefix.length) === invocationPrefix) {
|
||||
var invocation = JSON.parse(message.substring(invocationPrefix.length));
|
||||
var invokedModule = require(path.resolve(process.cwd(), invocation.moduleName));
|
||||
var func = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
|
||||
|
||||
@@ -10,40 +10,40 @@ using Newtonsoft.Json.Serialization;
|
||||
namespace Microsoft.AspNet.NodeServices {
|
||||
internal class HttpNodeInstance : OutOfProcessNodeInstance {
|
||||
private readonly static Regex PortMessageRegex = new Regex(@"^\[Microsoft.AspNet.NodeServices.HttpNodeHost:Listening on port (\d+)\]$");
|
||||
|
||||
private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
|
||||
private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
|
||||
private int _portNumber;
|
||||
|
||||
|
||||
public HttpNodeInstance(string projectPath, int port = 0, string[] watchFileExtensions = null)
|
||||
: base(EmbeddedResourceReader.Read(typeof(HttpNodeInstance), "/Content/Node/entrypoint-http.js"), projectPath, MakeCommandLineOptions(port, watchFileExtensions))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
private static string MakeCommandLineOptions(int port, string[] watchFileExtensions) {
|
||||
var result = "--port " + port.ToString();
|
||||
if (watchFileExtensions != null && watchFileExtensions.Length > 0) {
|
||||
result += " --watch " + string.Join(",", watchFileExtensions);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public override async Task<T> Invoke<T>(NodeInvocationInfo invocationInfo) {
|
||||
await this.EnsureReady();
|
||||
|
||||
|
||||
using (var client = new HttpClient()) {
|
||||
// TODO: Use System.Net.Http.Formatting (PostAsJsonAsync etc.)
|
||||
var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
|
||||
var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
|
||||
var response = await client.PostAsync("http://localhost:" + this._portNumber, payload);
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode) {
|
||||
throw new Exception("Call to Node module failed with error: " + responseString);
|
||||
}
|
||||
|
||||
|
||||
var responseIsJson = response.Content.Headers.ContentType.MediaType == "application/json";
|
||||
if (responseIsJson) {
|
||||
return JsonConvert.DeserializeObject<T>(responseString);
|
||||
@@ -54,7 +54,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnOutputDataReceived(string outputData) {
|
||||
var match = this._portNumber != 0 ? null : PortMessageRegex.Match(outputData);
|
||||
if (match != null && match.Success) {
|
||||
@@ -63,7 +63,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
base.OnOutputDataReceived(outputData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnBeforeLaunchProcess() {
|
||||
// Prepare to receive a new port number
|
||||
this._portNumber = 0;
|
||||
|
||||
@@ -19,21 +19,21 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
{
|
||||
private SemaphoreSlim _invocationSemaphore = new SemaphoreSlim(1);
|
||||
private TaskCompletionSource<string> _currentInvocationResult;
|
||||
|
||||
private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
|
||||
private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings {
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
|
||||
public InputOutputStreamNodeInstance(string projectPath)
|
||||
: base(EmbeddedResourceReader.Read(typeof(InputOutputStreamNodeInstance), "/Content/Node/entrypoint-stream.js"), projectPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override async Task<T> Invoke<T>(NodeInvocationInfo invocationInfo) {
|
||||
await this._invocationSemaphore.WaitAsync();
|
||||
try {
|
||||
await this.EnsureReady();
|
||||
|
||||
|
||||
var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
|
||||
var nodeProcess = this.NodeProcess;
|
||||
this._currentInvocationResult = new TaskCompletionSource<string>();
|
||||
@@ -46,7 +46,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
this._currentInvocationResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnOutputDataReceived(string outputData) {
|
||||
if (this._currentInvocationResult != null) {
|
||||
this._currentInvocationResult.SetResult(outputData);
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
return this._nodeProcess;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public OutOfProcessNodeInstance(string entryPointScript, string projectPath, string commandLineArguments = null)
|
||||
{
|
||||
this._childProcessLauncherLock = new object();
|
||||
@@ -32,13 +32,13 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
this._projectPath = projectPath;
|
||||
this._commandLineArguments = commandLineArguments ?? string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public abstract Task<T> Invoke<T>(NodeInvocationInfo invocationInfo);
|
||||
|
||||
|
||||
public Task<T> Invoke<T>(string moduleName, params object[] args) {
|
||||
return this.InvokeExport<T>(moduleName, null, args);
|
||||
}
|
||||
|
||||
|
||||
public async Task<T> InvokeExport<T>(string moduleName, string exportedFunctionName, params object[] args) {
|
||||
return await this.Invoke<T>(new NodeInvocationInfo {
|
||||
ModuleName = moduleName,
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
WorkingDirectory = this._projectPath
|
||||
WorkingDirectory = this._projectPath
|
||||
};
|
||||
|
||||
// Append projectPath to NODE_PATH so it can locate node_modules
|
||||
@@ -149,4 +149,4 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
Dispose (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.NodeServices {
|
||||
public interface INodeServices : IDisposable {
|
||||
public interface INodeServices : IDisposable {
|
||||
Task<T> Invoke<T>(string moduleName, params object[] args);
|
||||
|
||||
|
||||
Task<T> InvokeExport<T>(string moduleName, string exportedFunctionName, params object[] args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -3,4 +3,4 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
Http,
|
||||
InputOutputStream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.NodeServices {
|
||||
public static class EmbeddedResourceReader {
|
||||
public static string Read(Type assemblyContainingType, string path) {
|
||||
public static string Read(Type assemblyContainingType, string path) {
|
||||
var asm = assemblyContainingType.GetTypeInfo().Assembly;
|
||||
var embeddedResourceName = asm.GetName().Name + path.Replace("/", ".");
|
||||
|
||||
|
||||
using (var stream = asm.GetManifestResourceStream(embeddedResourceName))
|
||||
using (var sr = new StreamReader(stream)) {
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Microsoft.AspNet.NodeServices {
|
||||
public string FileName { get; private set; }
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
|
||||
public StringAsTempFile(string content) {
|
||||
this.FileName = Path.GetTempFileName();
|
||||
File.WriteAllText(this.FileName, content);
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -21,4 +21,4 @@ if (typeof (<any>Object).assign != 'function') {
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ export type ReactComponentClass<T, S> = new(props: T) => React.Component<T, S>;
|
||||
export class ComponentBuilder<TOwnProps, TActions, TExternalProps> {
|
||||
constructor(private stateToProps: (appState: any) => TOwnProps, private actionCreators: TActions) {
|
||||
}
|
||||
|
||||
|
||||
public withExternalProps<TAddExternalProps>() {
|
||||
return this as any as ComponentBuilder<TOwnProps, TActions, TAddExternalProps>;
|
||||
}
|
||||
|
||||
|
||||
public get allProps(): TOwnProps & TActions & TExternalProps { return null; }
|
||||
|
||||
|
||||
public connect<TState>(componentClass: ReactComponentClass<TOwnProps & TActions & TExternalProps, TState>): ReactComponentClass<TExternalProps, TState> {
|
||||
return nativeConnect(this.stateToProps, this.actionCreators as any)(componentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function provide<TOwnProps, TActions>(stateToProps: (appState: any) => TOwnProps, actionCreators: TActions) {
|
||||
|
||||
@@ -26,7 +26,7 @@ function loadViaWebpackNoCache(webpackConfigPath, modulePath) {
|
||||
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
|
||||
// The CommonsChunkPlugin is not compatible with a CommonJS environment like Node, nor is it needed in that case
|
||||
webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) {
|
||||
return !(plugin instanceof webpack.optimize.CommonsChunkPlugin);
|
||||
});
|
||||
@@ -70,7 +70,7 @@ var domainTask = require('domain-task');
|
||||
var baseUrl = require('domain-task/fetch').baseUrl;
|
||||
|
||||
function findBootModule(bootModule, callback) {
|
||||
var bootModuleNameFullPath = path.resolve(process.cwd(), bootModule.moduleName);
|
||||
var bootModuleNameFullPath = path.resolve(process.cwd(), bootModule.moduleName);
|
||||
if (bootModule.webpackConfig) {
|
||||
var webpackConfigFullPath = path.resolve(process.cwd(), bootModule.webpackConfig);
|
||||
loadViaWebpack(webpackConfigFullPath, bootModuleNameFullPath, callback);
|
||||
@@ -86,7 +86,7 @@ function findBootFunc(bootModule, callback) {
|
||||
callback(findBootModuleError);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Now try to pick out the function they want us to invoke
|
||||
var bootFunc;
|
||||
if (bootModule.exportName) {
|
||||
@@ -99,7 +99,7 @@ function findBootFunc(bootModule, callback) {
|
||||
// Native default export
|
||||
bootFunc = foundBootModule;
|
||||
}
|
||||
|
||||
|
||||
// Validate the result
|
||||
if (typeof bootFunc !== 'function') {
|
||||
if (bootModule.exportName) {
|
||||
@@ -119,7 +119,7 @@ function renderToString(callback, bootModule, absoluteRequestUrl, requestPathAnd
|
||||
callback(findBootFuncError);
|
||||
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.
|
||||
var domainTaskCompletionPromiseResolve;
|
||||
@@ -138,10 +138,10 @@ function renderToString(callback, bootModule, absoluteRequestUrl, requestPathAnd
|
||||
// Workaround for Node bug where native Promise continuations lose their domain context
|
||||
// (https://github.com/nodejs/node-v0.x-archive/issues/8648)
|
||||
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(function(successResult) {
|
||||
callback(null, { html: successResult.html, globals: successResult.globals });
|
||||
@@ -156,12 +156,12 @@ function renderToString(callback, bootModule, absoluteRequestUrl, requestPathAnd
|
||||
} else {
|
||||
domainTaskCompletionPromiseResolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindPromiseContinuationsToDomain(promise, domainInstance) {
|
||||
var originalThen = promise.then;
|
||||
var 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); }
|
||||
|
||||
@@ -10,10 +10,10 @@ module.exports = {
|
||||
if (!publicPath) {
|
||||
throw new Error('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack.config.');
|
||||
}
|
||||
|
||||
|
||||
var enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
|
||||
var enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
|
||||
|
||||
|
||||
var app = new express();
|
||||
var listener = app.listen(defaultPort, function() {
|
||||
// Build the final Webpack config based on supplied options
|
||||
@@ -22,9 +22,9 @@ module.exports = {
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
);
|
||||
|
||||
|
||||
if (enableReactHotModuleReplacement) {
|
||||
addReactHotModuleReplacementBabelTransform(webpackConfig);
|
||||
addReactHotModuleReplacementBabelTransform(webpackConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function addReactHotModuleReplacementBabelTransform(webpackConfig) {
|
||||
if (loaderConfig.loader && loaderConfig.loader.match(/\bbabel-loader\b/)) {
|
||||
// Ensure the babel-loader options includes a 'query'
|
||||
var query = loaderConfig.query = loaderConfig.query || {};
|
||||
|
||||
|
||||
// Ensure Babel plugins includes 'react-transform'
|
||||
var plugins = query.plugins = query.plugins || [];
|
||||
if (!plugins.some(function(pluginConfig) {
|
||||
@@ -61,7 +61,7 @@ function addReactHotModuleReplacementBabelTransform(webpackConfig) {
|
||||
})) {
|
||||
plugins.push(['react-transform', {}]);
|
||||
}
|
||||
|
||||
|
||||
// Ensure 'react-transform' plugin is configured to use 'react-transform-hmr'
|
||||
plugins.forEach(function(pluginConfig) {
|
||||
if (pluginConfig && pluginConfig[0] === 'react-transform') {
|
||||
@@ -86,6 +86,6 @@ function removeTrailingSlash(str) {
|
||||
if (str.lastIndexOf('/') === str.length - 1) {
|
||||
str = str.substring(0, str.length - 1);
|
||||
}
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -14,17 +14,17 @@ namespace Microsoft.AspNet.SpaServices.Prerendering
|
||||
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; }
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.AspNet.SpaServices.Prerendering
|
||||
{
|
||||
this.contextAccessor = contextAccessor;
|
||||
this.nodeServices = (INodeServices)serviceProvider.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
|
||||
|
||||
|
||||
// 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) {
|
||||
@@ -59,7 +59,7 @@ namespace Microsoft.AspNet.SpaServices.Prerendering
|
||||
requestAbsoluteUrl: UriHelper.GetEncodedUrl(this.contextAccessor.HttpContext.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) {
|
||||
|
||||
@@ -8,32 +8,32 @@ 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(INodeServices nodeServices, JavaScriptModuleExport bootModule, string requestAbsoluteUrl, string requestPathAndQuery) {
|
||||
return await nodeServices.InvokeExport<RenderToStringResult>(nodeScript.Value.FileName, "renderToString",
|
||||
return await nodeServices.InvokeExport<RenderToStringResult>(nodeScript.Value.FileName, "renderToString",
|
||||
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;
|
||||
|
||||
@@ -8,21 +8,21 @@ 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, IDictionary<string, object> values, RouteDirection routeDirection)
|
||||
{
|
||||
var clientRouteValue = (values[this.clientRouteTokenName] as string) ?? string.Empty;
|
||||
return !HasDotInLastSegment(clientRouteValue);
|
||||
}
|
||||
|
||||
|
||||
private bool HasDotInLastSegment(string uri)
|
||||
{
|
||||
var lastSegmentStartPos = uri.LastIndexOf('/');
|
||||
|
||||
@@ -33,18 +33,18 @@ namespace Microsoft.AspNet.Builder
|
||||
// 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);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Builder
|
||||
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
|
||||
@@ -46,14 +46,14 @@ namespace Microsoft.AspNet.Builder
|
||||
};
|
||||
var devServerInfo = nodeServices.InvokeExport<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer", JsonConvert.SerializeObject(devServerOptions)).Result;
|
||||
|
||||
// Proxy the corresponding requests through ASP.NET and into the Node listener
|
||||
// Proxy the corresponding requests through ASP.NET and into the Node listener
|
||||
appBuilder.Map(devServerInfo.PublicPath, builder => {
|
||||
builder.RunProxy(new ProxyOptions {
|
||||
Host = WebpackDevMiddlewareHostname,
|
||||
Port = devServerInfo.Port.ToString()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 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.
|
||||
@@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Builder
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#pragma warning disable CS0649
|
||||
class WebpackDevServerInfo {
|
||||
public int Port;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as url from 'url';
|
||||
import * as domain from 'domain';
|
||||
import * as domain from 'domain';
|
||||
import * as domainContext from 'domain-context';
|
||||
import { addTask } from './main';
|
||||
const isomorphicFetch = require('isomorphic-fetch');
|
||||
@@ -37,7 +37,7 @@ function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit
|
||||
if (!init.headers['Connection']) {
|
||||
init.headers['Connection'] = 'keep-alive';
|
||||
}
|
||||
|
||||
|
||||
return isomorphicFetch(req, init);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,6 @@ export function baseUrl(url?: string): string {
|
||||
noDomainBaseUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return domain.active ? domainContext.get(domainTaskStateKey) : noDomainBaseUrl;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function run<T>(codeToRun: () => T, completionCallback: (error: any) => v
|
||||
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;
|
||||
@@ -48,7 +48,7 @@ export function run<T>(codeToRun: () => T, completionCallback: (error: any) => v
|
||||
state.completionCallback(ex);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return synchronousResult;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user