mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Add aspnet-prerendering NPM package
This commit is contained in:
4
src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore
vendored
Normal file
4
src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/typings/
|
||||
/node_modules/
|
||||
/*.js
|
||||
/*.d.ts
|
||||
@@ -0,0 +1,3 @@
|
||||
!/*.js
|
||||
!/*.d.ts
|
||||
/typings/
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "aspnet-prerendering",
|
||||
"version": "1.0.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
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;
|
||||
url: string;
|
||||
absoluteUrl: string;
|
||||
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 params: BootFuncParams = {
|
||||
location: url.parse(requestPathAndQuery),
|
||||
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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Prerendering';
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"declaration": true,
|
||||
"outDir": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user