diff --git a/examples/ssg-basename/.umirc.ts b/examples/ssg-basename/.umirc.ts
new file mode 100644
index 000000000000..46e48430dff0
--- /dev/null
+++ b/examples/ssg-basename/.umirc.ts
@@ -0,0 +1,6 @@
+export default {
+ exportStatic: {},
+ ssr: {},
+ base: '/base/',
+ publicPath: '/base/', // 布署时需要布署在 base 文件夹下.
+};
diff --git a/examples/ssg-basename/package.json b/examples/ssg-basename/package.json
new file mode 100644
index 000000000000..516ef6602368
--- /dev/null
+++ b/examples/ssg-basename/package.json
@@ -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:*"
+ }
+}
diff --git a/examples/ssg-basename/src/layouts/index.tsx b/examples/ssg-basename/src/layouts/index.tsx
new file mode 100644
index 000000000000..6ec507a5a7fa
--- /dev/null
+++ b/examples/ssg-basename/src/layouts/index.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import { Outlet } from 'umi';
+
+const Layout = () => {
+ return (
+
+ );
+};
+
+export default Layout;
diff --git a/examples/ssg-basename/src/pages/about/index.tsx b/examples/ssg-basename/src/pages/about/index.tsx
new file mode 100644
index 000000000000..e5d53662f556
--- /dev/null
+++ b/examples/ssg-basename/src/pages/about/index.tsx
@@ -0,0 +1,24 @@
+import React, { useState } from 'react';
+import { Link } from 'umi';
+
+const About = () => {
+ const [num, setNum] = useState(0);
+
+ return (
+
+
+ ABOUT page
+
+
+
+
to home
+
+ );
+};
+
+export default About;
diff --git a/examples/ssg-basename/src/pages/index.tsx b/examples/ssg-basename/src/pages/index.tsx
new file mode 100644
index 000000000000..9385801fa5d1
--- /dev/null
+++ b/examples/ssg-basename/src/pages/index.tsx
@@ -0,0 +1,24 @@
+import React, { useState } from 'react';
+import { Link } from 'umi';
+
+const Home = () => {
+ const [num, setNum] = useState(0);
+ return (
+
+
+ HOME page
+
+
+
+
+
to about
+
+ );
+};
+
+export default Home;
diff --git a/examples/ssr-export-static-basename/.umirc.ts b/examples/ssr-basename/.umirc.ts
similarity index 63%
rename from examples/ssr-export-static-basename/.umirc.ts
rename to examples/ssr-basename/.umirc.ts
index 698110bb984d..6b596c65a98c 100644
--- a/examples/ssr-export-static-basename/.umirc.ts
+++ b/examples/ssr-basename/.umirc.ts
@@ -1,6 +1,5 @@
export default {
ssr: {},
exportStatic: {},
- hash: true,
- base: '/a/',
+ base: '/base/',
};
diff --git a/examples/ssr-export-static-basename/package.json b/examples/ssr-basename/package.json
similarity index 100%
rename from examples/ssr-export-static-basename/package.json
rename to examples/ssr-basename/package.json
diff --git a/examples/ssr-basename/plugins/mylogger.ts b/examples/ssr-basename/plugins/mylogger.ts
new file mode 100644
index 000000000000..8a7f85731fdd
--- /dev/null
+++ b/examples/ssr-basename/plugins/mylogger.ts
@@ -0,0 +1,37 @@
+import { writeFileSync } from 'fs';
+import { IApi } from 'umi';
+
+export default (api: IApi) => {
+ api.describe({
+ key: 'mylogger',
+ config: {
+ schema(joi) {
+ return joi.string();
+ },
+ },
+ enableBy: api.EnableBy.register,
+ });
+
+ api.registerMethod({
+ name: 'mylogger',
+ fn: (pathname, obj) => {
+ let cache = [];
+ const str = JSON.stringify(obj, function (key, value) {
+ if (typeof value === 'object' && value !== null) {
+ if (cache.indexOf(value) !== -1) {
+ // 移除
+ return;
+ }
+ // 收集所有的值
+ cache.push(value);
+ }
+ return value;
+ });
+ cache = null;
+
+ writeFileSync(pathname, str, () => {
+ console.log('write log finish');
+ });
+ },
+ });
+};
diff --git a/examples/ssr-basename/plugins/picker.ts b/examples/ssr-basename/plugins/picker.ts
new file mode 100644
index 000000000000..8b87eddef6c2
--- /dev/null
+++ b/examples/ssr-basename/plugins/picker.ts
@@ -0,0 +1,28 @@
+import type { IApi } from 'umi';
+
+export default (api: IApi) => {
+ api.describe({
+ key: 'routePicker',
+ config: {
+ schema({ zod }) {
+ return zod.array(zod.string());
+ },
+ },
+ enableBy: () => {
+ return api.name === 'dev';
+ },
+ });
+
+ console.log('插件 picker 注册');
+
+ api.onCheck(async () => {
+ // console.log('插件', api.appData.routes);
+ console.log('插件config', api.config);
+ api.config.routes = [
+ {
+ path: '/',
+ component: 'index',
+ },
+ ];
+ });
+};
diff --git a/examples/ssr-basename/src/layouts/index.tsx b/examples/ssr-basename/src/layouts/index.tsx
new file mode 100644
index 000000000000..6ec507a5a7fa
--- /dev/null
+++ b/examples/ssr-basename/src/layouts/index.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import { Outlet } from 'umi';
+
+const Layout = () => {
+ return (
+
+ );
+};
+
+export default Layout;
diff --git a/examples/ssr-basename/src/pages/about/form/index.tsx b/examples/ssr-basename/src/pages/about/form/index.tsx
new file mode 100644
index 000000000000..f94fd6d91678
--- /dev/null
+++ b/examples/ssr-basename/src/pages/about/form/index.tsx
@@ -0,0 +1,3 @@
+export default function AboutForm() {
+ return About Form
;
+}
diff --git a/examples/ssr-basename/src/pages/about/index.tsx b/examples/ssr-basename/src/pages/about/index.tsx
new file mode 100644
index 000000000000..e5d53662f556
--- /dev/null
+++ b/examples/ssr-basename/src/pages/about/index.tsx
@@ -0,0 +1,24 @@
+import React, { useState } from 'react';
+import { Link } from 'umi';
+
+const About = () => {
+ const [num, setNum] = useState(0);
+
+ return (
+
+
+ ABOUT page
+
+
+
+
to home
+
+ );
+};
+
+export default About;
diff --git a/examples/ssr-basename/src/pages/index.tsx b/examples/ssr-basename/src/pages/index.tsx
new file mode 100644
index 000000000000..9385801fa5d1
--- /dev/null
+++ b/examples/ssr-basename/src/pages/index.tsx
@@ -0,0 +1,24 @@
+import React, { useState } from 'react';
+import { Link } from 'umi';
+
+const Home = () => {
+ const [num, setNum] = useState(0);
+ return (
+
+
+ HOME page
+
+
+
+
+
to about
+
+ );
+};
+
+export default Home;
diff --git a/examples/ssr-export-static-basename/src/pages/about/index.tsx b/examples/ssr-export-static-basename/src/pages/about/index.tsx
deleted file mode 100644
index 28f2271f776b..000000000000
--- a/examples/ssr-export-static-basename/src/pages/about/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Link } from 'umi';
-
-const About = () => {
- return (
-
-
- about page
-
-
-
to home
-
- );
-};
-
-export default About;
diff --git a/examples/ssr-export-static-basename/src/pages/index.tsx b/examples/ssr-export-static-basename/src/pages/index.tsx
deleted file mode 100644
index 683660fae3ba..000000000000
--- a/examples/ssr-export-static-basename/src/pages/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Link } from 'umi';
-
-const Home = () => {
- return (
-
-
- home page
-
-
-
to about
-
- );
-};
-
-export default Home;
diff --git a/packages/preset-umi/src/features/exportStatic/exportStatic.ts b/packages/preset-umi/src/features/exportStatic/exportStatic.ts
index ae0d2f9be2b2..da12a00e7e63 100644
--- a/packages/preset-umi/src/features/exportStatic/exportStatic.ts
+++ b/packages/preset-umi/src/features/exportStatic/exportStatic.ts
@@ -60,13 +60,32 @@ function getExportHtmlData(routes: Record): 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 markup = await markupRender(location);
+ const [mainTpl, extraTpl = ''] = markup.split('