Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: router base not working for ssr and export static #12140

Merged
merged 9 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading