lerna多包项目
简介
Lerna
是一个快速、领先的构建系统,用于管理和发布来 自同一源码仓库的多个JavaScript/TypeScript
软件包。
lerna8 + npm + workspaces + umi
包版本
1 | "node": "^20.18.0", |
开启 npm workspaces
命令行执行
npx lerna init
, 初始化项目lerna.json
中添加下面配置1
2
3
4
5{
"npmClient": "npm",
"packages": ["packages/*"],
... // 其他配置
}package.json
中添加下面配置1
2
3{
"workspaces": ["packages/*"]
}然后在根目录下创建
packages
目录 (注意:名称要与上面配置的packages/*
目录名称一致)
注意:packages
下的项目中每个包的 package.json
都要配置 name
,否则会报错
进入 packages
创建主应用 app
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建主应用 app
npx create-umi@latest
# What's the target folder name?
app
# Pick Umi App Template
Ant Design Pro
# Pick Npm Client
npm
# Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件)
taobao在
packages/app/package.json
中配置name
1
2
3
4{
"name": "app",
... // 其他配置
}
进入 packages
创建子应用 demo1
, demo2
依次执行下面命令(这里只演示
demo1
,demo2
创建同下)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建子应用 demo1
npx create-umi@latest
# What's the target folder name?
demo1
# Pick Umi App Template
Simple App
# Pick Npm Client
npm
# Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件, 不需要可以删除)
taobao在
packages/app/package.json
中配置name
1
2
3
4{
"name": "demo1",
... // 其他配置
}
设置应用的启动端口
在根目录下安装
cross-env
包 (不是项目根目录)1
2
3npm i cross-env -D -w=app -w=demo1 -w=demo2
# 或者
npm i cross-env -D --workspace=app --workspace=demo1 --workspace=demo2配置主应用
app
启动端口,在packages/app/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8000 max dev",
... // 其他配置
}
}配置子应用
demo1
启动端口,在packages/demo1/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8001 umi dev",
... // 其他配置
}
}配置子应用
demo2
启动端口,在packages/demo2/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8002 umi dev",
... // 其他配置
}
}启动应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages/app 目录
cd ./packages/app
# 启动主应用
npm run dev
# 进入 packages/demo1 目录
cd ./packages/demo1
# 启动子应用
npm run dev
# 进入 packages/demo2 目录
cd ./packages/demo2
# 启动子应用
npm run dev1
2
3
4
5
6
7
8# 根目录下启动主应用
npm run dev -w=app
# 根目录下启动子应用
npm run dev -w=demo1
# 根目录下启动子应用
npm run dev -w=demo2
主应用链接子应用
在主应用
./packages/app/.umirc.ts
中添加下面内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15export default {
routes: [
{ path: '/', redirect: '/home' },
{ name: '首页', path: '/home', component: './Home' },
{ name: '权限演示', path: '/access', component: './Access' },
{ name: ' CRUD 示例', path: '/table', component: './Table' },
// 新增微应用示例
{ name: 'Demo1', path: '/demo1/*', microApp: 'demo1' },
{ name: 'Demo2', path: '/demo2/*', microApp: 'demo2' },
],
qiankun: {
master: {},
},
... // 其他配置
};修改父应用的
./packages/app/src/app.ts
文件,导出qiankun
对象1
2
3
4
5
6
7
8
9
10
11
12export const qiankun = {
apps: [
{
name: 'demo1',
entry: '//localhost:8001',
},
{
name: 'demo2',
entry: '//localhost:8002',
},
],
};子应用安装
qiankun
插件1
npm i @umijs/plugins -D -w=demo1 -w=demo2
在子应用
./packages/demo1/.umirc.ts
与./packages/demo2/.umirc.ts
文件中引入qiankun
插件1
2
3
4
5
6
7export default {
plugins: ['@umijs/plugins/dist/qiankun'],
qiankun: {
slave: {},
},
... // 其他配置
};重新启动主应用与子应用,即可看到主应用中新增了
Demo1
与Demo2
两个子应用的链接
更多微前端配置
添加快捷命令
在根目录
package.json
中添加下面配置1
2
3
4
5
6
7
8
9
10
11{
"scripts": {
"app": "npm run dev -w=app",
"bootstrap": "lerna bootstrap",
"clean": "lerna clean && rimraf ./node_modules",
"demo1": "npm run dev -w=demo1",
"demo2": "npm run dev -w=demo2",
"preinstall": "npx only-allow npm"
},
... // 其他配置
}使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 安装包
npm install
npm run bootstrap
# 启动主应用
npm run app
# 启动子应用
npm run demo1
# 启动子应用
npm run demo2
# 清空依赖
npm run clean
lerna8 + pnpm + workspaces + umi
包版本
1 | "node": "^20.18.0", |
开启 pnpm workspaces
命令行执行
npx lerna init
, 初始化项目lerna.json
中添加下面配置1
2
3
4
5{
"npmClient": "pnpm",
"packages": ["packages/*"],
... // 其他配置
}在根目录下创建
pnpm-workspace.yaml
并添加下面配置1
2packages:
- 'packages/*'然后在根目录下创建
packages
目录 (注意:名称要与上面配置的packages/*
目录名称一致)
进入 packages
创建主应用 app
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建主应用 app
npx create-umi@latest
# What's the target folder name?
app
# Pick Umi App Template
Ant Design Pro
# Pick Npm Client
pnpm
# Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件)
taobao在
packages/app/package.json
中配置name
1
2
3
4{
"name": "app",
... // 其他配置
}
进入 packages
创建子应用 demo1
, demo2
依次执行下面命令(这里只演示
demo1
,demo2
创建同下)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建子应用 demo1
npx create-umi@latest
# What's the target folder name?
demo1
# Pick Umi App Template
Simple App
# Pick Npm Client
pnpm
# Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件, 不需要可以删除)
taobao在
packages/app/package.json
中配置name
1
2
3
4{
"name": "demo1",
... // 其他配置
}
设置应用的启动端口
在根目录下安装
cross-env
包 (不是项目根目录)1
2
3pnpm -F=app -F=demo1 -F=demo2 i cross-env -D
# 或者
pnpm --filter=app --filter=demo1 --filter=demo2 i cross-env -D配置主应用
app
启动端口,在packages/app/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8000 max dev",
... // 其他配置
}
}配置子应用
demo1
启动端口,在packages/demo1/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8001 umi dev",
... // 其他配置
}
}配置子应用
demo2
启动端口,在packages/demo2/package.json
中修改scripts
1
2
3
4
5
6{
"scripts": {
"dev": "cross-env PORT=8002 umi dev",
... // 其他配置
}
}启动应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages/app 目录
cd ./packages/app
# 启动主应用
pnpm run dev
# 进入 packages/demo1 目录
cd ./packages/demo1
# 启动子应用
pnpm run dev
# 进入 packages/demo2 目录
cd ./packages/demo2
# 启动子应用
pnpm run dev1
2
3
4
5
6
7
8# 根目录下启动主应用
pnpm -F=app run dev
# 根目录下启动子应用
pnpm -F=demo1 run dev
# 根目录下启动子应用
pnpm -F=demo2 run dev1
2# 根目录下启动所有应用
pnpm -r dev
主应用链接子应用
在主应用
./packages/app/.umirc.ts
中添加下面内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15export default {
routes: [
{ path: '/', redirect: '/home' },
{ name: '首页', path: '/home', component: './Home' },
{ name: '权限演示', path: '/access', component: './Access' },
{ name: ' CRUD 示例', path: '/table', component: './Table' },
// 新增微应用示例
{ name: 'Demo1', path: '/demo1/*', microApp: 'demo1' },
{ name: 'Demo2', path: '/demo2/*', microApp: 'demo2' },
],
qiankun: {
master: {},
},
... // 其他配置
};修改父应用的
./packages/app/src/app.ts
文件,导出qiankun
对象1
2
3
4
5
6
7
8
9
10
11
12export const qiankun = {
apps: [
{
name: 'demo1',
entry: '//localhost:8001',
},
{
name: 'demo2',
entry: '//localhost:8002',
},
],
};子应用安装
qiankun
插件1
pnpm -F=demo1 -F=demo2 i @umijs/plugins -D
在子应用
./packages/demo1/.umirc.ts
与./packages/demo2/.umirc.ts
文件中引入qiankun
插件1
2
3
4
5
6
7export default {
plugins: ['@umijs/plugins/dist/qiankun'],
qiankun: {
slave: {},
},
... // 其他配置
};重新启动主应用与子应用,即可看到主应用中新增了
Demo1
与Demo2
两个子应用的链接
更多微前端配置
添加快捷命令
在根目录
package.json
中添加下面配置1
2
3
4
5
6
7
8
9
10
11
12{
"scripts": {
"preinstall": "npx only-allow pnpm",
"all": "pnpm -r dev",
"app": "pnpm -F app dev",
"bootstrap": "lerna bootstrap",
"clean": "lerna clean && rimraf ./node_modules",
"demo1": "pnpm -F demo1 dev",
"demo2": "pnpm -F demo2 dev"
},
... // 其他配置
}使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 安装包
pnpm install
pnpm run bootstrap
# 启动主应用
pnpm run app
# 启动子应用
pnpm run demo1
# 启动子应用
pnpm run demo2
# 清空依赖
pnpm run clean
lerna8 + pnpm + workspaces + vite
包版本
1 | "node": "^20.18.0", |
开启 pnpm workspaces
命令行执行
npx lerna init
, 初始化项目lerna.json
中添加下面配置1
2
3
4
5{
"npmClient": "pnpm",
"packages": ["packages/*"],
... // 其他配置
}在根目录下创建
pnpm-workspace.yaml
并添加下面配置1
2packages:
- 'packages/*'然后在根目录下创建
packages
目录 (注意:名称要与上面配置的packages/*
目录名称一致)
进入 packages
创建主应用 app
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建主应用 app
pnpm create vite
# Project name
app
# Select a framework
React
# Pick Npm Client
pnpm
# Select a variant
TypeScript + SWC在
packages/app/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8000,
},
... // 其他配置
});
进入 packages
创建子应用 demo1
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建子应用 demo1
pnpm create vite
# Project name
demo1
# Select a framework
React
# Pick Npm Client
pnpm
# Select a variant
TypeScript + SWC在
packages/demo1/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8001,
},
... // 其他配置
});
进入 packages
创建子应用 demo2
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 进入 packages 目录
cd ./packages
# 创建子应用 demo2
pnpm create vite
# Project name
demo2
# Select a framework
React
# Pick Npm Client
pnpm
# Select a variant
TypeScript + SWC在
packages/demo2/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8002,
},
... // 其他配置
});
安装所需依赖
删除根目录下的
node_modules
文件夹与package-lock.json
文件在根目录下运行
pnpm install
安装依赖给
app
、demo
、demo2
安装所需的包 (这里可以将公用的包提取到 npm 仓库,然后在各个项目中安装, 这里不做演示)1
2# 在根目录下安装antd与react-router-dom
pnpm -F=app -F=demo1 -F=demo2 i antd react-router-dom
配置主应用
在
app
项目中安装 qiankun1
2# 根目录下执行
pnpm -F=app i qiankun1
2
3
4
5
6
7
8
9
10
11├── public
├── src
├── layout
├── index.tsx
├── microApp.tsx
├── router
├── 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
24import { start } from 'qiankun';
import { useEffect } from 'react';
const MicroApp = () => {
/** 加载子应用 */
const handleLoadMicroApp = () => {
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
};
useEffect(() => {
handleLoadMicroApp();
}, []);
return (
<div>
<div id="subapp"></div>
</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
73import React, { useState } from 'react';
import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
const { Header, Content, Footer, Sider } = Layout;
import MicroApp from './microApp';
/** 菜单栏配置 */
const menus = [
{ key: 'home', label: 'Home', meta: { path: '/home' } },
{
key: 'docs',
label: 'Docs',
children: [
{ key: 'test1', label: 'Test1', meta: { path: '/docs/test1' } },
{ key: 'test2', label: 'Test2', meta: { path: '/docs/test2' } },
],
},
{
key: 'demo1',
label: 'Demo1',
children: [
{ key: 'temp1', label: 'Temp1', meta: { path: '/demo1/temp1' } },
{ key: 'temp2', label: 'Temp2', meta: { path: '/demo1/temp2' } },
],
},
{
key: 'demo2',
label: 'Demo2',
meta: { path: '/demo2' },
},
];
const App: React.FC = () => {
const navigate = useNavigate();
const [selectedKeys, setSelectedKeys] = useState<string[]>(['home']); // 控制菜单栏的选中状态
/** 菜单栏点击事件 */
const handleClick = (e: any) => {
setSelectedKeys([e.key]);
navigate(e?.item?.props?.meta?.path);
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider>
<Menu theme="dark" selectedKeys={selectedKeys} mode="inline" items={menus} onClick={handleClick} />
</Sider>
<Layout>
<Header style={{ padding: 0, background: '#fff' }} />
<Content style={{ margin: '0 16px' }}>
<div
style={{
padding: 24,
minHeight: 360,
background: '#fff',
borderRadius: 16,
margin: '16px 0',
}}>
{/* 这里渲染子路由 */}
<Outlet />
{/* 这里挂载微应用 */}
<MicroApp />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>zxiaosi ©{new Date().getFullYear()}</Footer>
</Layout>
</Layout>
);
};
export default App;修改
packages/app/src/router/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
55import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
import BaseLayout from '../layout/index';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/',
element: <Navigate to="/home" replace={true} />, // 重定向
},
{
path: '/',
element: <BaseLayout />,
errorElement: <>BaseLayout Error</>,
children: [
{
path: '/home',
element: <>主应用 Home</>,
},
{
path: '/docs',
element: <Outlet />,
children: [
{ path: '/docs/test1', element: <>主应用 Docs Test1</> },
{ path: '/docs/test2', element: <>主应用 Docs Test2</> },
],
},
{
path: '/demo1',
element: <Navigate to="/demo1/temp1" replace={true} />,
errorElement: <>子应用 demo1 Error</>,
children: [
{
path: '/demo1/temp1',
element: <></>,
},
{
path: '/demo1/temp2',
element: <></>,
},
],
},
{
path: '/demo2',
element: <></>,
errorElement: <>子应用 demo2 Error</>,
},
],
},
{
path: '/login',
element: <>Login</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return <RouterProvider router={router} />;
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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
30import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { registerMicroApps, RegistrableApp } from 'qiankun';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
/** 子应用配置 */
const microApps: Array<RegistrableApp<any>> = [
{
name: 'demo1',
entry: '//localhost:8001',
container: '#subapp', // 子应用挂载的容器
activeRule: '/demo1',
},
{
name: 'demo2',
entry: '//localhost:8002',
container: '#subapp', // 子应用挂载的容器
activeRule: '/demo2',
},
];
/** 注册子应用 */
registerMicroApps(microApps);
配置子应用 demo1
在
demo1
项目中安装 vite-plugin-qiankun1
2# 在根目录下执行
pnpm -F=demo1 i vite-plugin-qiankun1
2
3
4
5
6
7
8├── public
├── src
├── router
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app/src/router/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { createBrowserRouter } from 'react-router-dom';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/demo1/temp1',
element: <>temp1</>,
},
{
path: '/demo1/temp2',
element: <>temp2</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return (
<>
<h1>子应用 demo1</h1>
<RouterProvider router={router} />
</>
);
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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 { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app = container || (document.getElementById('root') as HTMLDivElement);
createRoot(app).render(
<StrictMode>
<App />
</StrictMode>
);
};
/** 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/demo1/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
28import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
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 = 'demo1';
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 }),
],
});
};
配置子应用 demo2
在
demo2
项目中安装 vite-plugin-qiankun1
2# 在根目录下执行
pnpm -F=demo2 i vite-plugin-qiankun1
2
3
4
5
6
7
8├── public
├── src
├── router
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app/src/router/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11import { createBrowserRouter } from 'react-router-dom';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/demo2',
element: <>demo2</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return (
<>
<h1>子应用 demo2</h1>
<RouterProvider router={router} />
</>
);
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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 { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app = container || (document.getElementById('root') as HTMLDivElement);
createRoot(app).render(
<StrictMode>
<App />
</StrictMode>
);
};
/** 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/demo2/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
28import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
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 = 'demo2';
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 }),
],
});
};
更多微前端配置
添加快捷命令
在根目录
package.json
中添加下面配置1
2
3
4
5
6
7
8
9
10
11
12{
"scripts": {
"preinstall": "npx only-allow pnpm",
"all": "pnpm -r dev",
"app": "pnpm -F app dev",
"bootstrap": "lerna bootstrap",
"clean": "lerna clean && rimraf ./node_modules",
"demo1": "pnpm -F demo1 dev",
"demo2": "pnpm -F demo2 dev"
},
... // 其他配置
}使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 安装包
pnpm install
pnpm run bootstrap
# 启动主应用
pnpm run app
# 启动子应用
pnpm run demo1
# 启动子应用
pnpm run demo2
# 清空依赖
pnpm run clean
lerna6 + npm + workspaces + vite
包版本
1 | "node": "^16.13.2", |
开启 npm workspaces
命令行执行
npx lerna init
, 初始化项目lerna.json
中添加下面配置1
2
3
4{
"npmClient": "npm",
"packages": ["packages/*"]
}package.json
中添加下面配置1
2
3{
"workspaces": ["packages/*"]
}然后在根目录下创建
packages
目录 (注意:名称要与上面配置的packages/*
目录名称一致)
进入 packages
创建主应用 app
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建主应用 app
npm create vite@5
# Project name
app
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/app/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8000,
},
... // 其他配置
});
进入 packages
创建子应用 demo1
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建子应用 demo1
npm create vite@5
# Project name
demo1
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/demo1/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8001,
},
... // 其他配置
});
进入 packages
创建子应用 demo2
依次执行下面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入 packages 目录
cd ./packages
# 创建子应用 demo2
npm create vite@5
# Project name
demo2
# Select a framework
React
# Select a variant
TypeScript + SWC在
packages/demo2/vite.config.ts
文件中配置端口号1
2
3
4
5
6export default defineConfig({
server: {
port: 8002,
},
... // 其他配置
});
安装所需依赖
删除根目录下的
node_modules
文件夹与package-lock.json
文件在根目录下运行
npm install
安装依赖给
app
、demo
、demo2
安装所需的包 (这里可以将公用的包提取到 npm 仓库,然后在各个项目中安装, 这里不做演示)1
2# 在根目录下安装antd与react-router-dom
npm i antd react-router-dom -w=app -w=demo1 -w=demo2
配置主应用
在
app
项目中安装 qiankun1
2# 根目录下执行
npm i qiankun -w=app1
2
3
4
5
6
7
8
9
10
11├── public
├── src
├── layout
├── index.tsx
├── microApp.tsx
├── router
├── 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
24import { start } from 'qiankun';
import { useEffect } from 'react';
const MicroApp = () => {
/** 加载子应用 */
const handleLoadMicroApp = () => {
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
};
useEffect(() => {
handleLoadMicroApp();
}, []);
return (
<div>
<div id="subapp"></div>
</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
73import React, { useState } from 'react';
import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
const { Header, Content, Footer, Sider } = Layout;
import MicroApp from './microApp';
/** 菜单栏配置 */
const menus = [
{ key: 'home', label: 'Home', meta: { path: '/home' } },
{
key: 'docs',
label: 'Docs',
children: [
{ key: 'test1', label: 'Test1', meta: { path: '/docs/test1' } },
{ key: 'test2', label: 'Test2', meta: { path: '/docs/test2' } },
],
},
{
key: 'demo1',
label: 'Demo1',
children: [
{ key: 'temp1', label: 'Temp1', meta: { path: '/demo1/temp1' } },
{ key: 'temp2', label: 'Temp2', meta: { path: '/demo1/temp2' } },
],
},
{
key: 'demo2',
label: 'Demo2',
meta: { path: '/demo2' },
},
];
const App: React.FC = () => {
const navigate = useNavigate();
const [selectedKeys, setSelectedKeys] = useState<string[]>(['home']); // 控制菜单栏的选中状态
/** 菜单栏点击事件 */
const handleClick = (e: any) => {
setSelectedKeys([e.key]);
navigate(e?.item?.props?.meta?.path);
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider>
<Menu theme="dark" selectedKeys={selectedKeys} mode="inline" items={menus} onClick={handleClick} />
</Sider>
<Layout>
<Header style={{ padding: 0, background: '#fff' }} />
<Content style={{ margin: '0 16px' }}>
<div
style={{
padding: 24,
minHeight: 360,
background: '#fff',
borderRadius: 16,
margin: '16px 0',
}}>
{/* 这里渲染子路由 */}
<Outlet />
{/* 这里挂载微应用 */}
<MicroApp />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>zxiaosi ©{new Date().getFullYear()}</Footer>
</Layout>
</Layout>
);
};
export default App;修改
packages/app/src/router/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
55import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
import BaseLayout from '../layout/index';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/',
element: <Navigate to="/home" replace={true} />, // 重定向
},
{
path: '/',
element: <BaseLayout />,
errorElement: <>BaseLayout Error</>,
children: [
{
path: '/home',
element: <>主应用 Home</>,
},
{
path: '/docs',
element: <Outlet />,
children: [
{ path: '/docs/test1', element: <>主应用 Docs Test1</> },
{ path: '/docs/test2', element: <>主应用 Docs Test2</> },
],
},
{
path: '/demo1',
element: <Navigate to="/demo1/temp1" replace={true} />,
errorElement: <>子应用 demo1 Error</>,
children: [
{
path: '/demo1/temp1',
element: <></>,
},
{
path: '/demo1/temp2',
element: <></>,
},
],
},
{
path: '/demo2',
element: <></>,
errorElement: <>子应用 demo2 Error</>,
},
],
},
{
path: '/login',
element: <>Login</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return <RouterProvider router={router} />;
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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
30import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { registerMicroApps, RegistrableApp } from 'qiankun';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
/** 子应用配置 */
const microApps: Array<RegistrableApp<any>> = [
{
name: 'demo1',
entry: '//localhost:8001',
container: '#subapp', // 子应用挂载的容器
activeRule: '/demo1',
},
{
name: 'demo2',
entry: '//localhost:8002',
container: '#subapp', // 子应用挂载的容器
activeRule: '/demo2',
},
];
/** 注册子应用 */
registerMicroApps(microApps);
配置子应用 demo1
在
demo1
项目中安装 vite-plugin-qiankun1
2# 在根目录下执行
npm i vite-plugin-qiankun -w=demo11
2
3
4
5
6
7
8├── public
├── src
├── router
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app/src/router/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { createBrowserRouter } from 'react-router-dom';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/demo1/temp1',
element: <>temp1</>,
},
{
path: '/demo1/temp2',
element: <>temp2</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return (
<>
<h1>子应用 demo1</h1>
<RouterProvider router={router} />
</>
);
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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 { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app = container || (document.getElementById('root') as HTMLDivElement);
createRoot(app).render(
<StrictMode>
<App />
</StrictMode>
);
};
/** 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/demo1/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
28import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
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 = 'demo1';
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 }),
],
});
};
配置子应用 demo2
在
demo2
项目中安装 vite-plugin-qiankun1
2# 在根目录下执行
npm i vite-plugin-qiankun -w=demo21
2
3
4
5
6
7
8├── public
├── src
├── router
├── index.tsx
├── App.tsx
├── index.css
├── main.tsx
├── vite.config.ts修改
packages/app/src/router/index.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11import { createBrowserRouter } from 'react-router-dom';
/** 路由配置 */
const router = createBrowserRouter([
{
path: '/demo2',
element: <>demo2</>,
},
]);
export default router;修改
packages/app/src/App.tsx
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import router from './router';
import { RouterProvider } from 'react-router-dom';
function App() {
return (
<>
<h1>子应用 demo2</h1>
<RouterProvider router={router} />
</>
);
}
export default App;修改
packages/app/src/index.css
文件内容如下1
2
3
4body {
margin: 0;
padding: 0;
}修改
packages/app/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 { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
/** 渲染函数 */
const render = (container?: HTMLElement) => {
const app = container || (document.getElementById('root') as HTMLDivElement);
createRoot(app).render(
<StrictMode>
<App />
</StrictMode>
);
};
/** 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/demo2/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
28import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
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 = 'demo2';
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 }),
],
});
};
更多微前端配置
添加快捷命令
1 | { |
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 安装包
npm install
npm run bootstrap
# 启动主应用
npm run app
# 启动子应用
npm run demo1
# 启动子应用
npm run demo2
# 清空依赖
npm run clean