X Tutup
Skip to content

Commit 7c3d22c

Browse files
Add react tag helper. Clean up code and make it more consistent.
1 parent e410aff commit 7c3d22c

File tree

14 files changed

+146
-63
lines changed

14 files changed

+146
-63
lines changed

Microsoft.AspNet.NodeServices.Angular/AngularPrerenderTagHelper.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,10 @@
77
namespace Microsoft.AspNet.NodeServices.Angular
88
{
99
[HtmlTargetElement(Attributes = PrerenderModuleAttributeName)]
10-
public class AngularRunAtServerTagHelper : TagHelper
10+
public class AngularPrerenderTagHelper : TagHelper
1111
{
12-
static StringAsTempFile nodeScript;
1312
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
1413

15-
static AngularRunAtServerTagHelper() {
16-
// Consider populating this lazily
17-
var script = EmbeddedResourceReader.Read(typeof (AngularRunAtServerTagHelper), "/Content/Node/angular-rendering.js");
18-
nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
19-
}
20-
2114
const string PrerenderModuleAttributeName = "asp-ng2-prerender-module";
2215
const string PrerenderExportAttributeName = "asp-ng2-prerender-export";
2316

@@ -30,7 +23,7 @@ static AngularRunAtServerTagHelper() {
3023
private IHttpContextAccessor contextAccessor;
3124
private INodeServices nodeServices;
3225

33-
public AngularRunAtServerTagHelper(IServiceProvider nodeServices, IHttpContextAccessor contextAccessor)
26+
public AngularPrerenderTagHelper(IServiceProvider nodeServices, IHttpContextAccessor contextAccessor)
3427
{
3528
this.contextAccessor = contextAccessor;
3629
this.nodeServices = (INodeServices)nodeServices.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
@@ -44,12 +37,13 @@ public AngularRunAtServerTagHelper(IServiceProvider nodeServices, IHttpContextAc
4437

4538
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
4639
{
47-
var result = await this.nodeServices.InvokeExport(nodeScript.FileName, "renderComponent", new {
48-
componentModule = this.ModuleName,
49-
componentExport = this.ExportName,
50-
tagName = output.TagName,
51-
baseUrl = UriHelper.GetEncodedUrl(this.contextAccessor.HttpContext.Request)
52-
});
40+
var result = await AngularRenderer.RenderToString(
41+
nodeServices: this.nodeServices,
42+
componentModuleName: this.ModuleName,
43+
componentExportName: this.ExportName,
44+
componentTagName: output.TagName,
45+
requestUrl: UriHelper.GetEncodedUrl(this.contextAccessor.HttpContext.Request)
46+
);
5347
output.SuppressOutput();
5448
output.PostElement.AppendEncoded(result);
5549
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Threading.Tasks;
2+
3+
namespace Microsoft.AspNet.NodeServices.Angular
4+
{
5+
public static class AngularRenderer
6+
{
7+
private static StringAsTempFile nodeScript;
8+
9+
static AngularRenderer() {
10+
// Consider populating this lazily
11+
var script = EmbeddedResourceReader.Read(typeof (AngularRenderer), "/Content/Node/angular-rendering.js");
12+
nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
13+
}
14+
15+
public static async Task<string> RenderToString(INodeServices nodeServices, string componentModuleName, string componentExportName, string componentTagName, string requestUrl) {
16+
return await nodeServices.InvokeExport(nodeScript.FileName, "renderToString", new {
17+
moduleName = componentModuleName,
18+
exportName = componentExportName,
19+
tagName = componentTagName,
20+
requestUrl = requestUrl
21+
});
22+
}
23+
}
24+
}

Microsoft.AspNet.NodeServices.Angular/Content/Node/angular-rendering.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,39 @@ var ngUniversal = require('angular2-universal-patched');
33
var ng = require('angular2/angular2');
44
var ngRouter = require('angular2/router');
55

6-
module.exports = {
7-
renderComponent: function(callback, options) {
8-
// Find the component class. Use options.componentExport if specified, otherwise convert tag-name to PascalCase.
9-
var loadedModule = require(path.resolve(process.cwd(), options.componentModule));
10-
var componentExport = options.componentExport || options.tagName.replace(/(-|^)([a-z])/g, function (m1, m2, char) { return char.toUpperCase(); });
11-
var component = loadedModule[componentExport];
12-
if (!component) {
13-
throw new Error('The module "' + options.componentModule + '" has no export named "' + componentExport + '"');
14-
}
6+
function getExportOrThrow(moduleInstance, moduleFilename, exportName) {
7+
if (!(exportName in moduleInstance)) {
8+
throw new Error('The module "' + moduleFilename + '" has no export named "' + exportName + '"');
9+
}
10+
return moduleInstance[exportName];
11+
}
1512

13+
function findAngularComponent(options) {
14+
var resolvedPath = path.resolve(process.cwd(), options.moduleName);
15+
var loadedModule = require(resolvedPath);
16+
if (options.exportName) {
17+
// If exportName is specified explicitly, use it
18+
return getExportOrThrow(loadedModule, resolvedPath, options.exportName);
19+
} else if (typeof loadedModule === 'function') {
20+
// Otherwise, if the module itself is a function, assume that is the component
21+
return loadedModule;
22+
} else if (typeof loadedModule.default === 'function') {
23+
// Otherwise, if the module has a default export which is a function, assume that is the component
24+
return loadedModule.default;
25+
} else {
26+
// Otherwise, guess the export name by converting tag-name to PascalCase
27+
var tagNameAsPossibleExport = options.tagName.replace(/(-|^)([a-z])/g, function (m1, m2, char) { return char.toUpperCase(); });
28+
return getExportOrThrow(loadedModule, resolvedPath, tagNameAsPossibleExport);
29+
}
30+
}
31+
32+
module.exports = {
33+
renderToString: function(callback, options) {
34+
var component = findAngularComponent(options);
1635
var serverBindings = [
1736
ngRouter.ROUTER_BINDINGS,
1837
ngUniversal.HTTP_PROVIDERS,
19-
ng.provide(ngUniversal.BASE_URL, { useValue: options.baseUrl }),
38+
ng.provide(ngUniversal.BASE_URL, { useValue: options.requestUrl }),
2039
ngUniversal.SERVER_LOCATION_PROVIDERS
2140
];
2241

Microsoft.AspNet.NodeServices.Angular/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.0.0-alpha3",
2+
"version": "1.0.0-alpha4",
33
"description": "Microsoft.AspNet.NodeServices.Angular Class Library",
44
"authors": [
55
"Microsoft"

Microsoft.AspNet.NodeServices.React/Content/Node/react-rendering.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ var origJsLoader = require.extensions['.js'];
1010
require.extensions['.js'] = loadViaBabel;
1111
require.extensions['.jsx'] = loadViaBabel;
1212

13+
function findReactComponent(options) {
14+
var resolvedPath = path.resolve(process.cwd(), options.moduleName);
15+
var loadedModule = require(resolvedPath);
16+
if (options.exportName) {
17+
// If exportName is specified explicitly, use it
18+
if (!(options.exportName in loadedModule)) {
19+
throw new Error('The module "' + resolvedPath + '" has no export named "' + options.exportName + '"');
20+
}
21+
return loadedModule[options.exportName];
22+
} else if (typeof loadedModule === 'function') {
23+
// Otherwise, if the module itself is a function, assume that is the component
24+
return loadedModule;
25+
} else if (typeof loadedModule.default === 'function') {
26+
// Otherwise, if the module has a default export which is a function, assume that is the component
27+
return loadedModule.default;
28+
} else {
29+
throw new Error('Cannot find React component, because no export name was specified, and the module "' + resolvedPath + '" has no default exported class.');
30+
}
31+
}
32+
1333
function loadViaBabel(module, filename) {
1434
// Assume that all the app's own code is ES2015+ (optionally with JSX), but that none of the node_modules are.
1535
// The distinction is important because ES2015+ forces strict mode, and it may break ES3/5 if you try to run it in strict
@@ -25,14 +45,8 @@ function loadViaBabel(module, filename) {
2545

2646
module.exports = {
2747
renderToString: function(callback, options) {
28-
var resolvedPath = path.resolve(process.cwd(), options.moduleName);
29-
var requestedModule = require(resolvedPath);
30-
var component = options.exportName ? requestedModule[options.exportName] : requestedModule;
31-
if (!component) {
32-
throw new Error('The module "' + resolvedPath + '" has no export named "' + options.exportName + '"');
33-
}
34-
35-
var history = createMemoryHistory(options.baseUrl);
48+
var component = findReactComponent(options);
49+
var history = createMemoryHistory(options.requestUrl);
3650
var reactElement = React.createElement(component, { history: history });
3751
var html = ReactDOMServer.renderToString(reactElement);
3852
callback(null, html);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
4+
using Microsoft.AspNet.Http;
5+
using Microsoft.AspNet.Http.Extensions;
6+
7+
namespace Microsoft.AspNet.NodeServices.React
8+
{
9+
[HtmlTargetElement(Attributes = PrerenderModuleAttributeName)]
10+
public class ReactPrerenderTagHelper : TagHelper
11+
{
12+
static INodeServices fallbackNodeServices; // Used only if no INodeServices was registered with DI
13+
14+
const string PrerenderModuleAttributeName = "asp-react-prerender-module";
15+
const string PrerenderExportAttributeName = "asp-react-prerender-export";
16+
17+
[HtmlAttributeName(PrerenderModuleAttributeName)]
18+
public string ModuleName { get; set; }
19+
20+
[HtmlAttributeName(PrerenderExportAttributeName)]
21+
public string ExportName { get; set; }
22+
23+
private IHttpContextAccessor contextAccessor;
24+
private INodeServices nodeServices;
25+
26+
public ReactPrerenderTagHelper(IServiceProvider nodeServices, IHttpContextAccessor contextAccessor)
27+
{
28+
this.contextAccessor = contextAccessor;
29+
this.nodeServices = (INodeServices)nodeServices.GetService(typeof (INodeServices)) ?? fallbackNodeServices;
30+
31+
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
32+
// in your startup file, but then again it might be confusing that you don't need to.
33+
if (this.nodeServices == null) {
34+
this.nodeServices = fallbackNodeServices = Configuration.CreateNodeServices(NodeHostingModel.Http);
35+
}
36+
}
37+
38+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
39+
{
40+
var request = this.contextAccessor.HttpContext.Request;
41+
var result = await ReactRenderer.RenderToString(
42+
nodeServices: this.nodeServices,
43+
componentModuleName: this.ModuleName,
44+
componentExportName: this.ExportName,
45+
requestUrl: request.Path + request.QueryString.Value);
46+
output.Content.SetContentEncoded(result);
47+
}
48+
}
49+
}

Microsoft.AspNet.NodeServices.React/ReactRenderer.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,11 @@ static ReactRenderer() {
1212
nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit
1313
}
1414

15-
public static Task<string> RenderToString(INodeServices nodeServices, string moduleName, string baseUrl) {
16-
return RenderToString(nodeServices, moduleName, /* exportName */ null, baseUrl);
17-
}
18-
19-
public static async Task<string> RenderToString(INodeServices nodeServices, string moduleName, string exportName, string baseUrl) {
15+
public static async Task<string> RenderToString(INodeServices nodeServices, string componentModuleName, string componentExportName, string requestUrl) {
2016
return await nodeServices.InvokeExport(nodeScript.FileName, "renderToString", new {
21-
moduleName,
22-
exportName,
23-
baseUrl
17+
moduleName = componentModuleName,
18+
exportName = componentExportName,
19+
requestUrl = requestUrl
2420
});
2521
}
2622
}

Microsoft.AspNet.NodeServices.React/project.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.0.0-alpha3",
2+
"version": "1.0.0-alpha4",
33
"description": "Microsoft.AspNet.NodeServices.React Class Library",
44
"authors": [
55
"Microsoft"
@@ -25,7 +25,8 @@
2525
}
2626
},
2727
"dependencies": {
28-
"Microsoft.AspNet.NodeServices": "1.0.0-alpha3"
28+
"Microsoft.AspNet.NodeServices": "1.0.0-alpha3",
29+
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8"
2930
},
3031
"resource": [
3132
"Content/**/*"

samples/angular/MusicStore/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"EntityFramework.SQLite": "7.0.0-beta8",
2020
"Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta8",
2121
"AutoMapper": "4.0.0-alpha1",
22-
"Microsoft.AspNet.NodeServices.Angular": "1.0.0-alpha3"
22+
"Microsoft.AspNet.NodeServices.Angular": "1.0.0-alpha4"
2323
},
2424
"commands": {
2525
"web": "Microsoft.AspNet.Server.Kestrel"

samples/react/ReactGrid/Controllers/HomeController.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
11
using System.Threading.Tasks;
22
using Microsoft.AspNet.Mvc;
3-
using Microsoft.AspNet.NodeServices;
4-
using Microsoft.AspNet.NodeServices.React;
53

64
namespace ReactExample.Controllers
75
{
86
public class HomeController : Controller
97
{
10-
private INodeServices nodeServices;
11-
12-
public HomeController(INodeServices nodeServices) {
13-
this.nodeServices = nodeServices;
14-
}
15-
16-
public async Task<IActionResult> Index(int pageIndex)
8+
public IActionResult Index(int pageIndex)
179
{
18-
ViewData["ReactOutput"] = await ReactRenderer.RenderToString(this.nodeServices,
19-
moduleName: "ReactApp/components/ReactApp.jsx",
20-
baseUrl: Request.Path
21-
);
2210
return View();
2311
}
2412

0 commit comments

Comments
 (0)
X Tutup