diff --git a/packages/preset-umi/templates/server.tpl b/packages/preset-umi/templates/server.tpl index ca38d51c5cfe..3ae0fd1c2699 100644 --- a/packages/preset-umi/templates/server.tpl +++ b/packages/preset-umi/templates/server.tpl @@ -4,7 +4,7 @@ import { createHistory as createClientHistory } from './core/history'; import { getPlugins as getClientPlugins } from './core/plugin'; import { ServerInsertedHTMLContext } from './core/serverInsertedHTMLContext'; import { PluginManager } from '{{{ umiPluginPath }}}'; -import createRequestHandler, { createMarkupGenerator } from '{{{ umiServerPath }}}'; +import createRequestHandler, { createMarkupGenerator, createUmiHandler, createUmiServerLoader } from '{{{ umiServerPath }}}'; let helmetContext; @@ -54,6 +54,8 @@ const createOpts = { ServerInsertedHTMLContext, }; const requestHandler = createRequestHandler(createOpts); +export const renderRoot = createUmiHandler(createOpts); +export const serverLoader = createUmiServerLoader(createOpts); export const _markupGenerator = createMarkupGenerator(createOpts); diff --git a/packages/server/src/ssr.ts b/packages/server/src/ssr.ts index 515e965dbf9d..13d62f7b4b94 100644 --- a/packages/server/src/ssr.ts +++ b/packages/server/src/ssr.ts @@ -1,7 +1,7 @@ import React, { ReactElement } from 'react'; import * as ReactDomServer from 'react-dom/server'; import { matchRoutes } from 'react-router-dom'; -import { Writable } from 'stream'; +import { Writable, Readable } from 'stream'; import type { IRoutesById } from './types'; interface RouteLoaders { @@ -235,6 +235,58 @@ export default function createRequestHandler( }; } +export function createUmiHandler( + opts: CreateRequestHandlerOptions, +) { + const jsxGeneratorDeferrer = createJSXGenerator(opts); + + return function (req: Request) { + return new Promise(async (resolve, reject) => { + const jsx = await jsxGeneratorDeferrer(new URL(req.url).pathname); + + if (!jsx) { + reject(new Error('no page resource')); + return; + } + + let buf = Buffer.alloc(0) + const writable = new Writable(); + + writable._write = (chunk, _encoding, next) => { + buf = Buffer.concat([buf, chunk]); + next(); + }; + + writable.on('finish', async () => { + resolve(Readable.from(buf)) + }); + + const stream = await ReactDomServer.renderToPipeableStream(jsx.element, { + bootstrapScripts: [jsx.manifest.assets['umi.js'] || '/umi.js'], + onShellReady() { + stream.pipe(writable); + }, + onError(err: any) { + reject(err); + }, + }); + }) + }; +} + +export function createUmiServerLoader( + opts: CreateRequestHandlerOptions, +) { + return async function (req: Request) { + const query = Object.fromEntries(new URL(req.url).searchParams) + // 切换路由场景下,会通过此 API 执行 server loader + return await executeLoader( + query.route, + opts.routesWithServerLoader, + ); + }; +} + function matchRoutesForSSR(reqUrl: string, routesById: IRoutesById) { return ( matchRoutes(createClientRoutes({ routesById }), reqUrl)?.map(