Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mschipperheyn/c17280278218074a53147f54259af66a to your computer and use it in GitHub Desktop.
Save mschipperheyn/c17280278218074a53147f54259af66a to your computer and use it in GitHub Desktop.
SSR Styled Components in React Universally
/**
* This module is responsible for generating the HTML page response for
* the react application middleware.
*/
/* eslint-disable react/no-danger */
/* eslint-disable react/no-array-index-key */
import React, { Children, PropTypes } from 'react';
import serialize from 'serialize-javascript';
import styleSheet from 'styled-components/lib/models/StyleSheet';
import config from '../../../config';
import onlyIf from '../../../shared/utils/logic/onlyIf';
import removeNil from '../../../shared/utils/arrays/removeNil';
import getClientBundleEntryAssets from './getClientBundleEntryAssets';
import ClientConfig from '../../../config/components/ClientConfig';
import HTML from '../../../shared/components/HTML';
// PRIVATES
function KeyedComponent({ children }) {
return Children.only(children);
}
// Resolve the assets (js/css) for the client bundle's entry chunk.
const clientEntryAssets = getClientBundleEntryAssets();
function stylesheetTag(stylesheetFilePath) {
return (
<link
href={stylesheetFilePath}
media="screen, projection"
rel="stylesheet"
type="text/css"
/>
);
}
function stylesheetBlock(css){
return (
<style
type="text/css"
dangerouslySetInnerHTML={{ __html: css}}
/>
);
}
function scriptTag(jsFilePath) {
return <script type="text/javascript" src={jsFilePath} />;
}
// COMPONENT
function ServerHTML(props) {
const {
asyncComponents,
helmet,
nonce,
reactAppString,
} = props;
// Creates an inline script definition that is protected by the nonce.
const inlineScript = body => (
<script
nonce={nonce}
type="text/javascript"
dangerouslySetInnerHTML={{ __html: body }}
/>
);
const styledComponentCSS = styleSheet.getCSS();
const headerElements = removeNil([
...onlyIf(helmet, () => helmet.meta.toComponent()),
...onlyIf(helmet, () => helmet.link.toComponent()),
onlyIf(styledComponentCSS, () => stylesheetBlock(styledComponentCSS)),
onlyIf(
clientEntryAssets && clientEntryAssets.css,
() => stylesheetTag(clientEntryAssets.css),
),
...onlyIf(helmet, () => helmet.style.toComponent()),
]);
const bodyElements = removeNil([
// Binds the client configuration object to the window object so
// that we can safely expose some configuration values to the
// client bundle that gets executed in the browser.
<ClientConfig nonce={nonce} />,
// Bind our async components state so the client knows which ones
// to initialise so that the checksum matches the server response.
// @see https://github.com/ctrlplusb/react-async-component
onlyIf(
asyncComponents,
() => inlineScript(
`window.${asyncComponents.STATE_IDENTIFIER}=${serialize(asyncComponents.state)};`,
),
),
// Enable the polyfill io script?
// This can't be configured within a react-helmet component as we
// may need the polyfill's before our client JS gets parsed.
onlyIf(
config('polyfillIO.enabled'),
() => scriptTag(config('polyfillIO.url')),
),
// When we are in development mode our development server will
// generate a vendor DLL in order to dramatically reduce our
// compilation times. Therefore we need to inject the path to the
// vendor dll bundle below.
onlyIf(
process.env.BUILD_FLAG_IS_DEV && config('bundles.client.devVendorDLL.enabled'),
() => scriptTag(
`${config('bundles.client.webPath')}${config('bundles.client.devVendorDLL.name')}.js?t=${Date.now()}`,
),
),
onlyIf(
clientEntryAssets && clientEntryAssets.js,
() => scriptTag(clientEntryAssets.js),
),
...onlyIf(
helmet,
() => helmet.script.toComponent(),
),
]);
return (
<HTML
title={config('htmlPage.defaultTitle')}
description={config('htmlPage.description')}
appBodyString={reactAppString}
headerElements={
headerElements.map((x, idx) => <KeyedComponent key={idx}>{x}</KeyedComponent>)
}
bodyElements={
bodyElements.map((x, idx) => <KeyedComponent key={idx}>{x}</KeyedComponent>)
}
/>
);
}
ServerHTML.propTypes = {
asyncComponents: PropTypes.shape({
state: PropTypes.object.isRequired,
STATE_IDENTIFIER: PropTypes.string.isRequired,
}),
// eslint-disable-next-line react/forbid-prop-types
helmet: PropTypes.object,
nonce: PropTypes.string,
reactAppString: PropTypes.string,
};
// EXPORT
export default ServerHTML;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment