Skip to content

Commit

Permalink
Feature : can render as stream
Browse files Browse the repository at this point in the history
  • Loading branch information
formula1 committed Aug 8, 2016
1 parent 2787e8d commit 2091a94
Show file tree
Hide file tree
Showing 5 changed files with 459 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.11.6",
"chai": "^3.5.0",
"concat-stream": "^1.5.1",
"coveralls": "^2.11.12",
"cross-env": "^2.0.0",
"eslint": "^3.2.2",
Expand Down
7 changes: 5 additions & 2 deletions packages/inferno-server/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import renderToString, { renderToStaticMarkup } from '../../../src/server/renderToString';
import streamAsString, { RenderStream, streamAsStaticMarkup } from '../../../src/server/renderToString';

export default {
renderToString,
renderToStaticMarkup
renderToStaticMarkup,
streamAsString,
streamAsStaticMarkup,
RenderStream
};

245 changes: 245 additions & 0 deletions src/server/__tests__/creation-stream.spec.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import renderStream from './../renderToString.stream';
import concatStream from 'concat-stream';
import Component from './../../component/es2015';
import { createBlueprint } from './../../core/shapes';

class StatefulComponent extends Component {
render() {
return {
tag: 'span',
children: `stateless ${ this.props.value }!`
};
}
}

describe('SSR Creation - (non-JSX)', () => {
[
{
description: 'should render div with span child',
template: () => {
return {
tag: 'div',
children: {
dom: null,
tag: 'span'
}
};
},
result: '<div><span></span></div>'
}, {
description: 'should render div with span child and styling',
template: () => {
return {
tag: 'div',
children: {
dom: null,
tag: 'span',
style: 'border-left: 10px;'
}
};
},
result: '<div><span style="border-left: 10px;"></span></div>'
}, {
description: 'should render div with span child and styling #2',
template: () => {
return {
tag: 'div',
children: {
dom: null,
tag: 'span',
style: { borderLeft: 10 }
}
};
},
result: '<div><span style="border-left:10px;"></span></div>'
}, {
description: 'should render div with span child and styling #3',
template: () => {
return {
tag: 'div',
children: {
dom: null,
tag: 'span',
style: { fontFamily: 'Arial' }
}
};
},
result: '<div><span style="font-family:Arial;"></span></div>'
}, {
description: 'should render div with span child (with className)',
template: () => {
return {
tag: 'div',
className: 'foo',
children: {
dom: null,
className: 'bar',
tag: 'span'
}
};
},
result: '<div class="foo"><span class="bar"></span></div>'
}, {
description: 'should render div with text child',
template: () => {
return {
tag: 'div',
children: 'Hello world'
};
},
result: '<div>Hello world</div>'
}, {
description: 'should render div with text child (XSS script attack)',
template: () => {
return {
tag: 'div',
children: 'Hello world <img src="x" onerror="alert(\'XSS\')">'
};
},
result: '<div>Hello world &lt;img src=&quot;x&quot; onerror=&quot;alert(&#039;XSS&#039;)&quot;&gt;</div>'
}, {
description: 'should render div with text children',
template: () => {
return {
tag: 'div',
children: [ 'Hello', ' world' ]
};
},
result: '<div>Hello<!----> world</div>'
}, {
description: 'should render a void element correct',
template: () => {
return {
tag: 'input'
};
},
result: '<input>'
}, {
description: 'should render div with node children',
template: () => {
return {
tag: 'div',
children: [
{
tag: 'span',
children: 'Hello'
},
{
tag: 'span',
children: ' world!'
}
]
};
},
result: '<div><span>Hello</span><span> world!</span></div>'
}, {
description: 'should render div with node children #2',
template: () => {
return {
tag: 'div',
children: [
{
tag: 'span',
children: 'Hello',
attrs: {
id: '123'
}
},
{
tag: 'span',
children: ' world!',
className: 'foo'
}
]
};
},
result: '<div><span id="123">Hello</span><span class="foo"> world!</span></div>'
}, {
description: 'should render div with falsy children',
template: () => {
return {
tag: 'div',
children: 0
};
},
result: '<div>0</div>'
}, {
description: 'should render div with dangerouslySetInnerHTML',
template: () => {
return {
tag: 'div',
attrs: {
dangerouslySetInnerHTML: { __html: '<span>test</span>' }
}
};
},
result: '<div><span>test</span></div>'
}, {
description: 'should render a stateful component',
template: (value) => {
return {
tag: 'div',
children: {
tag: StatefulComponent,
attrs: {
value
}
}
};
},
result: '<div><span>stateless foo!</span></div>'
}, {
description: 'should render a stateless component',
template: (value) => {
return {
tag: 'div',
children: {
tag: ({ value }) => ({
tag: 'span',
children: `stateless ${ value }!`
}),
attrs: {
value
}
}
};
},
result: '<div><span>stateless foo!</span></div>'
}
].forEach(test => {
it(test.description, () => {
const container = document.createElement('div');
const vDom = test.template('foo');
return streamPromise(vDom).then(function (output) {
document.body.appendChild(container);
container.innerHTML = output;
expect(output).to.equal(test.result);
document.body.removeChild(container);
});
});
});
it('should not duplicate attrs', () => {
const tpl = createBlueprint({
tag: 'div',
attrs: { id: 'test' }
});

return Promise.all([
streamPromise(tpl()),
streamPromise(tpl()),
streamPromise(tpl())
]).then(function (results) {
expect(results[ 0 ] === results[ 1 ] && results[ 1 ] === results[ 2 ]).to.equal(true);
});
});
});

function streamPromise(dom) {
return new Promise(function (res, rej) {
renderStream(dom)
.on('error', rej)
.pipe(concatStream(function (buffer) {
res(buffer.toString('utf-8'));
}));
});
}
27 changes: 27 additions & 0 deletions src/server/prop-renderers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
isStringOrNumber,
isNullOrUndefined,
isNumber
} from './../core/utils';
import { isUnitlessNumber } from '../DOM/utils';
import { toHyphenCase, escapeAttr } from './utils';

export function renderStyleToString(style) {
if (isStringOrNumber(style)) {
return style;
} else {
const styles = [];
const keys = Object.keys(style);

for (let i = 0; i < keys.length; i++) {
const styleName = keys[i];
const value = style[styleName];
const px = isNumber(value) && !isUnitlessNumber[styleName] ? 'px' : '';

if (!isNullOrUndefined(value)) {
styles.push(`${ toHyphenCase(styleName) }:${ escapeAttr(value) }${ px };`);
}
}
return styles.join();
}
}
Loading

0 comments on commit 2091a94

Please sign in to comment.