Skip to content

Commit 17f9ece

Browse files
Support new prerendering mode that doesn't require you to deploy node_modules to production. This is a breaking change in aspnet-prerendering, hence the major version bump. The NuGet package is back-compatible though.
1 parent 9f6b0b0 commit 17f9ece

File tree

9 files changed

+235
-122
lines changed

9 files changed

+235
-122
lines changed

src/Microsoft.AspNetCore.SpaServices/Content/Node/prerenderer.js

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,105 @@
5252
/***/ function(module, exports, __webpack_require__) {
5353

5454
"use strict";
55-
// Pass through the invocation to the 'aspnet-prerendering' package, verifying that it can be loaded
56-
function renderToString(callback) {
57-
var aspNetPrerendering;
55+
var path = __webpack_require__(2);
56+
// Separate declaration and export just to add type checking on function signature
57+
exports.renderToString = renderToStringImpl;
58+
// This function is invoked by .NET code (via NodeServices). Its job is to hand off execution to the application's
59+
// prerendering boot function. It can operate in two modes:
60+
// [1] Legacy mode
61+
// This is for backward compatibility with projects created with templates older than the generator version 0.6.0.
62+
// In this mode, we don't really do anything here - we just load the 'aspnet-prerendering' NPM module (which must
63+
// exist in node_modules, and must be v1.x (not v2+)), and pass through all the parameters to it. Code in
64+
// 'aspnet-prerendering' v1.x will locate the boot function and invoke it.
65+
// The drawback to this mode is that, for it to work, you have to deploy node_modules to production.
66+
// [2] Current mode
67+
// This is for projects created with the Yeoman generator 0.6.0+ (or projects manually updated). In this mode,
68+
// we don't invoke 'require' at runtime at all. All our dependencies are bundled into the NuGet package, so you
69+
// don't have to deploy node_modules to production.
70+
// To determine whether we're in mode [1] or [2], the code locates your prerendering boot function, and checks whether
71+
// a certain flag is attached to the function instance.
72+
function renderToStringImpl(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds) {
5873
try {
59-
aspNetPrerendering = __webpack_require__(2);
74+
var renderToStringFunc = findRenderToStringFunc(applicationBasePath, bootModule);
75+
var isNotLegacyMode = renderToStringFunc && renderToStringFunc['isServerRenderer'];
76+
if (isNotLegacyMode) {
77+
// Current (non-legacy) mode - we invoke the exported function directly (instead of going through aspnet-prerendering)
78+
// It's type-safe to just apply the incoming args to this function, because we already type-checked that it's a RenderToStringFunc,
79+
// just like renderToStringImpl itself is.
80+
renderToStringFunc.apply(null, arguments);
81+
}
82+
else {
83+
// Legacy mode - just hand off execution to 'aspnet-prerendering' v1.x, which must exist in node_modules at runtime
84+
renderToStringFunc = __webpack_require__(3).renderToString;
85+
if (renderToStringFunc) {
86+
renderToStringFunc(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds);
87+
}
88+
else {
89+
callback('If you use aspnet-prerendering >= 2.0.0, you must update your server-side boot module to call createServerRenderer. '
90+
+ 'Either update your boot module code, or revert to aspnet-prerendering version 1.x');
91+
}
92+
}
6093
}
6194
catch (ex) {
62-
// Developers sometimes have trouble with badly-configured Node installations, where it's unable
63-
// to find node_modules. Or they accidentally fail to deploy node_modules, or even to run 'npm install'.
64-
// Make sure such errors are reported back to the .NET part of the app.
65-
callback('Prerendering failed because of an error while loading \'aspnet-prerendering\'. Error was: '
95+
// Make sure loading errors are reported back to the .NET part of the app
96+
callback('Prerendering failed because of error: '
6697
+ ex.stack
6798
+ '\nCurrent directory is: '
6899
+ process.cwd());
69-
return;
70100
}
71-
return aspNetPrerendering.renderToString.apply(this, arguments);
72101
}
73-
exports.renderToString = renderToString;
102+
;
103+
function findBootModule(applicationBasePath, bootModule) {
104+
var bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
105+
if (bootModule.webpackConfig) {
106+
// If you're using asp-prerender-webpack-config, you're definitely in legacy mode
107+
return null;
108+
}
109+
else {
110+
return require(bootModuleNameFullPath);
111+
}
112+
}
113+
function findRenderToStringFunc(applicationBasePath, bootModule) {
114+
// First try to load the module
115+
var foundBootModule = findBootModule(applicationBasePath, bootModule);
116+
if (foundBootModule === null) {
117+
return null; // Must be legacy mode
118+
}
119+
// Now try to pick out the function they want us to invoke
120+
var renderToStringFunc;
121+
if (bootModule.exportName) {
122+
// Explicitly-named export
123+
renderToStringFunc = foundBootModule[bootModule.exportName];
124+
}
125+
else if (typeof foundBootModule !== 'function') {
126+
// TypeScript-style default export
127+
renderToStringFunc = foundBootModule.default;
128+
}
129+
else {
130+
// Native default export
131+
renderToStringFunc = foundBootModule;
132+
}
133+
// Validate the result
134+
if (typeof renderToStringFunc !== 'function') {
135+
if (bootModule.exportName) {
136+
throw new Error("The module at " + bootModule.moduleName + " has no function export named " + bootModule.exportName + ".");
137+
}
138+
else {
139+
throw new Error("The module at " + bootModule.moduleName + " does not export a default function, and you have not specified which export to invoke.");
140+
}
141+
}
142+
return renderToStringFunc;
143+
}
74144

75145

76146
/***/ },
77147
/* 2 */
148+
/***/ function(module, exports) {
149+
150+
module.exports = require("path");
151+
152+
/***/ },
153+
/* 3 */
78154
/***/ function(module, exports) {
79155

80156
module.exports = require("aspnet-prerendering");

src/Microsoft.AspNetCore.SpaServices/Content/Node/webpack-dev-middleware.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,22 @@
4444
/* 0 */
4545
/***/ function(module, exports, __webpack_require__) {
4646

47-
module.exports = __webpack_require__(3);
47+
module.exports = __webpack_require__(4);
4848

4949

5050
/***/ },
5151
/* 1 */,
5252
/* 2 */,
53-
/* 3 */
53+
/* 3 */,
54+
/* 4 */
5455
/***/ function(module, exports, __webpack_require__) {
5556

5657
"use strict";
5758
// Pass through the invocation to the 'aspnet-webpack' package, verifying that it can be loaded
5859
function createWebpackDevServer(callback) {
5960
var aspNetWebpack;
6061
try {
61-
aspNetWebpack = __webpack_require__(4);
62+
aspNetWebpack = __webpack_require__(5);
6263
}
6364
catch (ex) {
6465
// Developers sometimes have trouble with badly-configured Node installations, where it's unable
@@ -76,7 +77,7 @@
7677

7778

7879
/***/ },
79-
/* 4 */
80+
/* 5 */
8081
/***/ function(module, exports) {
8182

8283
module.exports = require("aspnet-webpack");
Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,94 @@
1-
// Pass through the invocation to the 'aspnet-prerendering' package, verifying that it can be loaded
2-
export function renderToString(callback) {
3-
let aspNetPrerendering;
1+
/// <reference path="../npm/aspnet-prerendering/src/PrerenderingInterfaces.d.ts" />
2+
import * as url from 'url';
3+
import * as path from 'path';
4+
declare var __non_webpack_require__;
5+
6+
// Separate declaration and export just to add type checking on function signature
7+
export const renderToString: RenderToStringFunc = renderToStringImpl;
8+
9+
// This function is invoked by .NET code (via NodeServices). Its job is to hand off execution to the application's
10+
// prerendering boot function. It can operate in two modes:
11+
// [1] Legacy mode
12+
// This is for backward compatibility with projects created with templates older than the generator version 0.6.0.
13+
// In this mode, we don't really do anything here - we just load the 'aspnet-prerendering' NPM module (which must
14+
// exist in node_modules, and must be v1.x (not v2+)), and pass through all the parameters to it. Code in
15+
// 'aspnet-prerendering' v1.x will locate the boot function and invoke it.
16+
// The drawback to this mode is that, for it to work, you have to deploy node_modules to production.
17+
// [2] Current mode
18+
// This is for projects created with the Yeoman generator 0.6.0+ (or projects manually updated). In this mode,
19+
// we don't invoke 'require' at runtime at all. All our dependencies are bundled into the NuGet package, so you
20+
// don't have to deploy node_modules to production.
21+
// To determine whether we're in mode [1] or [2], the code locates your prerendering boot function, and checks whether
22+
// a certain flag is attached to the function instance.
23+
function renderToStringImpl(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) {
424
try {
5-
aspNetPrerendering = require('aspnet-prerendering');
25+
let renderToStringFunc = findRenderToStringFunc(applicationBasePath, bootModule);
26+
const isNotLegacyMode = renderToStringFunc && renderToStringFunc['isServerRenderer'];
27+
28+
if (isNotLegacyMode) {
29+
// Current (non-legacy) mode - we invoke the exported function directly (instead of going through aspnet-prerendering)
30+
// It's type-safe to just apply the incoming args to this function, because we already type-checked that it's a RenderToStringFunc,
31+
// just like renderToStringImpl itself is.
32+
renderToStringFunc.apply(null, arguments);
33+
} else {
34+
// Legacy mode - just hand off execution to 'aspnet-prerendering' v1.x, which must exist in node_modules at runtime
35+
renderToStringFunc = require('aspnet-prerendering').renderToString;
36+
if (renderToStringFunc) {
37+
renderToStringFunc(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery, customDataParameter, overrideTimeoutMilliseconds);
38+
} else {
39+
callback('If you use aspnet-prerendering >= 2.0.0, you must update your server-side boot module to call createServerRenderer. '
40+
+ 'Either update your boot module code, or revert to aspnet-prerendering version 1.x');
41+
}
42+
}
643
} catch (ex) {
7-
// Developers sometimes have trouble with badly-configured Node installations, where it's unable
8-
// to find node_modules. Or they accidentally fail to deploy node_modules, or even to run 'npm install'.
9-
// Make sure such errors are reported back to the .NET part of the app.
44+
// Make sure loading errors are reported back to the .NET part of the app
1045
callback(
11-
'Prerendering failed because of an error while loading \'aspnet-prerendering\'. Error was: '
46+
'Prerendering failed because of error: '
1247
+ ex.stack
1348
+ '\nCurrent directory is: '
1449
+ process.cwd()
1550
);
16-
return;
51+
}
52+
};
53+
54+
function findBootModule(applicationBasePath: string, bootModule: BootModuleInfo): any {
55+
const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
56+
if (bootModule.webpackConfig) {
57+
// If you're using asp-prerender-webpack-config, you're definitely in legacy mode
58+
return null;
59+
} else {
60+
return __non_webpack_require__(bootModuleNameFullPath);
61+
}
62+
}
63+
64+
function findRenderToStringFunc(applicationBasePath: string, bootModule: BootModuleInfo): RenderToStringFunc {
65+
// First try to load the module
66+
const foundBootModule = findBootModule(applicationBasePath, bootModule);
67+
if (foundBootModule === null) {
68+
return null; // Must be legacy mode
69+
}
70+
71+
// Now try to pick out the function they want us to invoke
72+
let renderToStringFunc: RenderToStringFunc;
73+
if (bootModule.exportName) {
74+
// Explicitly-named export
75+
renderToStringFunc = foundBootModule[bootModule.exportName];
76+
} else if (typeof foundBootModule !== 'function') {
77+
// TypeScript-style default export
78+
renderToStringFunc = foundBootModule.default;
79+
} else {
80+
// Native default export
81+
renderToStringFunc = foundBootModule;
82+
}
83+
84+
// Validate the result
85+
if (typeof renderToStringFunc !== 'function') {
86+
if (bootModule.exportName) {
87+
throw new Error(`The module at ${ bootModule.moduleName } has no function export named ${ bootModule.exportName }.`);
88+
} else {
89+
throw new Error(`The module at ${ bootModule.moduleName } does not export a default function, and you have not specified which export to invoke.`);
90+
}
1791
}
1892

19-
return aspNetPrerendering.renderToString.apply(this, arguments);
93+
return renderToStringFunc;
2094
}

src/Microsoft.AspNetCore.SpaServices/TypeScript/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"target": "es3",
44
"module": "commonjs",
55
"moduleResolution": "node",
6-
"types": ["node"]
6+
"types": ["node"],
7+
"lib": ["es2015"]
78
},
89
"exclude": [
910
"node_modules"
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/typings/
22
/node_modules/
3-
/*.js
4-
/*.d.ts
3+
/**/*.js
4+
5+
/**/.d.ts
6+
!/src/**/*.d.ts

src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aspnet-prerendering",
3-
"version": "1.0.7",
3+
"version": "2.0.0",
44
"description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
55
"main": "index.js",
66
"scripts": {
@@ -17,8 +17,7 @@
1717
"url": "https://github.com/aspnet/JavaScriptServices.git"
1818
},
1919
"dependencies": {
20-
"domain-task": "^2.0.1",
21-
"es6-promise": "^3.1.2"
20+
"domain-task": "^2.0.1"
2221
},
2322
"devDependencies": {
2423
"@types/node": "^6.0.42",

0 commit comments

Comments
 (0)