Qiankun多个子应用共存
起因
最初接触 umi 微前端 时, 感觉这个就是两种子应用的加载方式
后来在网上看到大佬的发的这个文章 探索微前端的场景极限, 才发现这个实现不简单, 引用了大佬的话:
在应用
A
中通过调用loadMicroApp(B)
的方式唤起微应用B
,然后在微应用B
中通过loadMicroApp(C)
的方式唤起微应用C
,通过这样的调用链路即可很完美的完成产品上的诉求。但是现实情况往往没有那么简单,前面提到过,若想要
loadMicroApp API
能符合预期的运行,我们需要确保被加载的微应用是不含自己的路由系统,否则会出现多个应用间路由系统互相抢占/冲突
的情况。这种场景下,我们其实只需要确保微应用的路由系统不会干扰到全局的
URL
系统即可。幸运的是react-router
的 memory history 模式很好的解决了这一问题。
仓库地址
项目搭建
使用 lerna 创建一个 monorepo
项目。
- 命令行执行
npx lerna init
, 初始化项目
开启 npm workspaces
lerna.json
中添加下面配置1
2
3
4
5{
"npmClient": "npm",
"packages": ["packages/*"],
... // 其他配置
}然后在根目录下创建 packages 目录 (注意:名称要与上面配置的 packages/* 目录名称一致)
全局安装包
在 根目录 下安装 react-router
1
npm install react-router
在 根目录 下安装 antd
1
npm install antd
在 根目录 下安装 less
1
npm install less -D
在 根目录 下安装 [格式化工具](可选), 详见
.prettierrc
文件1
npm install prettier-plugin-organize-imports prettier-plugin-packagejson -D
进入 packages
创建主应用 app
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建主应用 app
npm create vite@latest
# Project name
app
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/app/vite.config.ts
文件中配置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14export default defineConfig({
server: {
port: 8000,
},
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
... // 其他配置
});
进入 packages
创建子应用 app1
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建子应用 app1
npm create vite@latest
# Project name
app1
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/app1/vite.config.ts
文件中配置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14export default defineConfig({
server: {
port: 8001,
},
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
... // 其他配置
});
进入 packages
创建子应用 app2
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建子应用 app2
npm create vite@latest
# Project name
app2
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/app2/vite.config.ts
文件中配置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14export default defineConfig({
server: {
port: 8002,
},
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
... // 其他配置
});
进入 packages
创建子应用 app3
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建子应用 app3
npm create vite@latest
# Project name
app3
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/app3/vite.config.ts
文件中配置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14export default defineConfig({
server: {
port: 8003,
},
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
... // 其他配置
});
配置项目
配置主应用 app
在
app
项目中安装 qiankun1
2# 在根目录下执行
npm install qiankun -w=app文件目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14├── src
├── layout
|── index.less
├── index.tsx
├── microApp.tsx
├── pages
├── home
├── index.tsx
├── 404
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app/src/layout/microApp.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import { LoadableApp, loadMicroApp, MicroApp } from 'qiankun';
import { useEffect, useRef } from 'react';
interface Props {
/** 子应用参数 */
microapp: LoadableApp<any>;
}
/**
* 微应用
*/
const MicroApp = (props: Props) => {
const { microapp } = props;
const mocroInstanceRef = useRef<MicroApp>(undefined);
useEffect(() => {
const container = `#${microapp.container}`;
mocroInstanceRef.current = loadMicroApp({ ...microapp, container });
return () => {
mocroInstanceRef.current?.unmount();
};
}, [microapp]);
return (
<div
id={microapp?.container || 'subapp'}
style={{ width: '100%', height: '100%' }}></div>
);
};
export default MicroApp;修改
packages/app/src/layout/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115import { Layout, Menu } from 'antd';
import { MenuItemType } from 'antd/es/menu/interface';
import { LoadableApp } from 'qiankun';
import { useEffect, useMemo, useState } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router';
import './index.less';
import MicroApp from './microApp';
const { Header, Content, Footer } = Layout;
type MenuItemProps = {
/** 菜单路径 */
path?: string;
/** 子应用参数 */
microapp?: LoadableApp<any>;
} & MenuItemType;
/** 菜单数据 */
const menuItems: MenuItemProps[] = [
{
key: '1',
label: 'Home',
path: '/home',
},
{
key: '2',
label: 'app1',
path: '/app1',
microapp: {
name: 'app1',
container: 'app1',
entry: 'http://localhost:8001',
},
},
{
key: '3',
label: 'app2',
path: '/app2',
microapp: {
name: 'app2',
container: 'app2',
entry: 'http://localhost:8002',
},
},
{
key: '4',
label: 'app3',
path: '/app3',
microapp: {
name: 'app3',
container: 'app3',
entry: 'http://localhost:8003',
},
},
];
/** 基础布局 */
const BaseLayout = () => {
const nativate = useNavigate();
const location = useLocation();
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
/** 菜单点击事件 */
const handleMenuClick = (item: any) => {
setSelectedKeys([item.key]);
nativate(item.item?.props.path);
};
/** 菜单选中项 */
const selectObj = useMemo(() => {
return (
menuItems.find((item) => item.key === selectedKeys[0]) || undefined
);
}, [selectedKeys]);
useEffect(() => {
const path = location.pathname;
const selectItem = menuItems.find((item) => item.path === path);
if (!selectItem?.key) return;
setSelectedKeys([selectItem?.key + '' || '1']);
}, [location]);
return (
<Layout>
<Header className="header">
<div className="logo">Qiankun</div>
<Menu
theme="dark"
mode="horizontal"
className="menu"
items={menuItems}
selectedKeys={selectedKeys}
onClick={handleMenuClick}
/>
</Header>
<Content className="content">
<div className="outlet">
{selectObj?.microapp ? (
<MicroApp microapp={selectObj.microapp} />
) : (
<Outlet />
)}
</div>
</Content>
<Footer className="footer">
Demo ©{new Date().getFullYear()} Created Mr.XiaoSi
</Footer>
</Layout>
);
};
export default BaseLayout;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42import { lazy } from 'react';
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router';
/** 路由配置 */
const routes = createBrowserRouter([
{
path: '/',
element: <Navigate to="/home" />,
},
{
path: '/',
Component: lazy(() => import('./layout')),
children: [
{
path: '/home',
Component: lazy(() => import('./pages/home')),
},
{
path: '/app1/*',
element: <></>,
},
{
path: '/app2/*',
element: <></>,
},
{
path: '/app3/*',
element: <></>,
},
],
},
{
path: '*',
Component: lazy(() => import('./pages/404')),
},
]);
function App() {
return <RouterProvider router={routes} />;
}
export default App;修改
packages/app/vite.config.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 8000,
},
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
});
配置子应用 app1
在
app1
项目中安装 qiankun、vite-plugin-qiankun、postcss-prefix-selector1
2
3
4# 在根目录下执行
npm install qiankun -w=app1
npm install vite-plugin-qiankun -D -w=app1
npm install postcss-prefix-selector -D -w=app1文件目录
1
2
3
4
5
6
7
8
9
10
11
12
13├── src
├── layout
|── index.less
├── index.tsx
├── pages
├── dashboard
├── index.tsx
├── user
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app1/src/layout/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router';
const { Sider, Content } = Layout;
import { MenuItemType } from 'antd/es/menu/interface';
import { useState } from 'react';
import './index.less';
/** 菜单数据 */
const menuItems: (MenuItemType & { path: string })[] = [
{
key: 'dashboard',
label: 'App1 Dashboard',
path: '/',
},
{
key: 'user',
label: 'User',
path: '/user',
},
];
const BaseLayout = () => {
const nativate = useNavigate();
const [activeKey, setActiveKey] = useState<any[]>([menuItems[0].key]);
/** 菜单点击事件 */
const handleMenuClick = (item: any) => {
nativate(item.item?.props?.path);
setActiveKey([item.key]);
};
return (
<Layout>
<Sider width={200}>
<Menu
mode="inline"
items={menuItems}
selectedKeys={activeKey}
onClick={handleMenuClick}
/>
</Sider>
<Content>
<Outlet />
</Content>
</Layout>
);
};
export default BaseLayout;修改
packages/app1/src/pages/dashboard/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50import { Button, Drawer } from 'antd';
import { loadMicroApp, MicroApp } from 'qiankun';
import { useRef, useState } from 'react';
const Dashboard = () => {
const microInstanceRef = useRef<MicroApp>(undefined);
const [open, setOpen] = useState(false);
/** 点击事件 */
const handleClick = () => {
setOpen(true);
};
/** 弹窗打开关闭事件回调 */
const handleAfterOpenChange = (flag) => {
if (flag) {
microInstanceRef.current = loadMicroApp({
name: 'app2',
container: '#app2',
entry: 'http://localhost:8002',
props: { routerType: 'memory' },
});
} else {
setOpen(false);
microInstanceRef.current?.unmount();
}
};
return (
<>
<div>
App1 Dashboard
<div className="test">使用样式隔离</div>
<Button type="primary" onClick={handleClick}>
打开 app2
</Button>
</div>
<Drawer
width={600}
open={open}
onClose={() => setOpen(false)}
afterOpenChange={handleAfterOpenChange}>
<div id="app2" style={{ height: '100%' }}></div>
</Drawer>
</>
);
};
export default Dashboard;修改
packages/app1/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import { lazy } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router';
/** 路由配置 */
const router = createBrowserRouter(
[
{
path: '/',
Component: lazy(() => import('./layout')),
children: [
{
path: '/',
Component: lazy(() => import('./pages/dashboard')),
},
{
path: '/user',
Component: lazy(() => import('./pages/user')),
},
],
},
],
{ basename: '/app1' }
);
function App() {
return <RouterProvider router={router} />;
}
export default App;修改
packages/app1/src/main.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import { createRoot } from 'react-dom/client';
import {
QiankunProps,
qiankunWindow,
renderWithQiankun,
} from 'vite-plugin-qiankun/dist/helper';
import App from './App.tsx';
import './index.css';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app =
container || (document.getElementById('root') as HTMLDivElement);
app.setAttribute('data-qiankun-app1', 'true');
createRoot(app).render(<App />);
};
/** Qiankun 生命周期钩子 */
const qiankun = () => {
renderWithQiankun({
bootstrap() {
console.warn('App bootstrap');
},
async mount(props: QiankunProps) {
console.warn('App mount');
render(props.container);
},
update: (props: QiankunProps) => {
console.warn('App update', props);
},
unmount: (props: QiankunProps) => {
console.warn('App unmount', props);
},
});
};
// 检查是否在 Qiankun 环境中
console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);
if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
else render();修改
packages/app1/vite.config.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import react from '@vitejs/plugin-react-swc';
import prefixer from 'postcss-prefix-selector';
import { defineConfig } from 'vite';
import qiankun from 'vite-plugin-qiankun';
// https://vitejs.dev/config/
// https://cloud.tencent.com/developer/article/2138139
export default ({ mode }) => {
const useDevMode = mode === 'development';
const host = '127.0.0.1';
const port = 8001;
const subAppName = 'app1';
const base = useDevMode
? `http://${host}:${port}/${subAppName}`
: `/${subAppName}`;
return defineConfig({
base,
server: {
port,
cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
},
plugins: [
// 在开发模式下需要把react()关掉
// https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
...[useDevMode ? [] : [react()]],
qiankun(subAppName, { useDevMode }),
],
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
postcss: {
plugins: [
prefixer({
prefix: `[data-qiankun-${subAppName}]`,
transform(prefix, selector, prefixedSelector, filePath, rule) {
if (selector.match(/^(html|body)/)) {
return selector.replace(/^([^\s]*)/, `$1 ${prefix}`);
}
if (filePath.match(/node_modules/)) {
return selector; // Do not prefix styles imported from node_modules
}
const annotation = rule.prev();
if (
annotation?.type === 'comment' &&
annotation.text.trim() === 'no-prefix'
) {
return selector; // Do not prefix style rules that are preceded by: /* no-prefix */
}
return prefixedSelector;
},
}),
],
},
},
});
};
配置子应用 app2
在
app2
项目中安装 vite-plugin-qiankun、postcss-prefix-selector1
2
3# 在根目录下执行
npm install vite-plugin-qiankun -D -w=app2
npm install postcss-prefix-selector -D -w=app2文件目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14├── src
├── layout
|── index.less
├── index.tsx
├── pages
├── dashboard
|── index.less
├── index.tsx
├── user
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app2/src/layout/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router';
const { Sider, Content } = Layout;
import { MenuItemType } from 'antd/es/menu/interface';
import { useState } from 'react';
import './index.less';
/** 菜单数据 */
const menuItems: (MenuItemType & { path: string })[] = [
{
key: 'dashboard',
label: 'App2 Dashboard',
path: '/',
},
{
key: 'user',
label: 'User',
path: '/user',
},
];
const BaseLayout = () => {
const nativate = useNavigate();
const [activeKey, setActiveKey] = useState<any[]>([menuItems[0].key]);
/** 菜单点击事件 */
const handleMenuClick = (item: any) => {
nativate(item.item?.props?.path);
setActiveKey([item.key]);
};
return (
<Layout>
<Sider width={200}>
<Menu
mode="inline"
items={menuItems}
selectedKeys={activeKey}
onClick={handleMenuClick}
/>
</Sider>
<Content>
<Outlet />
</Content>
</Layout>
);
};
export default BaseLayout;修改
packages/app2/src/pages/dashboard/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import { Button } from 'antd';
const Dashboard = () => {
return (
<div>
App2 Dashboard
<div className="test">使用样式隔离</div>
<Button>测试组件库样式</Button>
</div>
);
};
export default Dashboard;修改
packages/app2/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import { lazy, useMemo } from 'react';
import {
createBrowserRouter,
createHashRouter,
createMemoryRouter,
RouteObject,
RouterProvider,
} from 'react-router';
/** 路由配置 */
const router: RouteObject[] = [
{
path: '/',
Component: lazy(() => import('./layout')),
children: [
{
path: '/',
Component: lazy(() => import('./pages/dashboard')),
},
{
path: '/user',
Component: lazy(() => import('./pages/user')),
},
],
},
];
function App({ args }: any) {
const { routerType = 'brower' } = args;
const mergeRouter = useMemo(() => {
switch (routerType) {
case 'hash':
return createHashRouter(router, { basename: '/app2' });
case 'memory':
return createMemoryRouter(router, {
basename: '/app2',
initialEntries: ['/app2', '/app2/user'],
initialIndex: 0,
});
default:
return createBrowserRouter(router, { basename: '/app2' });
}
}, [routerType]);
return <RouterProvider router={mergeRouter} />;
}
export default App;修改
packages/app2/src/main.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import { createRoot } from 'react-dom/client';
import {
QiankunProps,
qiankunWindow,
renderWithQiankun,
} from 'vite-plugin-qiankun/dist/helper';
import App from './App.tsx';
import './index.css';
/** 渲染函数 */
const render = (container?: HTMLElement, args = {}) => {
const app =
container || (document.getElementById('root') as HTMLDivElement);
app.setAttribute('data-qiankun-app2', 'true');
createRoot(app).render(<App args={args} />);
};
/** Qiankun 生命周期钩子 */
const qiankun = () => {
renderWithQiankun({
bootstrap() {
console.warn('App bootstrap');
},
async mount(props: QiankunProps) {
console.warn('App mount', props);
render(props.container, props);
},
update: (props: QiankunProps) => {
console.warn('App update', props);
},
unmount: (props: QiankunProps) => {
console.warn('App unmount', props);
},
});
};
// 检查是否在 Qiankun 环境中
console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);
if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
else render();修改
packages/app2/vite.config.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65import react from '@vitejs/plugin-react-swc';
import prefixer from 'postcss-prefix-selector';
import { defineConfig } from 'vite';
import qiankun from 'vite-plugin-qiankun';
// https://vitejs.dev/config/
// https://cloud.tencent.com/developer/article/2138139
export default ({ mode }) => {
const useDevMode = mode === 'development';
const host = '127.0.0.1';
const port = 8002;
const subAppName = 'app2';
const base = useDevMode
? `http://${host}:${port}/${subAppName}`
: `/${subAppName}`;
return defineConfig({
base,
server: {
port,
cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
},
plugins: [
// 在开发模式下需要把react()关掉
// https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
...[useDevMode ? [] : [react()]],
qiankun(subAppName, { useDevMode }),
],
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
postcss: {
plugins: [
prefixer({
prefix: `[data-qiankun-${subAppName}]`,
transform(prefix, selector, prefixedSelector, filePath, rule) {
if (selector.match(/^(html|body)/)) {
return selector.replace(/^([^\s]*)/, `$1 ${prefix}`);
}
if (filePath.match(/node_modules/)) {
return selector; // Do not prefix styles imported from node_modules
}
const annotation = rule.prev();
if (
annotation?.type === 'comment' &&
annotation.text.trim() === 'no-prefix'
) {
return selector; // Do not prefix style rules that are preceded by: /* no-prefix */
}
return prefixedSelector;
},
}),
],
},
},
});
};
配置子应用 app3
在
app3
项目中安装 vite-plugin-qiankun1
2# 在根目录下执行
npm install vite-plugin-qiankun -D -w=app3文件目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14├── src
├── layout
|── index.less
├── index.tsx
├── pages
├── dashboard
|── index.less
├── index.tsx
├── user
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app3/src/layout/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router';
const { Sider, Content } = Layout;
import { MenuItemType } from 'antd/es/menu/interface';
import { useState } from 'react';
import './index.less';
/** 菜单数据 */
const menuItems: (MenuItemType & { path: string })[] = [
{
key: 'dashboard',
label: 'App3 Dashboard',
path: '/',
},
{
key: 'user',
label: 'User',
path: '/user',
},
];
const BaseLayout = () => {
const nativate = useNavigate();
const [activeKey, setActiveKey] = useState<any[]>([menuItems[0].key]);
/** 菜单点击事件 */
const handleMenuClick = (item: any) => {
nativate(item.item?.props?.path);
setActiveKey([item.key]);
};
return (
<Layout>
<Sider width={200}>
<Menu
mode="inline"
items={menuItems}
selectedKeys={activeKey}
onClick={handleMenuClick}
/>
</Sider>
<Content>
<Outlet />
</Content>
</Layout>
);
};
export default BaseLayout;修改
packages/app3/src/pages/dashboard/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import { Button } from 'antd';
const Dashboard = () => {
return (
<div>
App3 Dashboard
<div className="test">使用样式隔离</div>
<Button>测试组件库样式</Button>
</div>
);
};
export default Dashboard;修改
packages/app3/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import { ConfigProvider } from 'antd';
import { lazy } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router';
/** 路由配置 */
const router = createBrowserRouter(
[
{
path: '/',
Component: lazy(() => import('./layout')),
children: [
{
path: '/',
Component: lazy(() => import('./pages/dashboard')),
},
{
path: '/user',
Component: lazy(() => import('./pages/user')),
},
],
},
],
{ basename: '/app3' }
);
function App() {
return (
<ConfigProvider prefixCls="app3">
<RouterProvider router={router} />
</ConfigProvider>
);
}
export default App;修改
packages/app3/src/main.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import { createRoot } from 'react-dom/client';
import {
QiankunProps,
qiankunWindow,
renderWithQiankun,
} from 'vite-plugin-qiankun/dist/helper';
import App from './App.tsx';
import './index.css';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app =
container || (document.getElementById('root') as HTMLDivElement);
createRoot(app).render(<App />);
};
/** Qiankun 生命周期钩子 */
const qiankun = () => {
renderWithQiankun({
bootstrap() {
console.warn('App bootstrap');
},
async mount(props: QiankunProps) {
console.warn('App mount');
render(props.container);
},
update: (props: QiankunProps) => {
console.warn('App update', props);
},
unmount: (props: QiankunProps) => {
console.warn('App unmount', props);
},
});
};
// 检查是否在 Qiankun 环境中
console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);
if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
else render();修改
packages/app3/vite.config.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import qiankun from 'vite-plugin-qiankun';
// https://vitejs.dev/config/
// https://cloud.tencent.com/developer/article/2138139
export default ({ mode }) => {
const useDevMode = mode === 'development';
const host = '127.0.0.1';
const port = 8003;
const subAppName = 'app3';
const base = useDevMode
? `http://${host}:${port}/${subAppName}`
: `/${subAppName}`;
return defineConfig({
base,
server: {
port,
cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
},
plugins: [
// 在开发模式下需要把react()关掉
// https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
...[useDevMode ? [] : [react()]],
qiankun(subAppName, { useDevMode }),
],
css: {
preprocessorOptions: {
less: {
math: 'always',
javascriptEnabled: true,
},
},
},
});
};