|
1 | | -// ----------------------------------------------------------------------------------------- |
2 | | -// Loading via Webpack |
3 | | -// This is optional. You don't have to use Webpack. But if you are doing, it's extremely convenient |
4 | | -// to be able to load your boot module via Webpack compilation, so you can use whatever source language |
5 | | -// you like (e.g., TypeScript), and so that loader plugins (e.g., require('./mystyles.less')) work in |
6 | | -// exactly the same way on the server as you do on the client. |
7 | | -// If you don't use Webpack, then it's up to you to define a plain-JS boot module that in turn loads |
8 | | -// whatever other files you need (e.g., using some other compiler/bundler API, or maybe just having |
9 | | -// already precompiled to plain JS files on disk). |
10 | | -function loadViaWebpackNoCache(webpackConfigPath, modulePath) { |
11 | | - var ExternalsPlugin = require('webpack-externals-plugin'); |
12 | | - var requireFromString = require('require-from-string'); |
13 | | - var MemoryFS = require('memory-fs'); |
14 | | - var webpack = require('webpack'); |
15 | | - |
16 | | - return new Promise(function(resolve, reject) { |
17 | | - // Load the Webpack config and make alterations needed for loading the output into Node |
18 | | - var webpackConfig = require(webpackConfigPath); |
19 | | - webpackConfig.entry = modulePath; |
20 | | - webpackConfig.target = 'node'; |
21 | | - webpackConfig.output = { path: '/', filename: 'webpack-output.js', libraryTarget: 'commonjs' }; |
22 | | - |
23 | | - // In Node, we want anything under /node_modules/ to be loaded natively and not bundled into the output |
24 | | - // (partly because it's faster, but also because otherwise there'd be different instances of modules |
25 | | - // depending on how they were loaded, which could lead to errors) |
26 | | - webpackConfig.plugins = webpackConfig.plugins || []; |
27 | | - webpackConfig.plugins.push(new ExternalsPlugin({ type: 'commonjs', include: /node_modules/ })); |
28 | | - |
29 | | - // The CommonsChunkPlugin is not compatible with a CommonJS environment like Node, nor is it needed in that case |
30 | | - webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) { |
31 | | - return !(plugin instanceof webpack.optimize.CommonsChunkPlugin); |
32 | | - }); |
33 | | - |
34 | | - // The typical use case for DllReferencePlugin is for referencing vendor modules. In a Node |
35 | | - // environment, it doesn't make sense to load them from a DLL bundle, nor would that even |
36 | | - // work, because then you'd get different module instances depending on whether a module |
37 | | - // was referenced via a normal CommonJS 'require' or via Webpack. So just remove any |
38 | | - // DllReferencePlugin from the config. |
39 | | - // If someone wanted to load their own DLL modules (not an NPM module) via DllReferencePlugin, |
40 | | - // that scenario is not supported today. We would have to add some extra option to the |
41 | | - // asp-prerender tag helper to let you specify a list of DLL bundles that should be evaluated |
42 | | - // in this context. But even then you'd need special DLL builds for the Node environment so that |
43 | | - // external dependencies were fetched via CommonJS requires, so it's unclear how that could work. |
44 | | - // The ultimate escape hatch here is just prebuilding your code as part of the application build |
45 | | - // and *not* using asp-prerender-webpack-config at all, then you can do anything you want. |
46 | | - webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) { |
47 | | - return !(plugin instanceof webpack.DllReferencePlugin); |
48 | | - }); |
49 | | - |
50 | | - // Create a compiler instance that stores its output in memory, then load its output |
51 | | - var compiler = webpack(webpackConfig); |
52 | | - compiler.outputFileSystem = new MemoryFS(); |
53 | | - compiler.run(function(err, stats) { |
54 | | - if (err) { |
55 | | - reject(err); |
56 | | - } else { |
57 | | - var fileContent = compiler.outputFileSystem.readFileSync('/webpack-output.js', 'utf8'); |
58 | | - var moduleInstance = requireFromString(fileContent); |
59 | | - resolve(moduleInstance); |
60 | | - } |
61 | | - }); |
62 | | - }); |
63 | | -} |
64 | | - |
65 | | -// Ensure we only go through the compile process once per [config, module] pair |
66 | | -var loadViaWebpackPromisesCache = {}; |
67 | | -function loadViaWebpack(webpackConfigPath, modulePath, callback) { |
68 | | - var cacheKey = JSON.stringify(webpackConfigPath) + JSON.stringify(modulePath); |
69 | | - if (!(cacheKey in loadViaWebpackPromisesCache)) { |
70 | | - loadViaWebpackPromisesCache[cacheKey] = loadViaWebpackNoCache(webpackConfigPath, modulePath); |
| 1 | +// Pass through the invocation to the 'aspnet-prerendering' package, verifying that it can be loaded |
| 2 | +module.exports.renderToString = function (callback) { |
| 3 | + var aspNetPrerendering; |
| 4 | + try { |
| 5 | + aspNetPrerendering = require('aspnet-prerendering'); |
| 6 | + } catch (ex) { |
| 7 | + callback('To use prerendering, you must install the \'aspnet-prerendering\' NPM package.'); |
| 8 | + return; |
71 | 9 | } |
72 | | - loadViaWebpackPromisesCache[cacheKey].then(function(result) { |
73 | | - callback(null, result); |
74 | | - }, function(error) { |
75 | | - callback(error); |
76 | | - }) |
77 | | -} |
78 | | - |
79 | | -// ----------------------------------------------------------------------------------------- |
80 | | -// Rendering |
81 | | - |
82 | | -var url = require('url'); |
83 | | -var path = require('path'); |
84 | | -var domain = require('domain'); |
85 | | -var domainTask = require('domain-task'); |
86 | | -var baseUrl = require('domain-task/fetch').baseUrl; |
87 | | - |
88 | | -function findBootModule(applicationBasePath, bootModule, callback) { |
89 | | - var bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName); |
90 | | - if (bootModule.webpackConfig) { |
91 | | - var webpackConfigFullPath = path.resolve(applicationBasePath, bootModule.webpackConfig); |
92 | | - loadViaWebpack(webpackConfigFullPath, bootModuleNameFullPath, callback); |
93 | | - } else { |
94 | | - callback(null, require(bootModuleNameFullPath)); |
95 | | - } |
96 | | -} |
97 | | - |
98 | | -function findBootFunc(applicationBasePath, bootModule, callback) { |
99 | | - // First try to load the module (possibly via Webpack) |
100 | | - findBootModule(applicationBasePath, bootModule, function(findBootModuleError, foundBootModule) { |
101 | | - if (findBootModuleError) { |
102 | | - callback(findBootModuleError); |
103 | | - return; |
104 | | - } |
105 | | - |
106 | | - // Now try to pick out the function they want us to invoke |
107 | | - var bootFunc; |
108 | | - if (bootModule.exportName) { |
109 | | - // Explicitly-named export |
110 | | - bootFunc = foundBootModule[bootModule.exportName]; |
111 | | - } else if (typeof foundBootModule !== 'function') { |
112 | | - // TypeScript-style default export |
113 | | - bootFunc = foundBootModule.default; |
114 | | - } else { |
115 | | - // Native default export |
116 | | - bootFunc = foundBootModule; |
117 | | - } |
118 | | - |
119 | | - // Validate the result |
120 | | - if (typeof bootFunc !== 'function') { |
121 | | - if (bootModule.exportName) { |
122 | | - callback(new Error('The module at ' + bootModule.moduleName + ' has no function export named ' + bootModule.exportName + '.')); |
123 | | - } else { |
124 | | - callback(new Error('The module at ' + bootModule.moduleName + ' does not export a default function, and you have not specified which export to invoke.')); |
125 | | - } |
126 | | - } else { |
127 | | - callback(null, bootFunc); |
128 | | - } |
129 | | - }); |
130 | | -} |
131 | | - |
132 | | -function renderToString(callback, applicationBasePath, bootModule, absoluteRequestUrl, requestPathAndQuery) { |
133 | | - findBootFunc(applicationBasePath, bootModule, function (findBootFuncError, bootFunc) { |
134 | | - if (findBootFuncError) { |
135 | | - callback(findBootFuncError); |
136 | | - return; |
137 | | - } |
138 | | - |
139 | | - // Prepare a promise that will represent the completion of all domain tasks in this execution context. |
140 | | - // The boot code will wait for this before performing its final render. |
141 | | - var domainTaskCompletionPromiseResolve; |
142 | | - var domainTaskCompletionPromise = new Promise(function (resolve, reject) { |
143 | | - domainTaskCompletionPromiseResolve = resolve; |
144 | | - }); |
145 | | - var params = { |
146 | | - location: url.parse(requestPathAndQuery), |
147 | | - url: requestPathAndQuery, |
148 | | - absoluteUrl: absoluteRequestUrl, |
149 | | - domainTasks: domainTaskCompletionPromise |
150 | | - }; |
151 | | - |
152 | | - // Open a new domain that can track all the async tasks involved in the app's execution |
153 | | - domainTask.run(function() { |
154 | | - // Workaround for Node bug where native Promise continuations lose their domain context |
155 | | - // (https://github.com/nodejs/node-v0.x-archive/issues/8648) |
156 | | - bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain.active); |
157 | | - |
158 | | - // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context |
159 | | - baseUrl(absoluteRequestUrl); |
160 | | - |
161 | | - // Actually perform the rendering |
162 | | - bootFunc(params).then(function(successResult) { |
163 | | - callback(null, { html: successResult.html, globals: successResult.globals }); |
164 | | - }, function(error) { |
165 | | - callback(error, null); |
166 | | - }); |
167 | | - }, function allDomainTasksCompleted(error) { |
168 | | - // There are no more ongoing domain tasks (typically data access operations), so we can resolve |
169 | | - // the domain tasks promise which notifies the boot code that it can do its final render. |
170 | | - if (error) { |
171 | | - callback(error, null); |
172 | | - } else { |
173 | | - domainTaskCompletionPromiseResolve(); |
174 | | - } |
175 | | - }); |
176 | | - }); |
177 | | -} |
178 | | - |
179 | | -function bindPromiseContinuationsToDomain(promise, domainInstance) { |
180 | | - var originalThen = promise.then; |
181 | | - promise.then = function then(resolve, reject) { |
182 | | - if (typeof resolve === 'function') { resolve = domainInstance.bind(resolve); } |
183 | | - if (typeof reject === 'function') { reject = domainInstance.bind(reject); } |
184 | | - return originalThen.call(this, resolve, reject); |
185 | | - }; |
186 | | -} |
187 | | - |
188 | | -module.exports.renderToString = renderToString; |
| 10 | + |
| 11 | + return aspNetPrerendering.renderToString.apply(this, arguments); |
| 12 | +}; |
0 commit comments