From ec9544c6446195013e68ff2edb483ea6bfac6044 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 10 Mar 2016 23:22:29 +0000 Subject: [PATCH] Add aspnet-prerendering NPM package --- .../npm/aspnet-prerendering/.gitignore | 4 + .../npm/aspnet-prerendering/.npmignore | 3 + .../npm/aspnet-prerendering/LICENSE.txt | 12 ++ .../npm/aspnet-prerendering/README.md | 6 + .../npm/aspnet-prerendering/package.json | 16 ++ .../aspnet-prerendering/src/Prerendering.ts | 148 ++++++++++++++++++ .../npm/aspnet-prerendering/src/index.ts | 1 + .../npm/aspnet-prerendering/tsconfig.json | 12 ++ .../npm/aspnet-prerendering/tsd.json | 18 +++ 9 files changed, 220 insertions(+) create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.npmignore create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/LICENSE.txt create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/README.md create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/package.json create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/index.ts create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsconfig.json create mode 100644 src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsd.json diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore new file mode 100644 index 0000000..a1df9a5 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.gitignore @@ -0,0 +1,4 @@ +/typings/ +/node_modules/ +/*.js +/*.d.ts diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.npmignore b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.npmignore new file mode 100644 index 0000000..858cdc4 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/.npmignore @@ -0,0 +1,3 @@ +!/*.js +!/*.d.ts +/typings/ diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/LICENSE.txt b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/LICENSE.txt new file mode 100644 index 0000000..0bdc196 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/LICENSE.txt @@ -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. diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/README.md b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/README.md new file mode 100644 index 0000000..e459de7 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/README.md @@ -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. diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/package.json new file mode 100644 index 0000000..1af75a9 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/package.json @@ -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" + } +} diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts new file mode 100644 index 0000000..bcb82bf --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -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; +} + +export interface BootFuncParams { + location: url.Url; + url: string; + absoluteUrl: string; + domainTasks: Promise; +} + +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(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(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, 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); + }; +} diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/index.ts b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/index.ts new file mode 100644 index 0000000..aaff576 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/src/index.ts @@ -0,0 +1 @@ +export * from './Prerendering'; diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsconfig.json b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsconfig.json new file mode 100644 index 0000000..de676e9 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "commonjs", + "target": "es5", + "declaration": true, + "outDir": "." + }, + "exclude": [ + "node_modules" + ] +} diff --git a/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsd.json b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsd.json new file mode 100644 index 0000000..3281014 --- /dev/null +++ b/src/Microsoft.AspNet.SpaServices/npm/aspnet-prerendering/tsd.json @@ -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" + } + } +}