Skip to content

Commit

Permalink
fix: router base not working for ssr and export static (#12140)
Browse files Browse the repository at this point in the history
* fix: ssr exportStatic not handle with base

* fix: update pnpm-lock.yaml

* fix: 修复 getClientRootComponent 方法. 增加 ssr basename 案例

* fix: 解决 exportStatic 文件冲突

* fix: 修复合并冲突导致的问题

* fix: 完善例子

* fix: 完善例子
  • Loading branch information
jaykou25 authored Sep 4, 2024
1 parent 8ae268e commit 6665b61
Show file tree
Hide file tree
Showing 17 changed files with 205 additions and 7 deletions.
6 changes: 6 additions & 0 deletions examples/ssg-basename/.umirc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
exportStatic: {},
ssr: {},
base: '/base/',
publicPath: '/base/', // 布署时需要布署在 base 文件夹下.
};
14 changes: 14 additions & 0 deletions examples/ssg-basename/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@example/ssg-basename",
"private": true,
"description": "该案例用于测试 ssg 预渲染. 当 umi 配置表中设置了 base 后, ssg 输出的预渲染页面中的 a 标签需要有正确的 base 前缀",
"scripts": {
"build": "umi build",
"dev": "umi dev",
"setup": "umi setup",
"start": "npm run dev"
},
"dependencies": {
"umi": "workspace:*"
}
}
14 changes: 14 additions & 0 deletions examples/ssg-basename/src/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { Outlet } from 'umi';

const Layout = () => {
return (
<div>
<div style={{ marginBottom: '10px' }}>HEADER</div>
<Outlet />
</div>
);
};

export default Layout;
24 changes: 24 additions & 0 deletions examples/ssg-basename/src/pages/about/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Link } from 'umi';

const About = () => {
const [num, setNum] = useState(0);

return (
<div>
<div>
<strong>ABOUT</strong> page
<button
style={{ marginLeft: '5px' }}
onClick={() => setNum((val) => val + 1)}
>
couts: {num}
</button>
</div>
<br />
<Link to="/">to home</Link>
</div>
);
};

export default About;
24 changes: 24 additions & 0 deletions examples/ssg-basename/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Link } from 'umi';

const Home = () => {
const [num, setNum] = useState(0);
return (
<div>
<div>
<strong>HOME</strong> page
<button
style={{ marginLeft: '5px' }}
onClick={() => setNum((val) => val + 1)}
>
couts: {num}
</button>
</div>

<br />
<Link to="/about">to about</Link>
</div>
);
};

export default Home;
5 changes: 5 additions & 0 deletions examples/ssr-basename/.umirc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
ssr: {},
exportStatic: {},
base: '/base/',
};
14 changes: 14 additions & 0 deletions examples/ssr-basename/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@example/ssr-basename",
"private": true,
"description": "该案例用于测试 SSR 中带 basename 的情况. 在 dev 或 build 下, 服务端需要返回正确的 html 片段, 比如 a 标签需要有正确的 base 前缀",
"scripts": {
"build": "umi build",
"dev": "umi dev",
"setup": "umi setup",
"start": "npm run dev"
},
"dependencies": {
"umi": "workspace:*"
}
}
14 changes: 14 additions & 0 deletions examples/ssr-basename/src/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { Outlet } from 'umi';

const Layout = () => {
return (
<div>
<div style={{ marginBottom: '10px' }}>HEADER</div>
<Outlet />
</div>
);
};

export default Layout;
24 changes: 24 additions & 0 deletions examples/ssr-basename/src/pages/about/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Link } from 'umi';

const About = () => {
const [num, setNum] = useState(0);

return (
<div>
<div>
<strong>ABOUT</strong> page
<button
style={{ marginLeft: '5px' }}
onClick={() => setNum((val) => val + 1)}
>
couts: {num}
</button>
</div>
<br />
<Link to="/">to home</Link>
</div>
);
};

export default About;
24 changes: 24 additions & 0 deletions examples/ssr-basename/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Link } from 'umi';

const Home = () => {
const [num, setNum] = useState(0);
return (
<div>
<div>
<strong>HOME</strong> page
<button
style={{ marginLeft: '5px' }}
onClick={() => setNum((val) => val + 1)}
>
couts: {num}
</button>
</div>

<br />
<Link to="/about">to about</Link>
</div>
);
};

export default Home;
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ function getExportHtmlData(routes: Record<string, IRoute>): IExportHtmlItem[] {
async function getPreRenderedHTML(api: IApi, htmlTpl: string, path: string) {
const {
exportStatic: { ignorePreRenderError = false },
base,
} = api.config;
markupRender ??= require(absServerBuildPath(api))._markupGenerator;

try {
const html = await markupRender(path);
const location = `${base.endsWith('/') ? base.slice(0, -1) : base}${path}`;
const html = await markupRender(location);
logger.info(`Pre-render for ${path}`);
return html;
} catch (err) {
Expand Down
1 change: 1 addition & 0 deletions packages/preset-umi/src/features/tmpFiles/tmpFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ if (process.env.NODE_ENV === 'development') {
__INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
),
mountElementId,
basename: api.config.base,
},
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/preset-umi/templates/server.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ const createOpts = {
ServerInsertedHTMLContext,
htmlPageOpts: {{{htmlPageOpts}}},
__INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {{{__INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED}}},
mountElementId: '{{{mountElementId}}}'

mountElementId: '{{{mountElementId}}}',
basename: '{{{basename}}}'
};
const requestHandler = createRequestHandler(createOpts);
/**
Expand Down
7 changes: 6 additions & 1 deletion packages/renderer-react/src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IRootComponentOptions } from './types';

// Get the root React component for ReactDOMServer.renderToString
export async function getClientRootComponent(opts: IRootComponentOptions) {
const basename = '/';
const basename = opts.basename || '/';
const components = { ...opts.routeComponents };
// todo 参数对齐
const clientRoutes = createClientRoutes({
Expand All @@ -23,7 +23,12 @@ export async function getClientRootComponent(opts: IRootComponentOptions) {
routes: clientRoutes,
},
});

let rootContainer = (
// 这里的 location 需要包含 basename, 否则会影响 StaticRouter 的匹配.
// 由于 getClientRootComponent 方法会同时用于 ssr 和 ssg, 所以在调用该方法时需要注意传入的 location 是否包含 basename.
// 1. 在用于 ssr 时传入的 location 来源于 request.url, 它是包含 basename 的, 所以没有问题.
// 2. 但是在用于 ssg 时(static export), 需要注意传入的 locaiton 要拼接上 basename.
<StaticRouter basename={basename} location={opts.location}>
<Routes />
</StaticRouter>
Expand Down
1 change: 1 addition & 0 deletions packages/renderer-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export interface IRootComponentOptions extends IHtmlHydrateOptions {
location: string;
loaderData: { [routeKey: string]: any };
manifest: any;
basename?: string;
}

export interface IHtmlProps extends IHtmlHydrateOptions {
Expand Down
20 changes: 17 additions & 3 deletions packages/server/src/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface CreateRequestHandlerOptions extends CreateRequestServerlessOptions {
pureHtml: boolean;
};
mountElementId: string;
basename?: string;
}

interface IExecLoaderOpts {
Expand Down Expand Up @@ -93,6 +94,7 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
getRoutes,
createHistory,
sourceDir,
basename,
} = opts;

// make import { history } from 'umi' work
Expand All @@ -110,7 +112,8 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
},
});

const matches = matchRoutesForSSR(url, routes);
const matches = matchRoutesForSSR(url, routes, basename);

if (matches.length === 0) {
return;
}
Expand Down Expand Up @@ -169,6 +172,7 @@ function createJSXGenerator(opts: CreateRequestHandlerOptions) {
__INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:
opts.__INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
mountElementId: opts.mountElementId,
basename,
};
const element = (await opts.getClientRootComponent(
context,
Expand Down Expand Up @@ -603,9 +607,19 @@ export function createAppRootElement(opts: CreateRequestHandlerOptions) {
};
}

function matchRoutesForSSR(reqUrl: string, routesById: IRoutesById) {
function matchRoutesForSSR(
reqUrl: string,
routesById: IRoutesById,
basename?: string,
) {
// react-router-dom 在 v6.4.0 版本上增加了对 basename 结尾为斜杠的支持
// 目前 @umijs/server 依赖的 react-router-dom 版本为 v6.3.0
// 如果传入的 basename 结尾带斜杠, 比如 '/base/', 则会匹配不到.
// 日后如果依赖的版本升级, 此段代码可以删除.
const _basename = basename?.endsWith('/') ? basename.slice(0, -1) : basename;

return (
matchRoutes(createClientRoutes({ routesById }), reqUrl)?.map(
matchRoutes(createClientRoutes({ routesById }), reqUrl, _basename)?.map(
(route: any) => route.route.id,
) || []
);
Expand Down
12 changes: 12 additions & 0 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 6665b61

Please sign in to comment.