Created
March 3, 2017 21:40
-
-
Save mschipperheyn/c17280278218074a53147f54259af66a to your computer and use it in GitHub Desktop.
SSR Styled Components in React Universally
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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