From 0c3f31233b96f1ef69ee61d8d8b09b2eced37a4a Mon Sep 17 00:00:00 2001 From: Daniel Juhl Date: Fri, 28 Apr 2017 09:17:18 +0200 Subject: [PATCH] lib: move server side rendering code to separate file to avoid loading react-dom on the client --- example/server-side/server.js | 2 +- lib/__tests__/basic.test.js | 50 +++++++++++++----- lib/index.js | 96 ++++++++++++++--------------------- lib/server.js | 16 ++++++ 4 files changed, 91 insertions(+), 73 deletions(-) create mode 100644 lib/server.js diff --git a/example/server-side/server.js b/example/server-side/server.js index 10dbdc5..1e9356c 100644 --- a/example/server-side/server.js +++ b/example/server-side/server.js @@ -3,7 +3,7 @@ import React from 'react'; import ReactDOMServer from 'react-dom/server'; import Root from '../components/root'; import Nested from '../components/page-one'; -import DocumentMeta from '../../lib'; +import DocumentMeta from '../../lib/server'; const server = express(); diff --git a/lib/__tests__/basic.test.js b/lib/__tests__/basic.test.js index 63fc892..4e0dee5 100644 --- a/lib/__tests__/basic.test.js +++ b/lib/__tests__/basic.test.js @@ -2,6 +2,7 @@ import assert from 'assert'; import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import DocumentMeta from '../'; +import DocumentMetaServer from '../server'; describe('DocumentMeta', () => { before(() => { @@ -77,14 +78,30 @@ describe('DocumentMeta', () => { }); }); - describe('.renderAsHTML()', () => { + describe('container element with children', () => { + it('renders the children', () => { + const title = 'foo'; + const markup = renderToStaticMarkup( + +
Child element
+
+ ); + + assert.strictEqual(markup, '
Child element
'); + assert.deepEqual(DocumentMeta.rewind(), { title }); + }); + }); +}); + +describe('DocumentMetaServer', () => { + describe('.renderToStaticMarkup()', () => { it('returns an empty string if no meta data has been mounted', () => { React.createElement(DocumentMeta, {title: 'a'}, React.createElement(DocumentMeta, {title: 'b'}, React.createElement(DocumentMeta, {title: 'c'}) ) ); - assert.strictEqual(DocumentMeta.renderAsHTML(), ''); + assert.strictEqual(DocumentMetaServer.renderToStaticMarkup(), ''); }); it('returns the latest document meta as HTML', () => { @@ -95,22 +112,29 @@ describe('DocumentMeta', () => { ) ) ); - assert.strictEqual(DocumentMeta.renderAsHTML(), 'c'); + assert.strictEqual(DocumentMetaServer.renderToStaticMarkup(), 'c'); }); }); - - describe('container element with children', () => { - it('renders the children', () => { - const title = 'foo'; - const markup = renderToStaticMarkup( - -
Child element
-
+ describe('.renderAsHTML()', () => { + it('returns an empty string if no meta data has been mounted', () => { + React.createElement(DocumentMeta, {title: 'a'}, + React.createElement(DocumentMeta, {title: 'b'}, + React.createElement(DocumentMeta, {title: 'c'}) + ) ); + assert.strictEqual(DocumentMetaServer.renderAsHTML(), ''); + }); - assert.strictEqual(markup, '
Child element
'); - assert.deepEqual(DocumentMeta.rewind(), { title }); + it('returns the latest document meta as HTML', () => { + renderToStaticMarkup( + React.createElement(DocumentMeta, {title: 'a'}, + React.createElement(DocumentMeta, {title: 'b'}, + React.createElement(DocumentMeta, {title: 'c'}) + ) + ) + ); + assert.strictEqual(DocumentMetaServer.renderAsHTML(), 'c'); }); }); }); diff --git a/lib/index.js b/lib/index.js index bf0f111..c5a0104 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,21 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { renderToStaticMarkup } from 'react-dom/server'; import withSideEffect from 'react-side-effect'; -import { - clone, - defaults, - forEach -} from './utils'; +import { clone, defaults, forEach } from './utils'; -import { - canUseDOM, - removeDocumentMeta, - insertDocumentMeta -} from './dom'; +import { canUseDOM, removeDocumentMeta, insertDocumentMeta } from './dom'; -function reducePropsTostate ( propsList ) { +function reducePropsTostate(propsList) { const props = {}; let extend = true; @@ -25,11 +16,11 @@ function reducePropsTostate ( propsList ) { const _props = clone(propsList[i]); - if ( _props.hasOwnProperty('description') ) { - defaults(_props, { meta: { name: { description: _props.description } } } ); + if (_props.hasOwnProperty('description')) { + defaults(_props, { meta: { name: { description: _props.description } } }); } - if ( _props.hasOwnProperty('canonical') ) { - defaults(_props, { link: { rel: { canonical: _props.canonical } } } ); + if (_props.hasOwnProperty('canonical')) { + defaults(_props, { link: { rel: { canonical: _props.canonical } } }); } defaults(props, _props); @@ -52,7 +43,7 @@ function handleStateChangeOnClient(props) { } } -function ograph ( p ) { +function ograph(p) { if (!p.meta) { p.meta = {}; } @@ -61,21 +52,21 @@ function ograph ( p ) { } const group = p.meta.property; - if ( group ) { - if ( p.title && !group['og:title'] ) { + if (group) { + if (p.title && !group['og:title']) { group['og:title'] = p.title; } - if ( p.hasOwnProperty('description') && !group['og:description'] ) { + if (p.hasOwnProperty('description') && !group['og:description']) { group['og:description'] = p.description; } - if ( p.hasOwnProperty('canonical') && !group['og:url'] ) { + if (p.hasOwnProperty('canonical') && !group['og:url']) { group['og:url'] = p.canonical; } } return p; } -function parseTags (tagName, props = {}) { +function parseTags(tagName, props = {}) { const tags = []; const contentKey = tagName === 'link' ? 'href' : 'content'; Object.keys(props).forEach(groupKey => { @@ -83,7 +74,7 @@ function parseTags (tagName, props = {}) { if (typeof group === 'string') { tags.push({ tagName, - [ groupKey ]: group + [groupKey]: group }); return; } @@ -93,8 +84,8 @@ function parseTags (tagName, props = {}) { if (value !== null) { tags.push({ tagName, - [ groupKey ]: key, - [ contentKey ]: value + [groupKey]: key, + [contentKey]: value }); } }); @@ -103,44 +94,37 @@ function parseTags (tagName, props = {}) { return tags; } -function getTags ( _props ) { - return [].concat( parseTags( 'meta', _props.meta ), parseTags( 'link', _props.link ) ); +function getTags(_props) { + return [].concat( + parseTags('meta', _props.meta), + parseTags('link', _props.link) + ); } -function render ( meta = {}, opts ) { - if ( typeof opts !== 'object' ) { - return meta; - } - +export function render(meta = {}) { let i = 0; const tags = []; - function renderTag ( entry ) { + function renderTag(entry) { const { tagName, ...attr } = entry; - if ( tagName === 'meta' ) { - return ( ); + if (tagName === 'meta') { + return ; } - if ( tagName === 'link' ) { - return ( ); + if (tagName === 'link') { + return ; } return null; } - if ( meta.title ) { - tags.push( { meta.title } ); + if (meta.title) { + tags.push({meta.title}); } - getTags( meta ).reduce(( acc, entry ) => { - tags.push( renderTag(entry) ); + return getTags(meta).reduce((acc, entry) => { + tags.push(renderTag(entry)); return tags; }, tags); - - if ( opts.asReact ) { - return tags; - } - - return renderToStaticMarkup(
{ tags }
).replace(/(^
|<\/div>$)/g, '').replace(/data-rdm="true"/g, 'data-rdm'); } class DocumentMeta extends Component { @@ -169,12 +153,14 @@ class DocumentMeta extends Component { ) ), auto: PropTypes.objectOf(PropTypes.bool) - } + }; render() { const { children } = this.props; const count = React.Children.count(children); - return count === 1 ? React.Children.only(children) : ( count ?
{ this.props.children }
: null ); + return count === 1 + ? React.Children.only(children) + : count ?
{this.props.children}
: null; } } @@ -183,16 +169,8 @@ const DocumentMetaWithSideEffect = withSideEffect( handleStateChangeOnClient )(DocumentMeta); -DocumentMetaWithSideEffect.renderAsReact = function rewindAsReact () { - return render( DocumentMetaWithSideEffect.rewind(), { asReact: true } ); -}; - -DocumentMetaWithSideEffect.renderAsHTML = function rewindAsHTML () { - return render( DocumentMetaWithSideEffect.rewind(), { asHtml: true } ); -}; - -DocumentMetaWithSideEffect.renderToStaticMarkup = function rewindAsHTML () { - return render( DocumentMetaWithSideEffect.rewind(), { asHtml: true } ); +DocumentMetaWithSideEffect.renderAsReact = function rewindAsReact() { + return render(DocumentMetaWithSideEffect.rewind()); }; export default DocumentMetaWithSideEffect; diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..9105e53 --- /dev/null +++ b/lib/server.js @@ -0,0 +1,16 @@ +import DocumentMeta, { render } from './index'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +function rewindAsStaticMarkup() { + const tags = render(DocumentMeta.rewind()); + + return renderToStaticMarkup(
{tags}
) + .replace(/(^
|<\/div>$)/g, '') + .replace(/data-rdm="true"/g, 'data-rdm'); +} + +export default DocumentMeta; + +DocumentMeta.renderToStaticMarkup = rewindAsStaticMarkup; +DocumentMeta.renderAsHTML = rewindAsStaticMarkup;