diff --git a/examples/ssr-demo/.umirc.ts b/examples/ssr-demo/.umirc.ts
index 4cc8d65ccf22..c905a4960ba7 100644
--- a/examples/ssr-demo/.umirc.ts
+++ b/examples/ssr-demo/.umirc.ts
@@ -9,7 +9,7 @@ export default {
scripts: [`https://a.com/b.js`],
ssr: {
builder: 'webpack',
- hydrateFromHtml: true,
+ hydrateFromRoot: false,
},
styles: [`body { color: red; }`, `https://a.com/b.css`],
diff --git a/packages/preset-umi/src/features/ssr/ssr.ts b/packages/preset-umi/src/features/ssr/ssr.ts
index 78e30b4f58ca..ede66b9d22a0 100644
--- a/packages/preset-umi/src/features/ssr/ssr.ts
+++ b/packages/preset-umi/src/features/ssr/ssr.ts
@@ -27,7 +27,7 @@ export default (api: IApi) => {
serverBuildPath: zod.string(),
platform: zod.string(),
builder: zod.enum(['esbuild', 'webpack']),
- hydrateFromHtml: zod.boolean(),
+ hydrateFromRoot: zod.boolean(),
})
.deepPartial();
},
diff --git a/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts b/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts
index 16ac6ef96676..1863f0b19b8c 100644
--- a/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts
+++ b/packages/preset-umi/src/features/tmpFiles/tmpFiles.ts
@@ -2,11 +2,11 @@ import { importLazy, lodash, winPath } from '@umijs/utils';
import { existsSync, readdirSync } from 'fs';
import { basename, dirname, join } from 'path';
import { RUNTIME_TYPE_FILE_NAME } from 'umi';
+import { getMarkupArgs } from '../../commands/dev/getMarkupArgs';
import { TEMPLATES_DIR } from '../../constants';
import { IApi } from '../../types';
import { getModuleExports } from './getModuleExports';
import { importsToStr } from './importsToStr';
-
const routesApi: typeof import('./routes') = importLazy(
require.resolve('./routes'),
);
@@ -496,16 +496,8 @@ if (process.env.NODE_ENV === 'development') {
}
return memo;
}, []);
- const {
- headScripts,
- scripts,
- styles,
- title,
- favicons,
- links,
- metas,
- ssr,
- } = api.config;
+ const { headScripts, scripts, styles, title, favicons, links, metas } =
+ await getMarkupArgs({ api });
api.writeTmpFile({
noPluginDir: true,
path: 'umi.server.ts',
@@ -531,9 +523,9 @@ if (process.env.NODE_ENV === 'development') {
favicons,
links,
metas,
+ scripts: scripts || [],
}),
- scripts: JSON.stringify(scripts || []),
- hydrateFromHtml: ssr?.hydrateFromHtml ?? true,
+ hydrateFromRoot: api.config.ssr?.hydrateFromRoot ?? false,
},
});
}
diff --git a/packages/preset-umi/templates/server.tpl b/packages/preset-umi/templates/server.tpl
index e52a07d8d61e..02a2a181f97f 100644
--- a/packages/preset-umi/templates/server.tpl
+++ b/packages/preset-umi/templates/server.tpl
@@ -52,8 +52,7 @@ const createOpts = {
createHistory,
ServerInsertedHTMLContext,
metadata: {{{metadata}}},
- scripts: {{{scripts}}},
- hydrateFromHtml: {{{hydrateFromHtml}}}
+ hydrateFromRoot: {{{hydrateFromRoot}}}
};
const requestHandler = createRequestHandler(createOpts);
diff --git a/packages/renderer-react/src/browser.tsx b/packages/renderer-react/src/browser.tsx
index 9553d5510b8f..9dd23058d2d8 100644
--- a/packages/renderer-react/src/browser.tsx
+++ b/packages/renderer-react/src/browser.tsx
@@ -100,7 +100,7 @@ export type RenderClientOpts = {
* ssr 是否从 html 根节点开始 hydrate
* @doc 默认 true,从 html 开始渲染,false 时从 app root 开始
*/
- hydrateFromHtml?: boolean;
+ hydrateFromRoot?: boolean;
/**
* 当前的路由配置
*/
@@ -336,13 +336,19 @@ const getBrowser = (
*/
export function renderClient(opts: RenderClientOpts) {
const rootElement = opts.rootElement || document.getElementById('root')!;
+
const Browser = getBrowser(opts, );
// 为了测试,直接返回组件
if (opts.components) return Browser;
if (opts.hydrate) {
+ // @ts-ignore
+ const loaderData = window.__UMI_LOADER_DATA__ || {};
+ // @ts-ignore
+ const metadata = window.__UMI_METADATA_LOADER_DATA__ || {};
+
ReactDOM.hydrateRoot(
document,
-
+
,
);
diff --git a/packages/renderer-react/src/html.tsx b/packages/renderer-react/src/html.tsx
index a7c408c0af44..c80adc363e70 100644
--- a/packages/renderer-react/src/html.tsx
+++ b/packages/renderer-react/src/html.tsx
@@ -94,15 +94,16 @@ export function Html({
/>
{children}
- {loaderData && (
-
- )}
+
+
{metadata?.scripts?.map((script: IScript, key: number) => {
const { content, ...rest } = normalizeScripts(script);
return (
diff --git a/packages/renderer-react/src/server.tsx b/packages/renderer-react/src/server.tsx
index 4b4b9c2a29d0..8a74c8f8ded8 100644
--- a/packages/renderer-react/src/server.tsx
+++ b/packages/renderer-react/src/server.tsx
@@ -50,7 +50,7 @@ export async function getClientRootComponent(opts: IRootComponentOptions) {
{rootContainer}
);
- if (opts.hydrateFromHtml) {
+ if (!opts.hydrateFromRoot) {
return {app};
} else {
return app;
diff --git a/packages/renderer-react/src/types.ts b/packages/renderer-react/src/types.ts
index a8ca103536f5..6a9320ec095b 100644
--- a/packages/renderer-react/src/types.ts
+++ b/packages/renderer-react/src/types.ts
@@ -51,7 +51,7 @@ export interface IRootComponentOptions {
loaderData: { [routeKey: string]: any };
manifest: any;
metadata?: IMetadata;
- hydrateFromHtml: boolean;
+ hydrateFromRoot: boolean;
}
export interface IHtmlProps {
diff --git a/packages/server/src/ssr.ts b/packages/server/src/ssr.ts
index 4315db0e760a..80c001fa574f 100644
--- a/packages/server/src/ssr.ts
+++ b/packages/server/src/ssr.ts
@@ -1,12 +1,11 @@
///
import type { RequestHandler } from '@umijs/bundler-utils/compiled/express';
-import mergeWith from 'lodash.mergewith';
import React, { ReactElement } from 'react';
import * as ReactDomServer from 'react-dom/server';
import { matchRoutes } from 'react-router-dom';
import { Writable } from 'stream';
import type {
- IOpts,
+ IMetadata,
IRoutesById,
IServerLoaderArgs,
MetadataLoader,
@@ -40,9 +39,8 @@ interface CreateRequestHandlerOptions extends CreateRequestServerlessOptions {
createHistory: (opts: any) => any;
helmetContext?: any;
ServerInsertedHTMLContext: React.Context;
- metadata: IOpts;
- scripts: IOpts['scripts'];
- hydrateFromHtml: boolean;
+ metadata: IMetadata;
+ hydrateFromRoot: boolean;
}
interface IExecLoaderOpts {
@@ -112,7 +110,7 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
}
const loaderData: Record = {};
- let metadata: Record = {};
+ // let metadata: Record = {};
await Promise.all(
matches
.filter((id: string) => routes[id].hasServerLoader)
@@ -134,17 +132,13 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
serverLoaderArgs,
serverLoaderData: loaderData[id],
});
-
- metadata = mergeWith(
- metadataLoaderData,
- opts.metadata,
- (pre, next) => {
- if (Array.isArray(pre) || Array.isArray(next)) {
- return Array.prototype.concat.call(pre || [], next || []);
- }
- },
- );
- metadata.scripts = opts.scripts;
+ Object.entries(metadataLoaderData).forEach(([k, v]) => {
+ if (Array.isArray(v)) {
+ opts.metadata[k] = (opts.metadata[k] || []).concat(v);
+ } else {
+ opts.metadata[k] = v;
+ }
+ });
}
resolve();
}),
@@ -162,8 +156,8 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
location: url,
manifest,
loaderData,
- metadata,
- hydrateFromHtml: opts.hydrateFromHtml,
+ metadata: opts.metadata,
+ hydrateFromRoot: opts.hydrateFromRoot,
};
const element = (await opts.getClientRootComponent(
@@ -533,18 +527,10 @@ async function executeLoader(params: IExecLoaderOpts) {
}
async function executeMetadataLoader(params: IExecMetaLoaderOpts) {
- const {
- routesWithServerLoader,
- routeKey,
- serverLoaderArgs,
- serverLoaderData,
- } = params;
+ const { routesWithServerLoader, routeKey, serverLoaderData } = params;
const mod = await routesWithServerLoader[routeKey]();
if (!mod.serverLoader || typeof mod.serverLoader !== 'function') {
return;
}
- return (mod.metadataLoader satisfies MetadataLoader)(
- serverLoaderData,
- serverLoaderArgs,
- );
+ return (mod.metadataLoader satisfies MetadataLoader)(serverLoaderData);
}
diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts
index 47f08adcec67..55621ff07bf4 100644
--- a/packages/server/src/types.ts
+++ b/packages/server/src/types.ts
@@ -62,11 +62,12 @@ export interface IMetadata {
*/
lang?: string;
metas?: IMetaTag[];
- headScripts?: IOpts['headScripts'];
- links?: IOpts['links'];
+ headScripts?: (Record | string)[];
+ links?: Record[];
styles?: string[];
favicons?: string[];
- scripts?: IOpts['scripts'];
+ scripts?: (Record | string)[];
+ [key: string]: any;
}
export type MetadataLoader = (
serverLoaderData: T,