diff --git a/README.md b/README.md index b5dc35e..bab6eb4 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Additionally, it can contain the following **optional** properties, - `staticMarkup`: \ - a boolean that indicates if render components without React data attributes and client data. (_Default: `false`_). This is useful if you want to render simple static page, as stripping away the extra React attributes and client data can save lots of bytes. - `scriptType`: \ - a string that can be used as the type for the script (if it is included, which is only if staticMarkup is false). (_Default: `application/json`_). +- `styledComponents`: \ - a boolean that indicates if `styled-components` should collect styles on the server and add style tags to the head. (_Default: `false`_). Set this to `true` if you are using `styled-components` in your project. ###### Rendering views on server side ```js @@ -231,7 +232,7 @@ var engine = require('react-engine').server.create({ * When Express's `view cache` app property is false (mostly in non-production environments), views are automatically reloaded before render. So there is no need to restart the server for seeing the changes. * You can use `js` as the engine if you decide not to write your react views in `jsx`. * [Blog on react-engine](https://www.paypal-engineering.com/2015/04/27/isomorphic-react-apps-with-react-engine/) -* You can add [nonce](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) in `_locals`, which will be added in `script` tag that gets injected into the server rendered pages, like `res.locals.nonce = 'nonce value'` +* You can add [nonce](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) in `_locals`, which will be added in `script` tag that gets injected into the server rendered pages, like `res.locals.nonce = 'nonce value'` ### License diff --git a/lib/config.json b/lib/config.json index 46c8575..761a35d 100644 --- a/lib/config.json +++ b/lib/config.json @@ -4,6 +4,7 @@ "client": { "markupId": "react-engine-props" }, + "styledComponents": false, "staticMarkup": false, "scriptType": "application/json" } diff --git a/lib/server.js b/lib/server.js index 33a19f1..6f52961 100644 --- a/lib/server.js +++ b/lib/server.js @@ -68,6 +68,7 @@ exports.create = function create(createOptions) { createOptions.docType = isString(createOptions.docType) ? createOptions.docType : Config.docType; createOptions.renderOptionsKeysToFilter = createOptions.renderOptionsKeysToFilter || []; createOptions.staticMarkup = createOptions.staticMarkup !== undefined ? createOptions.staticMarkup : Config.staticMarkup; + createOptions.styledComponents = createOptions.styledComponents !== undefined ? createOptions.styledComponents : Config.styledComponents; assert(Array.isArray(createOptions.renderOptionsKeysToFilter), '`renderOptionsKeysToFilter` - should be an array'); @@ -109,53 +110,66 @@ exports.create = function create(createOptions) { if (createOptions.staticMarkup) { // render the component to static markup html += ReactDOMServer.renderToStaticMarkup(component); - } else { - // render the redux wrapped component - if (createOptions.reduxStoreInitiator) { - // add redux provider - var Provider = require('react-redux').Provider; - var initStore; - try { - initStore = require(createOptions.reduxStoreInitiator); - if (initStore.default) { - initStore = initStore.default; - } - var store = initStore(data); - var wrappedComponent = React.createElement(Provider, { store: store }, component); - // render the component - html += ReactDOMServer.renderToString(wrappedComponent); - } catch (err) { - return done(err); + return html; + } + + // render the redux wrapped component + if (createOptions.reduxStoreInitiator) { + // add redux provider + var Provider = require('react-redux').Provider; + var initStore; + try { + initStore = require(createOptions.reduxStoreInitiator); + if (initStore.default) { + initStore = initStore.default; } - } else { - // render the component - html += ReactDOMServer.renderToString(component); + var store = initStore(data); + component = React.createElement(Provider, { store: store }, component); + } catch (err) { + return done(err); } + } - // the `script` tag that gets injected into the server rendered pages. - // https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values - var openScriptTag = `'; - - - if (createOptions.docType === '') { - // if the `docType` is empty, the user did not want to add a docType to the rendered component, - // which means they might not be rendering a full page with `html` and `body` tags - // so attach the script tag to just the end of the generated html string - html += script; - } - else { - var htmlTag = createOptions.scriptLocation === 'head' ? '' : ''; - html = html.replace(htmlTag, script + htmlTag); + // render the component and get styled-components styles + if (createOptions.styledComponents) { + var ServerStyleSheet = require('styled-components').ServerStyleSheet; + var sheet = new ServerStyleSheet(); + try { + html += ReactDOMServer.renderToString(sheet.collectStyles(component)); + var styleTags = sheet.getStyleTags(); + // add the styles to the end of the head + var htmlTag = ''; + html = html.replace(htmlTag, styleTags + htmlTag); + } catch (err) { + return done(err); } + } else { + html += ReactDOMServer.renderToString(component); + } + + // the `script` tag that gets injected into the server rendered pages. + // https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values + var openScriptTag = `'; + + + if (createOptions.docType === '') { + // if the `docType` is empty, the user did not want to add a docType to the rendered component, + // which means they might not be rendering a full page with `html` and `body` tags + // so attach the script tag to just the end of the generated html string + html += script; + html += styleTags; + } else { + var htmlTag = createOptions.scriptLocation === 'head' ? '' : ''; + html = html.replace(htmlTag, script + htmlTag); } return html; diff --git a/package.json b/package.json index 4ed28e1..9be74b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-engine", - "version": "4.5.1", + "name": "@laurenskling/react-engine", + "version": "4.6.1", "description": "a composite render engine for express apps to render both plain react views and react-router views", "main": "index.js", "scripts": { @@ -13,10 +13,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/paypal/react-engine" + "url": "https://github.com/laurenskling/react-engine" }, "bugs": { - "url": "https://github.com/paypal/react-engine/issues" + "url": "https://github.com/laurenskling/react-engine/issues" }, "publishConfig": { "registry": "https://registry.npmjs.org" @@ -70,7 +70,8 @@ "Weng Zhi Ping", "Vincent Orr ", "skarflacka", - "Mark " + "Mark ", + "Laurens Kling " ], "license": "Apache-2.0" }