All files / ima/page/renderer ServerPageRenderer.js

97.44% Statements 38/39
72.73% Branches 8/11
88.89% Functions 8/9
97.44% Lines 38/39
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219        3x   3x 3x   3x 3x 3x 3x 3x 3x   3x 3x                                                           15x               15x                   15x                 3x 1x     2x     2x                 1x                                                                                                             1x   1x 2x   2x 1x   1x       1x                           6x 5x 5x   5x           6x                       5x         5x   5x 5x     5x                 5x       3x  
import ns from '../../namespace';
import AbstractPageRenderer from './AbstractPageRenderer';
import GenericError from '../../error/GenericError';
 
ns.namespace('ima.page.renderer');
 
let imaLoader = '';
let imaRunner = '';
 
Eif (typeof window === 'undefined' || window === null) {
  let nodeFs = 'fs';
  let nodePath = 'path';
  let fs = require(nodeFs);
  let path = require(nodePath);
  let folder = path.dirname(require.resolve('../../'));
 
  imaLoader = fs.readFileSync(`${folder}/polyfill/imaLoader.js`, 'utf8');
  imaRunner = fs.readFileSync(`${folder}/polyfill/imaRunner.js`, 'utf8');
}
 
/**
 * Server-side page renderer. The renderer renders the page into the HTML
 * markup and sends it to the client.
 *
 * @class ServerPageRenderer
 * @extends AbstractPageRenderer
 * @implements PageRenderer
 * @namespace ima.page.renderer
 * @module ima
 * @submodule ima.page
 */
export default class ServerPageRenderer extends AbstractPageRenderer {
  /**
	 * Initializes the server-side page renderer.
	 *
	 * @param {PageRendererFactory} factory Factory for receive $Utils to view.
	 * @param {vendor.$Helper} Helper The IMA.js helper methods.
	 * @param {vendor.ReactDOMServer} ReactDOMServer React framework instance
	 *        to use to render the page on the server side.
	 * @param {Object<string, *>} settings Application setting for the current
	 *        application environment.
	 * @param {Response} response Utility for sending the page markup to the
	 *        client as a response to the current HTTP request.
	 * @param {Cache} cache Resource cache caching the results of HTTP requests
	 *        made by services used by the rendered page.
	 */
  constructor(factory, Helper, ReactDOMServer, settings, response, cache) {
    super(factory, Helper, ReactDOMServer, settings);
 
    /**
		 * Utility for sending the page markup to the client as a response to
		 * the current HTTP request.
		 *
		 * @type {Response}
		 */
    this._response = response;
 
    /**
		 * The resource cache, caching the results of all HTTP requests made by
		 * the services using by the rendered page. The state of the cache will
		 * then be serialized and sent to the client to re-initialize the page
		 * at the client side.
		 *
		 * @type {Cache}
		 */
    this._cache = cache;
  }
 
  /**
	 * @inheritdoc
	 * @abstract
	 * @method mount
	 */
  mount(controller, view, pageResources, routeOptions) {
    if (this._response.isResponseSent()) {
      return Promise.resolve(this._response.getResponseParams());
    }
 
    return this._Helper
      .allPromiseHash(pageResources)
      .then(pageState =>
        this._renderPage(controller, view, pageState, routeOptions)
      );
  }
 
  /**
	 * @inheritdoc
	 * @method update
	 */
  update(controller, resourcesUpdate) {
    return Promise.reject(
      new GenericError('The update() is denied on server side.')
    );
  }
 
  /**
	 * @inheritdoc
	 * @method unmount
	 */
  unmount() {
    // nothing to do
  }
 
  /**
	 * The javascript code will include a settings the "revival" data for the
	 * application at the client-side.
	 *
	 * @return {string} The javascript code to include into the
	 *         rendered page.
	 */
  _getRevivalSettings() {
    return `
			(function(root) {
				root.$Debug = ${this._settings.$Debug};
				root.$IMA = root.$IMA || {};
				$IMA.Cache = ${this._cache.serialize()};
				$IMA.$Language = "${this._settings.$Language}";
				$IMA.$Env = "${this._settings.$Env}";
				$IMA.$Debug = ${this._settings.$Debug};
				$IMA.$Version = "${this._settings.$Version}";
				$IMA.$App = ${JSON.stringify(this._settings.$App)};
				$IMA.$Protocol = "${this._settings.$Protocol}";
				$IMA.$Host = "${this._settings.$Host}";
				$IMA.$Path = "${this._settings.$Path}";
				$IMA.$Root = "${this._settings.$Root}";
				$IMA.$LanguagePartPath = "${this._settings.$LanguagePartPath}";
			})(typeof window !== 'undefined' && window !== null ? window : global);
			${imaRunner}
			${imaLoader}
			`;
  }
 
  /**
	 * Creates a copy of the provided data map object that has the values of
	 * its fields wrapped into Promises.
	 *
	 * The the values that are already Promises will referenced directly
	 * without wrapping then into another Promise.
	 *
	 * @param {Object<string, *>=} [dataMap={}] A map of data that should have
	 *        its values wrapped into Promises.
	 * @return {Object<string, Promise>} A copy of the provided data map that
	 *         has all its values wrapped into promises.
	 */
  _wrapEachKeyToPromise(dataMap = {}) {
    let copy = {};
 
    for (let field of Object.keys(dataMap)) {
      let value = dataMap[field];
 
      if (value instanceof Promise) {
        copy[field] = value;
      } else {
        copy[field] = Promise.resolve(value);
      }
    }
 
    return copy;
  }
 
  /**
	 * Render page after all promises from loaded resources is resolved.
	 *
	 * @param {AbstractController} controller
	 * @param {React.Component} view
	 * @param {Object<string, *>} pageState
	 * @param {Object<string, *>} routeOptions
	 * @return {{content: string, status: number,
	 *         pageState: Object<string, *>}}
	 */
  _renderPage(controller, view, pageState, routeOptions) {
    if (!this._response.isResponseSent()) {
      controller.setState(pageState);
      controller.setMetaParams(pageState);
 
      this._response
        .status(controller.getHttpStatus())
        .setPageState(pageState)
        .send(this._renderPageContentToString(controller, view, routeOptions));
    }
 
    return this._response.getResponseParams();
  }
 
  /**
	 * Render page content to a string containing HTML markup.
	 *
	 * @param {AbstractController} controller
	 * @param {function(new: React.Component)} view
	 * @param {Object<string, *>} routeOptions
	 * @return {string}
	 */
  _renderPageContentToString(controller, view, routeOptions) {
    let reactElementView = this._getWrappedPageView(
      controller,
      view,
      routeOptions
    );
    let pageMarkup = this._ReactDOM.renderToString(reactElementView);
 
    let documentView = this._getDocumentView(routeOptions);
    let documentViewFactory = this._factory.createReactElementFactory(
      documentView
    );
    let appMarkup = this._ReactDOM.renderToStaticMarkup(
      documentViewFactory({
        page: pageMarkup,
        revivalSettings: this._getRevivalSettings(),
        metaManager: controller.getMetaManager(),
        $Utils: this._factory.getUtils()
      })
    );
 
    return '<!doctype html>\n' + appMarkup;
  }
}
 
ns.ima.page.renderer.ServerPageRenderer = ServerPageRenderer;