mirror of
https://github.com/zhigang1992/styled-components.git
synced 2026-01-12 17:52:45 +08:00
add seal fn back to ServerStyleSheet for backward compat (#2581)
* add seal fn back to ServerStyleSheet for backward compat * Make ServerStyleSheet sealed error more precise - collectStyles could very well be used for multiple elements although that's a rare use case - getStyleTags and getStyleElement are safe to be called again, unless interleaveWithNodeStream was called already - interleaveWithNodeStream is not safe to be called again * add tests verifying the error scenarios, allow getStyleTags during streaming
This commit is contained in:
@@ -2,9 +2,7 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IS_BROWSER, SC_ATTR, SC_ATTR_VERSION, SC_VERSION } from '../constants';
|
||||
|
||||
import StyledError from '../utils/error';
|
||||
import getNonce from '../utils/nonce';
|
||||
import StyleSheet from '../sheet';
|
||||
@@ -15,6 +13,8 @@ declare var __SERVER__: boolean;
|
||||
const CLOSING_TAG_R = /^\s*<\/[a-z]/i;
|
||||
|
||||
export default class ServerStyleSheet {
|
||||
isStreaming: boolean;
|
||||
|
||||
sheet: StyleSheet;
|
||||
|
||||
sealed: boolean;
|
||||
@@ -24,25 +24,36 @@ export default class ServerStyleSheet {
|
||||
this.sealed = false;
|
||||
}
|
||||
|
||||
_emitSheetCSS = (): string => {
|
||||
const css = this.sheet.toString();
|
||||
const nonce = getNonce();
|
||||
const attrs = [nonce && `nonce="${nonce}"`, SC_ATTR, `${SC_ATTR_VERSION}="${SC_VERSION}"`];
|
||||
const htmlAttr = attrs.filter(Boolean).join(' ');
|
||||
|
||||
return `<style ${htmlAttr}>${css}</style>`;
|
||||
};
|
||||
|
||||
collectStyles(children: any) {
|
||||
if (this.sealed) {
|
||||
throw new StyledError(2);
|
||||
}
|
||||
|
||||
this.sealed = true;
|
||||
return <StyleSheetManager sheet={this.sheet}>{children}</StyleSheetManager>;
|
||||
}
|
||||
|
||||
getStyleTags = (): string => {
|
||||
const css = this.sheet.toString();
|
||||
const nonce = getNonce();
|
||||
const attrs = [nonce && `nonce="${nonce}"`, SC_ATTR, `${SC_ATTR_VERSION}="${SC_VERSION}"`];
|
||||
if (this.sealed) {
|
||||
throw new StyledError(2);
|
||||
}
|
||||
|
||||
const htmlAttr = attrs.filter(Boolean).join(' ');
|
||||
return `<style ${htmlAttr}>${css}</style>`;
|
||||
return this._emitSheetCSS();
|
||||
};
|
||||
|
||||
getStyleElement = () => {
|
||||
if (this.sealed) {
|
||||
throw new StyledError(2);
|
||||
}
|
||||
|
||||
const props = {
|
||||
[SC_ATTR]: '',
|
||||
[SC_ATTR_VERSION]: SC_VERSION,
|
||||
@@ -62,20 +73,25 @@ export default class ServerStyleSheet {
|
||||
interleaveWithNodeStream(input: any) {
|
||||
if (!__SERVER__ || IS_BROWSER) {
|
||||
throw new StyledError(3);
|
||||
} else if (this.sealed) {
|
||||
throw new StyledError(2);
|
||||
}
|
||||
|
||||
this.seal();
|
||||
|
||||
// eslint-disable-next-line global-require
|
||||
const { Readable, Transform } = require('stream');
|
||||
|
||||
const readableStream: Readable = input;
|
||||
const { sheet, getStyleTags } = this;
|
||||
const { sheet, _emitSheetCSS } = this;
|
||||
|
||||
const transformer = new Transform({
|
||||
transform: function appendStyleChunks(chunk, /* encoding */ _, callback) {
|
||||
// Get the chunk and retrieve the sheet's CSS as an HTML chunk,
|
||||
// then reset its rules so we get only new ones for the next chunk
|
||||
const renderedHtml = chunk.toString();
|
||||
const html = getStyleTags();
|
||||
const html = _emitSheetCSS();
|
||||
|
||||
sheet.clearTag();
|
||||
|
||||
// prepend style html to chunk, unless the start of the chunk is a
|
||||
@@ -101,4 +117,8 @@ export default class ServerStyleSheet {
|
||||
|
||||
return readableStream.pipe(transformer);
|
||||
}
|
||||
|
||||
seal = () => {
|
||||
this.sealed = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,3 +61,54 @@ data-styled.g2[id=\\"sc-b\\"]{content:\\"c,\\"}
|
||||
"data-styled-version": "JEST_MOCK_VERSION",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleElement is called after interleaveWithNodeStream is called 1`] = `
|
||||
"Can't collect styles once you've consumed a \`ServerStyleSheet\`'s styles! \`ServerStyleSheet\` is a one off instance for each server-side render cycle.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleElement is called after streaming is complete 1`] = `
|
||||
"<style data-styled data-styled-version=\\"JEST_MOCK_VERSION\\">body{background:papayawhip;}
|
||||
data-styled.g1[id=\\"sc-global-a\\"]{content:\\"sc-global-a,\\"}
|
||||
.c{color:red;}
|
||||
data-styled.g2[id=\\"sc-b\\"]{content:\\"c,\\"}
|
||||
</style><h1 class=\\"sc-b c\\">Hello SSR!</h1>"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleElement is called after streaming is complete 2`] = `
|
||||
"Can't collect styles once you've consumed a \`ServerStyleSheet\`'s styles! \`ServerStyleSheet\` is a one off instance for each server-side render cycle.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleTags is called after interleaveWithNodeStream is called 1`] = `
|
||||
"Can't collect styles once you've consumed a \`ServerStyleSheet\`'s styles! \`ServerStyleSheet\` is a one off instance for each server-side render cycle.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleTags is called after streaming is complete 1`] = `
|
||||
"<style data-styled data-styled-version=\\"JEST_MOCK_VERSION\\">body{background:papayawhip;}
|
||||
data-styled.g1[id=\\"sc-global-a\\"]{content:\\"sc-global-a,\\"}
|
||||
.c{color:red;}
|
||||
data-styled.g2[id=\\"sc-b\\"]{content:\\"c,\\"}
|
||||
</style><h1 class=\\"sc-b c\\">Hello SSR!</h1>"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if getStyleTags is called after streaming is complete 2`] = `
|
||||
"Can't collect styles once you've consumed a \`ServerStyleSheet\`'s styles! \`ServerStyleSheet\` is a one off instance for each server-side render cycle.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?"
|
||||
`;
|
||||
|
||||
exports[`ssr should throw if interleaveWithNodeStream is called twice 1`] = `
|
||||
"Can't collect styles once you've consumed a \`ServerStyleSheet\`'s styles! \`ServerStyleSheet\` is a one off instance for each server-side render cycle.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?"
|
||||
`;
|
||||
|
||||
@@ -142,7 +142,8 @@ describe('ssr', () => {
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const html = renderToString(
|
||||
|
||||
renderToString(
|
||||
sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
@@ -150,6 +151,7 @@ describe('ssr', () => {
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
|
||||
const element = sheet.getStyleElement();
|
||||
|
||||
expect(element.props.dangerouslySetInnerHTML).toBeDefined();
|
||||
@@ -169,7 +171,8 @@ describe('ssr', () => {
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const html = renderToString(
|
||||
|
||||
renderToString(
|
||||
sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Heading>Hello SSR!</Heading>
|
||||
@@ -236,11 +239,6 @@ describe('ssr', () => {
|
||||
color: green;
|
||||
`;
|
||||
|
||||
// These create a long chunk of (hopefully) uninterrupted HTML
|
||||
const elements = new Array(100)
|
||||
.fill(0)
|
||||
.map((_, i) => <div key={i}>*************************</div>);
|
||||
|
||||
// This is the result of the above
|
||||
const expectedElements = '<div>*************************</div>'.repeat(100);
|
||||
|
||||
@@ -284,7 +282,7 @@ describe('ssr', () => {
|
||||
const jsx = sheet.collectStyles(null);
|
||||
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise(resolve => {
|
||||
stream.on('data', () => {});
|
||||
|
||||
stream.on('error', err => {
|
||||
@@ -339,4 +337,141 @@ describe('ssr', () => {
|
||||
stream.on('error', reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if interleaveWithNodeStream is called twice', () => {
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`;
|
||||
const Heading = styled.h1`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const jsx = sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
sheet.interleaveWithNodeStream(sheet.interleaveWithNodeStream(renderToNodeStream(jsx)))
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should throw if getStyleTags is called after interleaveWithNodeStream is called', () => {
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`;
|
||||
const Heading = styled.h1`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
|
||||
const jsx = sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
sheet.interleaveWithNodeStream(renderToNodeStream(jsx));
|
||||
|
||||
expect(sheet.getStyleTags).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should throw if getStyleElement is called after interleaveWithNodeStream is called', () => {
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`;
|
||||
const Heading = styled.h1`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
|
||||
const jsx = sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
sheet.interleaveWithNodeStream(renderToNodeStream(jsx));
|
||||
|
||||
expect(sheet.getStyleElement).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should throw if getStyleTags is called after streaming is complete', () => {
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`;
|
||||
const Heading = styled.h1`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const jsx = sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>
|
||||
);
|
||||
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let received = '';
|
||||
|
||||
stream.on('data', chunk => {
|
||||
received += chunk;
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(received).toMatchSnapshot();
|
||||
expect(sheet.sealed).toBe(true);
|
||||
expect(sheet.getStyleTags).toThrowErrorMatchingSnapshot();
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
stream.on('error', reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if getStyleElement is called after streaming is complete', () => {
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`;
|
||||
const Heading = styled.h1`
|
||||
color: red;
|
||||
`;
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const jsx = sheet.collectStyles(
|
||||
<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>
|
||||
);
|
||||
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let received = '';
|
||||
|
||||
stream.on('data', chunk => {
|
||||
received += chunk;
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(received).toMatchSnapshot();
|
||||
expect(sheet.sealed).toBe(true);
|
||||
expect(sheet.getStyleElement).toThrowErrorMatchingSnapshot();
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
stream.on('error', reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,10 +6,11 @@ Cannot create styled-component for component: %s.
|
||||
|
||||
## 2
|
||||
|
||||
Can't collect styles once you've consumed a `ServerStyleSheet`'s styles! `ServerStyleSheet` is a one off instance for each server-side render cycle.
|
||||
Can't call method, once `interleaveWithNodeStream` is used, since it
|
||||
will split the underlying styles into multiple parts.
|
||||
|
||||
- Are you trying to reuse it across renders?
|
||||
- Are you accidentally calling collectStyles twice?
|
||||
- Are you trying to call `interleaveWithNodeStream` twice?
|
||||
- Are you calling `getStyleTags`, `getStyleElement`, or `collectStyles` after it?
|
||||
|
||||
## 3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user