Skip to content

Commit

Permalink
feat: Tricky ssr support (#5)
Browse files Browse the repository at this point in the history
* 0.0.0-alpha.5

* refactor: use StyleProvider instead of StyleContext

* docs: Create 2 ssr demo

* docs: prepare default cssinjs

* chore: rename api

* feat: tricky ssr

* test: tricky ssr
  • Loading branch information
zombieJ committed Feb 14, 2022
1 parent 3486cc6 commit c03fe4c
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 122 deletions.
5 changes: 5 additions & 0 deletions docs/demo/ssr-advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## SSR Advanced

进阶 SSR 可以将样式抽离至外部的 style 标签下:

<code src="../examples/ssr-advanced.tsx">
5 changes: 5 additions & 0 deletions docs/demo/ssr-tricky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## SSR Tricky

我们强烈推荐使用 SSR Advanced 示例使用服务端渲染,如果渲染侧无法控制也可以通过降级方式自动处理:

<code src="../examples/ssr-tricky.tsx">
3 changes: 0 additions & 3 deletions docs/demo/ssr.md

This file was deleted.

8 changes: 3 additions & 5 deletions docs/examples/auto-clear.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import Button from './components/Button';
import { Cache, StyleContext } from '../../src/';

const cache = new Cache();
import { StyleProvider } from '../../src/';

export default function App() {
const [show, setShow] = React.useState(true);
Expand All @@ -13,7 +11,7 @@ export default function App() {
}, []);

return (
<StyleContext.Provider value={{ autoClear: true, cache }}>
<StyleProvider autoClear>
<div style={{ background: 'rgba(0,0,0,0.1)', padding: 16 }}>
<h3>配置同步自动删除添加的样式</h3>

Expand All @@ -34,6 +32,6 @@ export default function App() {
</>
)}
</div>
</StyleContext.Provider>
</StyleProvider>
);
}
101 changes: 101 additions & 0 deletions docs/examples/ssr-advanced.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import { hydrate } from 'react-dom';
import { renderToString } from 'react-dom/server';
import { StyleProvider, Cache, extractStyle } from '../../src';
import Button from './components/Button';
import Spin from './components/Spin';
import { DesignTokenContext } from './components/theme';

const Demo = () => {
const sharedProps: React.HTMLAttributes<HTMLButtonElement> = {
onClick: ({ target }) => {
console.log('Click:', target);
},
};

return (
<div style={{ display: 'flex', columnGap: 8 }}>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />

<DesignTokenContext.Provider
value={{ token: { primaryColor: 'red' }, hashed: true }}
>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />
</DesignTokenContext.Provider>
<DesignTokenContext.Provider
value={{ token: { primaryColor: 'green' }, hashed: 'v5' }}
>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />
</DesignTokenContext.Provider>
</div>
);
};

const Pre: React.FC = ({ children }) => (
<pre
style={{
background: '#FFF',
padding: 8,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{children}
</pre>
);

export default function App() {
const cacheRef = React.useRef(new Cache());

const [ssrHTML, ssrStyle] = React.useMemo(() => {
const html = renderToString(
<StyleProvider
// Tell cssinjs not insert dom style. No need in real world
mock="server"
cache={cacheRef.current}
>
<Demo />
</StyleProvider>,
);

const style = extractStyle(cacheRef.current);

return [html, style];
}, []);

// 模拟一个空白文档,并且注水
React.useEffect(() => {
console.log('Delay to hydrate...');
setTimeout(() => {
const styles = document.createElement('div');
styles.innerHTML = ssrStyle;

Array.from(styles.childNodes).forEach((style) => {
document.head.appendChild(style);
});

const container = document.getElementById('ssr');
hydrate(<Demo />, container);
}, 500);
}, []);

return (
<div style={{ background: 'rgba(0,0,0,0.1)', padding: 16 }}>
<h3>服务端渲染提前获取所有样式</h3>

<Pre>{ssrStyle}</Pre>
<Pre>{ssrHTML}</Pre>

<div id="ssr" dangerouslySetInnerHTML={{ __html: ssrHTML }} />
</div>
);
}
85 changes: 85 additions & 0 deletions docs/examples/ssr-tricky.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { hydrate } from 'react-dom';
import { renderToString } from 'react-dom/server';
import { StyleProvider } from '../../src';
import Button from './components/Button';
import Spin from './components/Spin';
import { DesignTokenContext } from './components/theme';

const Demo = () => {
const sharedProps: React.HTMLAttributes<HTMLButtonElement> = {
onClick: ({ target }) => {
console.log('Click:', target);
},
};

return (
<div style={{ display: 'flex', columnGap: 8 }}>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />

<DesignTokenContext.Provider
value={{ token: { primaryColor: 'red' }, hashed: true }}
>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />
</DesignTokenContext.Provider>
<DesignTokenContext.Provider
value={{ token: { primaryColor: 'green' }, hashed: 'v5' }}
>
<Button {...sharedProps} type="ghost">
Button
</Button>
<Spin />
</DesignTokenContext.Provider>
</div>
);
};

const Pre: React.FC = ({ children }) => (
<pre
style={{
background: '#FFF',
padding: 8,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{children}
</pre>
);

export default function App() {
const ssrHTML = React.useMemo(
() =>
renderToString(
<StyleProvider mock="server">
<Demo />
</StyleProvider>,
),
[],
);

// 模拟一个空白文档,并且注水
React.useEffect(() => {
console.log('Delay to hydrate...');
setTimeout(() => {
const container = document.getElementById('ssr');
hydrate(<Demo />, container);
}, 500);
}, []);

return (
<div style={{ background: 'rgba(0,0,0,0.1)', padding: 16 }}>
<h3>服务端渲染提前获取所有样式</h3>

<Pre>{ssrHTML}</Pre>

<div id="ssr" dangerouslySetInnerHTML={{ __html: ssrHTML }} />
</div>
);
}
74 changes: 0 additions & 74 deletions docs/examples/ssr.tsx

This file was deleted.

59 changes: 56 additions & 3 deletions src/StyleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
import * as React from 'react';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import CacheEntity from './Cache';
import { getTokenStyles } from './useStyleRegister';

const StyleContext = React.createContext<{
export interface StyleContextProps {
autoClear?: boolean;
/** @private Test only. Not work in production. */
insertStyle?: boolean;
mock?: 'server' | 'client';
/**
* Only set when you need ssr to extract style on you own.
* If not provided, it will auto create <style /> on the end of Provider in server side.
*/
cache: CacheEntity;
}>({
}

const StyleContext = React.createContext<StyleContextProps>({
cache: new CacheEntity(),
});

export type StyleProviderProps = Partial<StyleContextProps>;

const InlineStyle = ({ cache }: { cache: CacheEntity }) => {
const styles = getTokenStyles(cache);
return (
<>
{styles.map(({ token, style }, index) => (
<style
data-token-key={token}
key={index}
dangerouslySetInnerHTML={{ __html: style }}
/>
))}
</>
);
};

export const StyleProvider: React.FC<StyleProviderProps> = ({
autoClear,
mock,
cache,
children,
}) => {
const context = React.useMemo<StyleContextProps>(
() => ({
autoClear,
mock,
cache: cache || new CacheEntity(),
}),
[autoClear, mock, cache],
);

const shouldInsertSSRStyle = React.useMemo(() => {
const isServerSide = mock !== undefined ? mock === 'server' : !canUseDom();
return isServerSide && !cache;
}, [mock, cache]);

return (
<StyleContext.Provider value={context}>
{children}
{shouldInsertSSRStyle && <InlineStyle cache={context.cache} />}
</StyleContext.Provider>
);
};

export default StyleContext;
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import useStyleRegister, { extractStyle } from './useStyleRegister';
import type { CSSObject, CSSInterpolation } from './useStyleRegister';
import useCacheToken from './useCacheToken';
import Cache from './Cache';
import StyleContext from './StyleContext';
import { StyleProvider } from './StyleContext';
import Keyframes from './Keyframes';

export {
Theme,
useStyleRegister,
useCacheToken,
Cache,
StyleContext,
StyleProvider,
Keyframes,
extractStyle,
};
Expand Down
Loading

0 comments on commit c03fe4c

Please sign in to comment.