From c4ef72e9122985174b17933dbfaa0feed010c941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9F=E8=B4=A4?= Date: Fri, 16 Jun 2023 15:00:18 +0800 Subject: [PATCH 1/5] feat(ssr): support useServerInsertedHTML --- examples/ssr-demo/src/pages/index.tsx | 12 ++- packages/preset-umi/src/features/ssr/ssr.ts | 24 +++++ .../src/features/tmpFiles/tmpFiles.ts | 5 ++ packages/preset-umi/templates/server.tpl | 8 ++ packages/renderer-react/src/server.tsx | 2 + packages/server/src/ssr.ts | 89 ++++++++++++++++--- 6 files changed, 125 insertions(+), 15 deletions(-) diff --git a/examples/ssr-demo/src/pages/index.tsx b/examples/ssr-demo/src/pages/index.tsx index 01b9151f2a85..1c1efec02c6d 100644 --- a/examples/ssr-demo/src/pages/index.tsx +++ b/examples/ssr-demo/src/pages/index.tsx @@ -1,5 +1,10 @@ import React from 'react'; -import { Link, useClientLoaderData, useServerLoaderData } from 'umi'; +import { + Link, + useClientLoaderData, + useServerInsertedHTML, + useServerLoaderData, +} from 'umi'; import Button from '../components/Button'; // @ts-ignore import bigImage from './big_image.jpg'; @@ -14,6 +19,11 @@ import umiLogo from './umi.png'; export default function HomePage() { const clientLoaderData = useClientLoaderData(); const serverLoaderData = useServerLoaderData(); + + useServerInsertedHTML(() => { + return
inserted html
; + }); + return (

Hello~

diff --git a/packages/preset-umi/src/features/ssr/ssr.ts b/packages/preset-umi/src/features/ssr/ssr.ts index 65e7e1ee29b3..cfd42ab0be5b 100644 --- a/packages/preset-umi/src/features/ssr/ssr.ts +++ b/packages/preset-umi/src/features/ssr/ssr.ts @@ -69,6 +69,30 @@ export default (api: IApi) => { content: ` import * as React from 'react'; export { React }; +`, + }); + + api.writeTmpFile({ + noPluginDir: true, + path: 'core/serverInsertedHTMLContext.ts', + content: ` +// Use React.createContext to avoid errors from the RSC checks because +// it can't be imported directly in Server Components: +import React from 'react' + +export type ServerInsertedHTMLHook = (callbacks: () => React.ReactNode) => void; +// More info: https://github.com/vercel/next.js/pull/40686 +export const ServerInsertedHTMLContext = + React.createContext(null as any); + +// copy form https://github.com/vercel/next.js/blob/fa076a3a69c9ccf63c9d1e53e7b681aa6dc23db7/packages/next/src/shared/lib/server-inserted-html.tsx#L13 +export function useServerInsertedHTML(callback: () => React.ReactNode): void { + const addInsertedServerHTMLCallback = React.useContext(ServerInsertedHTMLContext); + // Should have no effects on client where there's no flush effects provider + if (addInsertedServerHTMLCallback) { + addInsertedServerHTMLCallback(callback); + } +} `, }); }); diff --git a/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts b/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts index cb30a8ce6802..907cfaeccc75 100644 --- a/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts +++ b/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts @@ -617,6 +617,11 @@ if (process.env.NODE_ENV === 'development') { exports.push(`export { TestBrowser } from './testBrowser';`); } } + if (api.config.ssr && api.appData.framework === 'react') { + exports.push( + `export { useServerInsertedHTML } from './core/serverInsertedHTMLContext';`, + ); + } // plugins exports.push('// plugins'); const allPlugins = readdirSync(api.paths.absTmpPath).filter((file) => diff --git a/packages/preset-umi/templates/server.tpl b/packages/preset-umi/templates/server.tpl index 3478c0944292..3b9581ebced8 100644 --- a/packages/preset-umi/templates/server.tpl +++ b/packages/preset-umi/templates/server.tpl @@ -6,11 +6,18 @@ import { PluginManager } from '{{{ umiPluginPath }}}'; import createRequestHandler, { createMarkupGenerator } from '{{{ umiServerPath }}}'; let helmetContext; +let ServerInsertedHTMLContext; try { helmetContext = require('./core/helmetContext').context; } catch { /* means `helmet: false`, do noting */ } + +try { + ServerInsertedHTMLContext = require('./core/serverInsertedHTMLContext').ServerInsertedHTMLContext; +} catch { /* means `helmet: false`, do noting */ } + + const routesWithServerLoader = { {{#routesWithServerLoader}} '{{{ id }}}': () => import('{{{ path }}}'), @@ -50,6 +57,7 @@ const createOpts = { getClientRootComponent, helmetContext, createHistory, + ServerInsertedHTMLContext, }; const requestHandler = createRequestHandler(createOpts); diff --git a/packages/renderer-react/src/server.tsx b/packages/renderer-react/src/server.tsx index 789f6605f037..f1fbcd96805c 100644 --- a/packages/renderer-react/src/server.tsx +++ b/packages/renderer-react/src/server.tsx @@ -63,6 +63,7 @@ export async function getClientRootComponent(opts: { function Html({ children, loaderData, manifest }: any) { // TODO: 处理 head 标签,比如 favicon.ico 的一致性 // TODO: root 支持配置 + return ( @@ -78,6 +79,7 @@ function Html({ children, loaderData, manifest }: any) { __html: `Enable JavaScript to run this app.`, }} /> +
{children}