Repository URL to install this package:
|
Version:
0.0.7 ▾
|
/**
* @module responsible for generating the HTML page response
* @see the react application middleware
*/
/* eslint-disable react/no-danger */
/* eslint-disable react/no-array-index-key */
import React from 'react';
import { oneRouter } from '@skava/router';
import { isArray, isObj } from 'exotic';
import serialize from 'serialize-javascript';
import { getClientBundleEntryAssets } from '../../aliased';
import { logger } from '../../log';
import { HTML } from '../Document';
import { findAssetsForRoute } from './findAssetsForRoute';
import { oneStorage } from '@skava/persistence';
import { fromClientPathToCompatPath, } from './compat';
const clientEntryAssets = getClientBundleEntryAssets();
// --------------- config ---------------
const HAS_STYLES = false;
// ----------------- components ------------------
/**
* make sure we don't give any `map` files
* @todo we could filter this elsewhere
* @param path @example /eh.js
*/
function shouldRemoveFromServerJavaScriptList(path) {
return path.includes('.map') === false;
}
/**
* @throws Invariant Violation: React.Children.only expected to receive a single React element child.
* return React.Children.only(props.children)
*/
function KeyedComponent(props) {
return props.children;
}
const toKeyed = (x, index) => (React.createElement(KeyedComponent, { key: index }, x));
function stylesheetTag(stylesheetFilePath) {
return React.createElement("link", { href: stylesheetFilePath, media: "screen, projection", rel: "stylesheet" });
}
function hackFilePath(jsFilePath) {
const pathWithSlash = jsFilePath.includes('/') ? jsFilePath : '/' + jsFilePath;
const hackedPath = pathWithSlash.replace('undefined', '');
const absPath = hackedPath.startsWith('/') ? oneRouter.origin + hackedPath : hackedPath;
const hackedAbsPath = absPath.replace('undefined/', '');
const finalPath = hackedAbsPath.startsWith('/') || hackedAbsPath.includes('http')
? hackedAbsPath
: '/' + hackedAbsPath;
return finalPath;
}
function scriptTag(jsFilePath) {
const finalPath = hackFilePath(jsFilePath);
/**
* @todo get import meta?
*/
logger.info('[ssr] adding_script_tag ', finalPath);
return React.createElement("script", { type: "text/javascript", src: finalPath });
}
/**
* @example <script>{stringProtectedByNonce}</script>
* @tutorial https://stackoverflow.com/questions/42922784/what-s-the-purpose-of-the-html-nonce-attribute-for-script-and-style-elements
*/
const inlineScript = (props) => (React.createElement("script", { nonce: props.nonce, type: "text/javascript", dangerouslySetInnerHTML: { __html: props.children } }));
/**
* this renders the elements wrapped with <HelmetReact> on our views/pages
*/
function createHeaderElements(props) {
const { helmet, styledTags } = props;
const noHelmet = !helmet;
let styleElements = [];
if (HAS_STYLES && noHelmet) {
const styleTag = stylesheetTag(clientEntryAssets.css);
styleElements.push(styleTag);
}
else if (helmet) {
styleElements = helmet.style.toComponent() || [];
}
if (styledTags) {
styleElements.push(styledTags);
}
return noHelmet
? []
: [
...helmet.title.toComponent(),
...helmet.base.toComponent(),
...helmet.meta.toComponent(),
...helmet.link.toComponent(),
...helmet.style.toComponent(),
...styleElements,
];
}
// ----------------- core ------------------
/**
* @todo see view-container and the byte minification there
*
* @example if (storeState)
* inlineScriptString += `window.__APP_STATE__`
* inlineScriptString += `=` + serialize(storeState);
*
* @return {String} JSON of the serialized state
*/
function fromGlobalStateToScriptString(inlineScriptState) {
let inlineScriptString = ``;
const GLOBAL_NAMES = Object.keys(inlineScriptState);
for (let index = 0; index < GLOBAL_NAMES.length; index++) {
const GLOBAL_NAME = GLOBAL_NAMES[index];
const GLOBAL_VALUE = inlineScriptState[GLOBAL_NAME];
inlineScriptString += `window.${GLOBAL_NAME} = ${serialize(GLOBAL_VALUE)};\n`;
}
return inlineScriptString;
}
/**
* for rehydrating state from a <script> added to the end of <body>
* full of JSON dumps
*/
function getRehydratableScript(props) {
const { nonce } = props;
const { routerState, asyncState, storeState, apolloState } = props;
const inlineScriptState = {
// @todo use this with new ssr data rendering...
// __APP_STATE__: storeState,
__APOLLO_STATE__: apolloState,
__ROUTER_STATE__: routerState,
};
const lastSaveTimeGlobal = `
__LAST_PUBLISH_TIMESTAMP__: ${oneStorage.get('__LAST_PUBLISH_TIMESTAMP__')}
`;
const children = fromGlobalStateToScriptString(inlineScriptState) + lastSaveTimeGlobal;
return inlineScript({ children, nonce });
}
function sanitizePath(path) {
return path.replace('/undefined', '').replace('/undefined', '');
}
function getBodyElements(props) {
// @@todo @@packages @@compat
const { helmet, reactAppString, isCompat } = props;
const bodyElements = [];
const rehydratable = getRehydratableScript(props);
bodyElements.push(rehydratable);
let jsList = clientEntryAssets.jsList;
if (isObj(clientEntryAssets) && isArray(clientEntryAssets.jsList)) {
logger.debug('[react-server] creating bodyElements for route using assets');
logger.debug('[react-server] @todo @james move this to library');
jsList = clientEntryAssets.jsList.filter(Boolean).map(sanitizePath);
/**
* we load the md5 manifest file from `compat`,
* by remapping the file names
*
* @example
* client/Basic.Page-12345.js
* compat/Basic.Page-67890.js
*/
if (isCompat) {
logger.warn('[compat] going to add COMPAT urls');
// dist/dist/bundled/compat/manifest.json
jsList = clientEntryAssets.jsList.map(fromClientPathToCompatPath);
}
else {
logger.warn('[compat] NOT going to add COMPAT urls');
}
findAssetsForRoute({ jsList })
.filter(shouldRemoveFromServerJavaScriptList)
// is ssr is disabled, only load index
.filter(x => (reactAppString === '' ? x.includes('index') : x))
.map(scriptTag)
// tslint:disable no-for-each-push
.forEach(asScriptTag => {
bodyElements.push(asScriptTag);
});
}
else {
logger.warn('[react-server] could not create bodyElements for assets');
}
// @todo @@perf use exotic
// every page with a <Helmet> has this
// should just require it to simplify
if (helmet) {
// currently this never happens
helmet.script.toComponent().forEach(scriptComponent => bodyElements.push(scriptComponent));
}
return bodyElements;
}
class ServerHTML extends React.PureComponent {
render() {
const { helmet, reactAppString } = this.props;
const headerElements = createHeaderElements(this.props);
const attributes = helmet ? helmet.htmlAttributes.toComponent() : undefined;
const bodyElements = getBodyElements(this.props);
/* eslint-disable react/jsx-pascal-case */
return (React.createElement(HTML, { htmlAttributes: attributes, headerElements: headerElements.map(toKeyed), bodyElements: bodyElements.map(toKeyed), appBodyString: reactAppString }));
}
}
export { ServerHTML };
export default ServerHTML;
//# sourceMappingURL=ServerHTML.js.map