Skip to content

Commit

Permalink
lib: move server side rendering code to separate file to avoid loadin…
Browse files Browse the repository at this point in the history
…g react-dom on the client
  • Loading branch information
danieljuhl committed Apr 28, 2017
1 parent bddb8a0 commit 0c3f312
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 73 deletions.
2 changes: 1 addition & 1 deletion example/server-side/server.js
Expand Up @@ -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();

Expand Down
50 changes: 37 additions & 13 deletions lib/__tests__/basic.test.js
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -77,14 +78,30 @@ describe('DocumentMeta', () => {
});
});

describe('.renderAsHTML()', () => {
describe('container element with children', () => {
it('renders the children', () => {
const title = 'foo';
const markup = renderToStaticMarkup(
<DocumentMeta title={ title }>
<div>Child element</div>
</DocumentMeta>
);

assert.strictEqual(markup, '<div>Child element</div>');
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', () => {
Expand All @@ -95,22 +112,29 @@ describe('DocumentMeta', () => {
)
)
);
assert.strictEqual(DocumentMeta.renderAsHTML(), '<title>c</title>');
assert.strictEqual(DocumentMetaServer.renderToStaticMarkup(), '<title>c</title>');
});
});


describe('container element with children', () => {
it('renders the children', () => {
const title = 'foo';
const markup = renderToStaticMarkup(
<DocumentMeta title={ title }>
<div>Child element</div>
</DocumentMeta>
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, '<div>Child element</div>');
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(), '<title>c</title>');
});
});
});
96 changes: 37 additions & 59 deletions 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;
Expand All @@ -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);
Expand All @@ -52,7 +43,7 @@ function handleStateChangeOnClient(props) {
}
}

function ograph ( p ) {
function ograph(p) {
if (!p.meta) {
p.meta = {};
}
Expand All @@ -61,29 +52,29 @@ 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 => {
const group = props[groupKey];
if (typeof group === 'string') {
tags.push({
tagName,
[ groupKey ]: group
[groupKey]: group
});
return;
}
Expand All @@ -93,8 +84,8 @@ function parseTags (tagName, props = {}) {
if (value !== null) {
tags.push({
tagName,
[ groupKey ]: key,
[ contentKey ]: value
[groupKey]: key,
[contentKey]: value
});
}
});
Expand All @@ -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 ( <meta {...attr} key={ i++ } data-rdm /> );
if (tagName === 'meta') {
return <meta {...attr} key={i++} data-rdm />;
}
if ( tagName === 'link' ) {
return ( <link {...attr} key={ i++ } data-rdm /> );
if (tagName === 'link') {
return <link {...attr} key={i++} data-rdm />;
}
return null;
}

if ( meta.title ) {
tags.push( <title key={ i++ }>{ meta.title }</title> );
if (meta.title) {
tags.push(<title key={i++}>{meta.title}</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( <div>{ tags }</div> ).replace(/(^<div>|<\/div>$)/g, '').replace(/data-rdm="true"/g, 'data-rdm');
}

class DocumentMeta extends Component {
Expand Down Expand Up @@ -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 ? <div>{ this.props.children }</div> : null );
return count === 1
? React.Children.only(children)
: count ? <div>{this.props.children}</div> : null;
}
}

Expand All @@ -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;
16 changes: 16 additions & 0 deletions 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(<div>{tags}</div>)
.replace(/(^<div>|<\/div>$)/g, '')
.replace(/data-rdm="true"/g, 'data-rdm');
}

export default DocumentMeta;

DocumentMeta.renderToStaticMarkup = rewindAsStaticMarkup;
DocumentMeta.renderAsHTML = rewindAsStaticMarkup;

0 comments on commit 0c3f312

Please sign in to comment.