Skip to content

Commit

Permalink
try to handle React's streaming renderer yielding at inopportune times (
Browse files Browse the repository at this point in the history
#2413)

* Styled Components test interleaveWithNodeStream

Added a test for interleaveWithNodeStream.  The niche case where the
style tag gets interleaved into a textarea causes the style tag to be
rendered as text in the text area.

* when streaming try to inject outside the nearest element

this helps for some edge cases like <textarea> where the
element accepts children but has weird behavior for the children
compared to typical HTML elements

* remove enzyme stuff in favor of a simpler check

* changelog entry
  • Loading branch information
Dsan10s authored and quantizor committed Mar 23, 2019
1 parent cc0f5d2 commit 80ca302
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this

- Filter `suppressClassNameWarning` to not to pass down to the wrapped components [@taneba](https://github.com/taneba) (see [#2365](https://github.com/styled-components/styled-components/pull/2365))

- Fix an edge case where React would break streaming inside `<textarea>` elements, which have special child behavior and aren't a suitable place to inject a style tag (see [#2413](https://github.com/styled-components/styled-components/pull/2413))

## [v4.1.3] - 2018-12-17

- Under the hood code cleanup of the Babel macro, by [@lucleray](https://github.com/lucleray) (see [#2286](https://github.com/styled-components/styled-components/pull/2286))
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"name": "styled-components-project",
"private": true,
"version": "4.1.3",
"scripts":{
"scripts": {
"bs": "lerna bootstrap",
"build": "lerna run build --stream --parallel",
"clean":"lerna clean",
"clean": "lerna clean",
"dev": "lerna run dev --stream --parallel",
"flow": "lerna run flow --stream --parallel",
"lint": "lerna run lint --stream --parallel",
Expand Down
18 changes: 16 additions & 2 deletions packages/styled-components/src/models/ServerStyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import StyleSheetManager from './StyleSheetManager';

declare var __SERVER__: boolean;

const CLOSING_TAG_R = /^\s*<\/[a-z]/i;

export default class ServerStyleSheet {
instance: StyleSheet;

Expand Down Expand Up @@ -80,13 +82,25 @@ export default class ServerStyleSheet {
/* force our StyleSheets to emit entirely new tags */
instance.sealAllTags();

/* prepend style html to chunk */
this.push(html + chunk);
const renderedHtml = chunk.toString();

/* prepend style html to chunk, unless the start of the chunk is a closing tag in which case append right after that */
if (CLOSING_TAG_R.test(renderedHtml)) {
const endOfClosingTag = renderedHtml.indexOf('>');

this.push(
renderedHtml.slice(0, endOfClosingTag + 1) +
html +
renderedHtml.slice(endOfClosingTag + 1)
);
} else this.push(html + renderedHtml);

callback();
},
});

readableStream.on('end', () => this.seal());

readableStream.on('error', err => {
this.seal();

Expand Down

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions packages/styled-components/src/test/ssr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,49 @@ describe('ssr', () => {
});
});
});

it('should not interleave style tags into textarea elements', () => {
const StyledTextArea = styled.textarea`
height: ${props => `${props.height}px`};
`;

const sheet = new ServerStyleSheet();

// Currently we cannot set the chunk size to read with react renderToNodeStream, so to ensure
// that multiple chunks are created, we initialize a large array of styled text areas. We give
// each textarea a different style to ensure a large enough number of style tags are generated
// to be interleaved in the document
const jsx = sheet.collectStyles(
<React.Fragment>
{new Array(500).fill(0).map((_, i) => (
<StyledTextArea
key={i}
className="test-textarea"
onChange={() => {}}
value={`Textarea ${i}`}
height={i}
/>
))}
</React.Fragment>
);

const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));

return new Promise((resolve, reject) => {
let received = '';

stream.on('data', chunk => {
received += chunk;
});

stream.on('end', () => {
const styleTagsInsideTextarea = received.match(/<\/style>[^<]*<\/textarea>/g);

expect(styleTagsInsideTextarea).toBeNull();
resolve();
});

stream.on('error', reject);
});
});
});
7 changes: 6 additions & 1 deletion packages/styled-components/test-utils/setupTestFramework.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// @flow
const consoleError = console.error;

const suppressedErrors = [
'Error: Could not parse CSS stylesheet',
'Warning: Use the `defaultValue` or `value` props instead of setting children on <textarea>',
];

beforeEach(() => {
// Suppress errors from JSDOM CSS parser
// See: https://github.com/jsdom/jsdom/issues/2177
// eslint-disable-next-line flowtype-errors/show-errors
(console: any).error = message => {
if (!message.includes('Error: Could not parse CSS stylesheet')) {
if (!suppressedErrors.some(suppressedError => message.includes(suppressedError))) {
consoleError(message);
}
};
Expand Down

0 comments on commit 80ca302

Please sign in to comment.