Skip to content

Commit

Permalink
fix(ssr): render same root in server
Browse files Browse the repository at this point in the history
  • Loading branch information
MadCcc committed Dec 26, 2023
1 parent 7592e7a commit 3c5b150
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 47 deletions.
3 changes: 3 additions & 0 deletions examples/ssr-demo/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import './index.less';
// @ts-ignore
import styles from './index.less';
// @ts-ignore
import { useId } from 'react';
import umiLogo from './umi.png';

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

useServerInsertedHTML(() => {
return <div>inserted html</div>;
Expand All @@ -28,6 +30,7 @@ export default function HomePage() {
return (
<div>
<h1 className="title">Hello~</h1>
<h2 id={id}>{id}</h2>
<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>
Expand Down
4 changes: 3 additions & 1 deletion packages/preset-umi/templates/server.tpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getClientRootComponent } from '{{{ serverRendererPath }}}';
import { getClientRootComponent, getServerHTMLStart, getServerHTMLEnd } from '{{{ serverRendererPath }}}';
import { getRoutes } from './core/route';
import { createHistory as createClientHistory } from './core/history';
import { getPlugins as getClientPlugins } from './core/plugin';
Expand Down Expand Up @@ -48,6 +48,8 @@ const createOpts = {
getRoutes,
manifest: getManifest,
getClientRootComponent,
getServerHTMLStart,
getServerHTMLEnd,
helmetContext,
createHistory,
ServerInsertedHTMLContext,
Expand Down
91 changes: 45 additions & 46 deletions packages/renderer-react/src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ interface IHtmlProps {
pluginManager: any;
location: string;
loaderData: { [routeKey: string]: any };
manifest: any;
metadata?: IMetadata;
}

// Get the root React component for ReactDOMServer.renderToString
Expand Down Expand Up @@ -60,53 +58,54 @@ export async function getClientRootComponent(opts: IHtmlProps) {
{rootContainer}
</AppContext.Provider>
);
return <Html {...opts}>{app}</Html>;
return <div id="root">{app}</div>;
}

function Html({
children,
loaderData,
export function getServerHTMLStart({
manifest,
metadata,
}: React.PropsWithChildren<IHtmlProps>) {
// TODO: 处理 head 标签,比如 favicon.ico 的一致性
// TODO: root 支持配置

return (
<html lang={metadata?.lang || 'en'}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{metadata?.title && <title>{metadata.title}</title>}
{metadata?.description && (
<meta name="description" content={metadata.description} />
)}
{metadata?.keywords?.length && (
<meta name="keywords" content={metadata.keywords.join(',')} />
)}
{metadata?.metas?.map((em) => (
<meta key={em.name} name={em.name} content={em.content} />
))}
{manifest.assets['umi.css'] && (
<link rel="stylesheet" href={manifest.assets['umi.css']} />
)}
</head>
<body>
<noscript
dangerouslySetInnerHTML={{
__html: `<b>Enable JavaScript to run this app.</b>`,
}}
/>
}: {
manifest: any;
metadata?: IMetadata;
}) {
return `<html lang={metadata?.lang || 'en'}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
${metadata?.title && `<title>${metadata.title}</title>`}
${
metadata?.description &&
`<meta name="description" content="${metadata.description}" />`
}
${
metadata?.keywords?.length &&
`<meta name="keywords" content="${metadata.keywords.join(',')}" />`
}
${metadata?.metas?.map(
(em: any) =>
`<meta key="${em.name}" name="${em.name}" content="${em.content}" />`,
)}
${
manifest.assets['umi.css'] &&
`<link rel="stylesheet" href="${manifest.assets['umi.css']}" />`
}
</head>
<body>
<noscript>
<b>Enable JavaScript to run this app.</b>
</noscript>
`;
}

<div id="root">{children}</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__UMI_LOADER_DATA__ = ${JSON.stringify(
loaderData,
)}`,
}}
/>
</body>
</html>
);
export function getServerHTMLEnd({
loaderData,
}: {
loaderData: { [routeKey: string]: any };
}) {
return `
<script>
window.__UMI_LOADER_DATA__ = ${JSON.stringify(loaderData)}
</script>
</body>
</html>`;
}
13 changes: 13 additions & 0 deletions packages/server/src/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
ServerLoader,
UmiRequest,
} from './types';
import { IMetadata } from './types';

interface RouteLoaders {
[key: string]: () => Promise<any>;
Expand All @@ -33,6 +34,10 @@ interface CreateRequestHandlerOptions extends CreateRequestServerlessOptions {
getValidKeys: () => any;
getRoutes: (PluginManager: any) => any;
getClientRootComponent: (PluginManager: any) => any;
getServerHTMLStart: (opts: {
loaderData: { [routeKey: string]: any };
}) => string;
getServerHTMLEnd: (opts: { manifest: any; metadata?: IMetadata }) => string;
createHistory: (opts: any) => any;
helmetContext?: any;
ServerInsertedHTMLContext: React.Context<ServerInsertedHTMLHook | null>;
Expand Down Expand Up @@ -157,6 +162,8 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
return {
element,
manifest,
loaderData,
metadata,
};
};
}
Expand Down Expand Up @@ -192,11 +199,14 @@ export function createMarkupGenerator(opts: CreateRequestHandlerOptions) {
let chunks: Buffer[] = [];
const writable = new Writable();

chunks.push(Buffer.from(opts.getServerHTMLStart(jsx)));

writable._write = (chunk, _encoding, next) => {
chunks.push(Buffer.from(chunk));
next();
};
writable.on('finish', async () => {
chunks.push(Buffer.from(opts.getServerHTMLEnd(jsx)));
let html = Buffer.concat(chunks).toString('utf8');
html += await getGenerateStaticHTML(serverInsertedHTMLCallbacks);
// append helmet tags to head
Expand Down Expand Up @@ -275,7 +285,10 @@ export default function createRequestHandler(
next();
};

res.write(opts.getServerHTMLStart(jsx));

writable.on('finish', async () => {
res.write(opts.getServerHTMLEnd(jsx));
res.write(await getGenerateStaticHTML());
res.end();
});
Expand Down

0 comments on commit 3c5b150

Please sign in to comment.