mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-23 10:08:57 +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