Skip to content

Commit

Permalink
feat: SSR support useServerInsertedHTML
Browse files Browse the repository at this point in the history
  • Loading branch information
MadCcc committed Mar 28, 2024
1 parent a1e5cee commit 8a6f53b
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 48 deletions.
2 changes: 2 additions & 0 deletions examples/ssr-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"start:prod": "node ./production-server.js"
},
"dependencies": {
"@ant-design/cssinjs": "^1.18.5",
"antd": "^5",
"express": "4.18.2",
"umi": "workspace:*"
}
Expand Down
63 changes: 45 additions & 18 deletions examples/ssr-demo/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Input } from 'antd';
import { useId, useState } from 'react';
import {
Link,
MetadataLoader,
Expand All @@ -15,35 +17,60 @@ import './index.less';
// @ts-ignore
import styles from './index.less';
// @ts-ignore
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import umiLogo from './umi.png';

export default function HomePage() {
const clientLoaderData = useClientLoaderData();
const serverLoaderData = useServerLoaderData<typeof serverLoader>();

const [cssCache] = useState(() => createCache());

useServerInsertedHTML(() => {
return <div>inserted html</div>;
return (
<style
dangerouslySetInnerHTML={{
__html: `.server_inserted_style { color: #1677ff }`,
}}
></style>
);
});

useServerInsertedHTML(() => {
const style = extractStyle(cssCache, { plain: true });
return (
<style
id="antd-cssinjs"
dangerouslySetInnerHTML={{ __html: style }}
></style>
);
});

const id = useId();

return (
<div>
<h1 className="title">Hello~</h1>
<p className={styles.blue}>This is index.tsx</p>
<p className={cssStyle.title}>I should be pink</p>
<p className={cssStyle.blue}>I should be cyan</p>
<Button />
<img src={bigImage} alt="" />
<img src={umiLogo} alt="umi" />
<Link to="/users/user">/users/user</Link>
<div style={{ backgroundColor: '#eee', padding: 12 }}>
<p>
点击这个按钮以后,需要加载 users, user2, info 三个路由组件需要的数据
</p>
<Link to="/users/user2/info">/users/user2/info</Link>
<StyleProvider cache={cssCache}>
<div>
<h1 className="title">Hello~</h1>
<p className="server_inserted_style">id: {id}</p>
<p className={styles.blue}>This is index.tsx</p>
<p className={cssStyle.title}>I should be pink</p>
<p className={cssStyle.blue}>I should be cyan</p>
<Button />
<Input placeholder="这个样式不应该闪烁" />
<img src={bigImage} alt="" />
<img src={umiLogo} alt="umi" />
<Link to="/users/user">/users/user</Link>
<div style={{ backgroundColor: '#eee', padding: 12 }}>
<p>
点击这个按钮以后,需要加载 users, user2, info 三个路由组件需要的数据
</p>
<Link to="/users/user2/info">/users/user2/info</Link>
</div>
<p>client loader data: {JSON.stringify(clientLoaderData)}</p>
<p>server loader data: {JSON.stringify(serverLoaderData)}</p>
</div>
<p>client loader data: {JSON.stringify(clientLoaderData)}</p>
<p>server loader data: {JSON.stringify(serverLoaderData)}</p>
</div>
</StyleProvider>
);
}

Expand Down
60 changes: 45 additions & 15 deletions packages/server/src/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,18 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {

const getGenerateStaticHTML = (
serverInsertedHTMLCallbacks?: Set<() => React.ReactNode>,
wrapper: (children: React.ReactElement) => React.ReactElement = (children) =>
children,
) => {
return (
ReactDomServer.renderToString(
React.createElement(React.Fragment, {
children: Array.from(serverInsertedHTMLCallbacks || []).map(
(callback) => callback(),
),
}),
wrapper(
React.createElement(React.Fragment, {
children: Array.from(serverInsertedHTMLCallbacks || []).map(
(callback) => callback(),
),
}),
),
) || ''
);
};
Expand Down Expand Up @@ -200,7 +204,7 @@ export function createMarkupGenerator(opts: CreateRequestHandlerOptions) {
};
writable.on('finish', async () => {
let html = Buffer.concat(chunks).toString('utf8');
html += await getGenerateStaticHTML(serverInsertedHTMLCallbacks);
html += getGenerateStaticHTML(serverInsertedHTMLCallbacks);
// append helmet tags to head
if (opts.helmetContext) {
html = html.replace(
Expand Down Expand Up @@ -343,6 +347,12 @@ export default function createRequestHandler(
res.status(200).json(data);
},
async sendPage(jsx) {
const serverInsertedHTMLCallbacks: Set<() => React.ReactNode> =
new Set();
const JSXProvider = createJSXProvider(
opts.ServerInsertedHTMLContext.Provider,
serverInsertedHTMLCallbacks,
);
const writable = new Writable();

res.type('html');
Expand All @@ -353,19 +363,39 @@ export default function createRequestHandler(
};

writable.on('finish', async () => {
res.write(getGenerateStaticHTML());
res.write(
getGenerateStaticHTML(serverInsertedHTMLCallbacks, (children) => {
return React.createElement(
'div',
{ id: 'umi-server-inserted-html', hidden: true },
children,
);
}),
);
res.write(`<script>
var serverInsertedHtml = document.getElementById('umi-server-inserted-html');
if (serverInsertedHtml) {
Array.from(serverInsertedHtml.children).forEach((node) => {
document.head.appendChild(node);
});
serverInsertedHtml.remove();
}
</script>`);
res.end();
});

const stream = ReactDomServer.renderToPipeableStream(jsx.element, {
bootstrapScripts: [jsx.manifest.assets['umi.js'] || '/umi.js'],
onShellReady() {
stream.pipe(writable);
},
onError(x: any) {
console.error(x);
const stream = ReactDomServer.renderToPipeableStream(
React.createElement(JSXProvider, undefined, jsx.element),
{
bootstrapScripts: [jsx.manifest.assets['umi.js'] || '/umi.js'],
onShellReady() {
stream.pipe(writable);
},
onError(x: any) {
console.error(x);
},
},
});
);
},
otherwise: next,
};
Expand Down
44 changes: 29 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8a6f53b

Please sign in to comment.