From 27d6476ec7a992093d08c0ac93bcb49ef6aaa39d Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:29:35 -0800 Subject: [PATCH 1/9] 'text/css' is not required (https://www.h3xed.com/web-development/is-type-text-css-needed-for-style-and-link-tags) --- src/render.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/render.js b/src/render.js index a4f9581f..cd26f092 100644 --- a/src/render.js +++ b/src/render.js @@ -45,8 +45,6 @@ function patch([added, removed]) { function makeStyleTag(str) { // based on implementation by glamor const tag = document.createElement('style') - - tag.type = 'text/css' tag.appendChild(document.createTextNode(str)) const head = document.head || document.getElementsByTagName('head')[0] From f996d5b506e3d2c1a11bee5f6ab9a0bb661814d8 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:30:09 -0800 Subject: [PATCH 2/9] add high-level server rendering APIs to avoid duplicates (#22) --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/server.js | 27 +++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/server.js diff --git a/README.md b/README.md index 93b78306..ee7d7d43 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,57 @@ export default () => ( ## Server-Side Rendering +### `styled-jsx/server` + +The main export flushes your styles to an array of `React.Element`: + +``` +import React from 'react' +import ReactDOM from 'react-dom/server' +import flush from 'styled-jsx/server' +import App from './app' +// … +export default (req, res) => { + const app = ReactDOM.renderToString() + const styles = flush() + const html = ReactDOM.renderToStaticMarkup( + { styles } + +
+ + ) + res.end('' + html) +} +``` + +We also expose `flushToHTML` to return generated HTML: + +```js +import React from 'react' +import ReactDOM from 'react-dom/server' +import { flushToHTML } from 'styled-jsx/server' +import App from './app' + +export default (req, res) => { + const app = ReactDOM.renderToString() + const styles = flushToString() + const html = ` + + ${styles} + +
${app}
+ + ` + res.end(html) +} +``` + +It's **paramount** that you use one of these two functions so that +the generated styles can be diffed when the client loads and +duplicate styles are avoided. + +### `styled-jsx/flush` + In the server rendering pipeline, you can obtain the entire CSS of all components by invoking `flush`: ```js diff --git a/src/server.js b/src/server.js new file mode 100644 index 00000000..b025f142 --- /dev/null +++ b/src/server.js @@ -0,0 +1,27 @@ +import React from 'react' +import flush from './flush' + +export default function flushToReact () { + const mem = flush() + const arr = [] + for (const id in mem) { + arr.push(React.createElement('style', { + id: `__jsx-style-${id}`, + // avoid warnings upon render with a key + key: `__jsx-style-${id}`, + dangerouslySetInnerHTML: { + __html: mem[id] + } + })) + } + return arr +} + +export function flushToHTML () { + const mem = flush() + let html = '' + for (const id in mem) { + html += `` + } + return html +} From 72ed27aabf83fc5555e5463da4315b72478dbe29 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:30:30 -0800 Subject: [PATCH 3/9] verify existing server-rendered DOM to avoid duplicate styles --- src/render.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/render.js b/src/render.js index cd26f092..e72a7f38 100644 --- a/src/render.js +++ b/src/render.js @@ -30,9 +30,16 @@ function diff(a, b) { return [added, removed] } +const fromServer = {} + function patch([added, removed]) { for (const [id, c] of added) { - tags[id] = makeStyleTag(c.props.css) + // avoid duplicates from server-rendered markup + if (undefined === fromServer[id]) { + fromServer[id] = document.getElementById(`__jsx-style-${id}`) + } + + tags[id] = fromServer[id] || makeStyleTag(c.props.css) } for (const [id] of removed) { From fe897aa81788d71b1da6555fcd711673a7d60e5c Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:31:24 -0800 Subject: [PATCH 4/9] expose `styled-jsx/server` --- gulpfile.babel.js | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 3c6faf02..29b49b84 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -13,7 +13,7 @@ gulp.task('transpile', () => { }) gulp.task('runtime-size', async () => { - const files = ['flush.js', 'memory.js', 'render.js', 'style.js'] + const files = ['flush.js', 'server.js', 'memory.js', 'render.js', 'style.js'] const result = await Promise.all(files .map(f => join(__dirname, 'src', f)) diff --git a/package.json b/package.json index 30b5dbcb..0d177448 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "files": [ "dist", "lib", + "server.js", "babel.js", "flush.js", "memory.js", From 3b650c49a9f75128d0f6869a5cff0827b6f38cfe Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:31:39 -0800 Subject: [PATCH 5/9] add server rendering tests --- package.json | 1 + test/index.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/package.json b/package.json index 0d177448..59253dc7 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "human-size": "^1.1.0", "mz": "^2.6.0", "react": "^15.4.1", + "react-dom": "^15.4.1", "xo": "^0.17.1" }, "peerDependencies": { diff --git a/test/index.js b/test/index.js index ba518fb3..b011b8b8 100644 --- a/test/index.js +++ b/test/index.js @@ -4,9 +4,13 @@ import path from 'path' // Packages import test from 'ava' import {transformFile} from 'babel-core' +import React from 'react' +import ReactDOM from 'react-dom/server' // Ours import plugin from '../src/babel' +import JSXStyle from '../src/style' +import flush, { flushToHTML } from '../src/server' import read from './_read' const transform = (file, opts = {}) => ( @@ -71,3 +75,41 @@ test('works with multiple jsx blocks', async t => { const out = await read('./fixtures/multiple-jsx.out.js') t.is(code, out.trim()) }) + +test('server rendering', t => { + function App () { + return React.createElement('div', null, + React.createElement(JSXStyle, { + css: 'p { color: red }', + styleId: 1 + }), + React.createElement(JSXStyle, { + css: 'div { color: blue }', + styleId: 2 + }) + ) + } + + // expected CSS + const expected = '' + + '' + + // render using react + ReactDOM.renderToString(React.createElement(App)) + const html = ReactDOM.renderToStaticMarkup( + React.createElement('head', null, flush()) + ) + t.is(html, `${expected}`) + + // assert that memory is empty + t.is(0, flush().length) + t.is('', flushToHTML()) + + // render to html again + ReactDOM.renderToString(React.createElement(App)) + t.is(expected, flushToHTML()) + + // assert that memory is empty + t.is(0, flush().length) + t.is('', flushToHTML()) +}) From a18a8b8461ec08f3215cb525b28d1ba79d42915b Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:31:46 -0800 Subject: [PATCH 6/9] yarn --- yarn.lock | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 646fe933..9d6d3287 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1900,7 +1900,7 @@ fast-levenshtein@~2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz#bd33145744519ab1c36c3ee9f31f08e9079b67f2" -fbjs@^0.8.4: +fbjs@^0.8.1, fbjs@^0.8.4: version "0.8.6" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.6.tgz#7eb67d6986b2d5007a9b6e92e0e7cb6f75cad290" dependencies: @@ -3736,6 +3736,14 @@ rc@^1.0.1, rc@^1.1.6, rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" +react-dom@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.4.1.tgz#d54c913261aaedb17adc20410d029dcc18a1344a" + dependencies: + fbjs "^0.8.1" + loose-envify "^1.1.0" + object-assign "^4.1.0" + react@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/react/-/react-15.4.1.tgz#498e918602677a3983cd0fd206dfe700389a0dd6" From 90543e07ef83dc3d34daafe703da68b260ec4281 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:34:11 -0800 Subject: [PATCH 7/9] avoid checking the DOM unnecessarily after removing a style --- src/render.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/render.js b/src/render.js index e72a7f38..c1ef1e03 100644 --- a/src/render.js +++ b/src/render.js @@ -46,6 +46,8 @@ function patch([added, removed]) { const t = tags[id] delete tags[id] t.parentNode.removeChild(t) + // avoid checking the DOM later on + fromServer[id] = null } } From d6e7af6e48b123bc76edd97516929f862b2d823f Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:35:23 -0800 Subject: [PATCH 8/9] README style fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee7d7d43..746fb312 100644 --- a/README.md +++ b/README.md @@ -150,12 +150,12 @@ export default () => ( The main export flushes your styles to an array of `React.Element`: -``` +```js import React from 'react' import ReactDOM from 'react-dom/server' import flush from 'styled-jsx/server' import App from './app' -// … + export default (req, res) => { const app = ReactDOM.renderToString() const styles = flush() From 34d55299128ac6b23e2e7ea5186af2b8612f1a7a Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Mon, 19 Dec 2016 09:46:50 -0800 Subject: [PATCH 9/9] fix linting --- src/server.js | 28 +++++++++++++++++----------- test/index.js | 8 ++++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/server.js b/src/server.js index b025f142..7a564158 100644 --- a/src/server.js +++ b/src/server.js @@ -1,27 +1,33 @@ import React from 'react' import flush from './flush' -export default function flushToReact () { +const {hasOwnProperty} = Object.prototype + +export default function flushToReact() { const mem = flush() const arr = [] for (const id in mem) { - arr.push(React.createElement('style', { - id: `__jsx-style-${id}`, - // avoid warnings upon render with a key - key: `__jsx-style-${id}`, - dangerouslySetInnerHTML: { - __html: mem[id] - } - })) + if (hasOwnProperty.call(mem, id)) { + arr.push(React.createElement('style', { + id: `__jsx-style-${id}`, + // avoid warnings upon render with a key + key: `__jsx-style-${id}`, + dangerouslySetInnerHTML: { + __html: mem[id] + } + })) + } } return arr } -export function flushToHTML () { +export function flushToHTML() { const mem = flush() let html = '' for (const id in mem) { - html += `` + if (hasOwnProperty.call(mem, id)) { + html += `` + } } return html } diff --git a/test/index.js b/test/index.js index b011b8b8..977b0f16 100644 --- a/test/index.js +++ b/test/index.js @@ -10,7 +10,7 @@ import ReactDOM from 'react-dom/server' // Ours import plugin from '../src/babel' import JSXStyle from '../src/style' -import flush, { flushToHTML } from '../src/server' +import flush, {flushToHTML} from '../src/server' import read from './_read' const transform = (file, opts = {}) => ( @@ -77,7 +77,7 @@ test('works with multiple jsx blocks', async t => { }) test('server rendering', t => { - function App () { + function App() { return React.createElement('div', null, React.createElement(JSXStyle, { css: 'p { color: red }', @@ -91,8 +91,8 @@ test('server rendering', t => { } // expected CSS - const expected = '' - + '' + const expected = '' + + '' // render using react ReactDOM.renderToString(React.createElement(App))