Qiankun 打包优化
文章参考下面两个 issues
:提取公共依赖 doc、qiankun 如何复用公共依赖
思路
方式一:通过
CDN
引入公共依赖,并在主子应用中排除这些公共依赖。(如果CDN网站
挂了,网站也就挂了,有风险)方式二:将
CDN
资源下载到public
文件夹下,并在主子应用中排除这些公共依赖。防止每个应用都在public
中引入依赖,这里将public
文件指定为libs
,统一存放公共依赖。
项目地址
创建项目
初始化项目
项目目录结构
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|── 项目根目录
│ ├── libs # 存放公共依赖(使用cdn方式不需要这个文件夹)
│ │ ├── react@18.3.1
│ │ │ ├── react.development.js
│ │ │ ├── react.production.min.js
│ │ ├── react-dom@18.3.1
│ │ │ ├── react-dom.development.js
│ │ │ ├── react-dom.production.min.js
│ ├── packages # 包目录
│ │ ├── app # 主应用 (应用根目录)
│ | | ├── src
│ | | | ├── App.tsx
│ | | | ├── main.tsx
│ | | ├── .env
│ | | ├── .env.production
│ | | ├── index.html
│ | | ├── vite.config.ts
│ │ ├── app1 # 子应用 (应用根目录)
│ | | ├── src
│ | | | ├── App.tsx
│ | | | ├── main.tsx
│ | | ├── .env
│ | | ├── .env.production
│ | | ├── index.html
│ | | ├── vite.config.ts
│ ├── package.json # 全局 package.json
│ ├── pnpm-workspace.yaml # pnpm workspace 配置文件使用
npm create vite
命令在packages
目录下创建两个应用
安装依赖
1 | # 在项目根目录执行 |
配置主应用 app
在
packages/app/src/main.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
59import { registerMicroApps, start } from 'qiankun';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
const container = document.getElementById('root')!;
const root = createRoot(container);
const render = (props: any) => root.render(<App {...props} />);
render({ loading: false });
const loader = (loading: boolean) => render({ loading });
/** 注册子应用 */
registerMicroApps(
[
{
name: 'app1',
entry: 'http://localhost:8001',
container: '#sub-app',
activeRule: '/app1',
props: {
appName: 'app1',
},
loader,
},
],
{
beforeLoad: [
async (app) => {
console.log(
'[LifeCycle] before load %c%s',
'color: green;',
app.name
);
},
],
beforeMount: [
async (app) => {
console.log(
'[LifeCycle] before mount %c%s',
'color: green;',
app.name
);
},
],
afterUnmount: [
async (app) => {
console.log(
'[LifeCycle] after unmount %c%s',
'color: green;',
app.name
);
},
],
}
);
start();在
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
42
43
44
45
46
47
48
49
50import {
createBrowserRouter,
Link,
Outlet,
RouterProvider,
} from 'react-router';
function App({ loading }: any) {
const router = createBrowserRouter(
[
{
path: '/',
element: (
<>
<div style={{ fontSize: 24 }}>主应用app</div>
<Link to={'/'}>回首页</Link>
<Link to={'/app1'} style={{ margin: '0 20px' }}>
子应用app
</Link>
<Link to={'/test'}>子模块test</Link>
{loading && <div>loading...</div>}
<Outlet />
<main id="sub-app"></main>
</>
),
children: [
{
path: 'app1/*',
element: <></>,
},
{
path: 'test',
element: <div>test</div>,
},
],
},
],
{
basename: '/',
}
);
return <RouterProvider router={router} />;
}
export default App;在
packages/app/vite.config.ts
中读取.env
文件并设置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig, loadEnv } from 'vite';
// https://vite.dev/config/
export default ({ mode }) => {
// 环境变量文件夹
const envDir = resolve(__dirname, './');
// 加载环境变量
const env = loadEnv(mode, envDir);
return defineConfig({
server: {
port: Number(env.VITE_PORT),
},
preview: {
port: Number(env.VITE_PORT),
},
plugins: [react()],
});
};在
packages/app/.env
中配置环境变量1
VITE_PORT=8000
配置子应用 app1
在
packages/app1/src/main.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
40import { createRoot, type Root } from 'react-dom/client';
import { name } from '../package.json';
import App from './App.tsx';
import './index.css';
let root: Root;
const render = (props: any = {}) => {
const container = props?.container
? props.container.querySelector('#root')
: document.getElementById('root');
root = createRoot(container);
root.render(<App appName={props.appName} />);
};
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log(`${name} bootstrap`);
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export async function mount(props: any) {
console.log(`${name} mount`, props);
render(props);
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export async function unmount(props: any) {
console.log(`${name} unmount`, props);
root.unmount();
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export async function update(props: any) {
console.log(`${name} update`, props);
}在
packages/app1/src/App.tsx
中配置路由1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { createBrowserRouter, RouterProvider } from 'react-router';
function App({ appName = '' }) {
const router = createBrowserRouter(
[
{
path: '/',
element: <>子应用app</>,
},
],
{
basename: `/${appName}`,
}
);
return <RouterProvider router={router} />;
}
export default App;在
packages/app1/vite.config.ts
中配置qiankun
插件并设置端口号1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig, loadEnv } from 'vite';
import qiankun from 'vite-plugin-qiankun-lite';
import { name } from './package.json';
// https://vite.dev/config/
export default ({ mode }) => {
// 环境变量文件夹
const envDir = resolve(__dirname, './');
// 加载环境变量
const env = loadEnv(mode, envDir);
return defineConfig({
server: {
port: Number(env.VITE_PORT),
},
preview: {
port: Number(env.VITE_PORT),
},
plugins: [react(), qiankun({ name: name, sandbox: true })],
});
};在
packages/app1/.env
中配置环境变量1
VITE_PORT=8001
未优化启动
打包项目
1 | # 在项目根目录执行 |
项目启动
1 | # 在项目根目录执行 |
只有在第一次加载子应用的时候,才会请求所有资源,后续加载会走缓存。如果想看子应用不走缓存的资源大小,可以 禁用缓存
优化配置
CDN 网站
安装依赖
安装
vite-plugin-externals
包1
2# 在项目根目录执行
pnpm -F app -F app1 i vite-plugin-externals -D使用
vite-plugin-externals
之后,就不用再配置rollup.external
了,而且开发环境
也会自动排除依赖
配置主应用 app
修改
packages/app/vite.config.ts
文件如下,添加vite-plugin-externals
插件。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
37import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig, loadEnv } from 'vite';
import { viteExternalsPlugin } from 'vite-plugin-externals';
// https://vite.dev/config/
export default ({ mode }) => {
// 环境变量文件夹
const envDir = resolve(__dirname, './');
// 静态资源服务的文件夹
const publicDir = resolve(__dirname, '../../libs');
// 加载环境变量
const env = loadEnv(mode, envDir);
return defineConfig({
publicDir: publicDir, // 指定依赖包位置
server: {
port: Number(env.VITE_PORT),
},
preview: {
port: Number(env.VITE_PORT),
},
plugins: [
react(),
/**
* 排除 react react-dom, 使用 cdn/本地文件 加载
* - https://github.com/umijs/qiankun/issues/581
* - https://github.com/umijs/qiankun/issues/627
*/
viteExternalsPlugin({
react: 'React',
'react-dom': 'ReactDOM',
'react-dom/client': 'ReactDOM',
}),
],
});
};在
packages/app/.env
中设置开发环境
的CDN链接
或者本地文件
1
2
3
4
5VITE_PORT=8000
# VITE_REACT_FILE_PATH=https://unpkg.com/react@18.3.1/umd/react.development.js
# VITE_REACT_DOM_FILE_PATH=https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js
VITE_REACT_FILE_PATH=/react@18.3.1/react.development.js
VITE_REACT_DOM_FILE_PATH=/react-dom@18.3.1/react-dom.development.js
配置子应用 app1
- 修改
packages/app1/vite.config.ts
文件如下,添加vite-plugin-externals
插件。
1 | import react from '@vitejs/plugin-react'; |
在
packages/app1/.env
中设置开发环境
的CDN链接
或者本地文件
1
2
3
4
5VITE_PORT=8001
# VITE_REACT_FILE_PATH=https://unpkg.com/react@18.3.1/umd/react.development.js
# VITE_REACT_DOM_FILE_PATH=https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js
VITE_REACT_FILE_PATH=/react@18.3.1/react.development.js
VITE_REACT_DOM_FILE_PATH=/react-dom@18.3.1/react-dom.development.js
配置所有应用
在
packages/app/.env.production
和packages/app1/.env.production
中配置生产环境
的CDN链接
或者本地文件
1
2
3
4# VITE_REACT_FILE_PATH=https://unpkg.com/react@18.3.1/umd/react.production.min.js
# VITE_REACT_DOM_FILE_PATH=https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js
VITE_REACT_FILE_PATH=/react@18.3.1/react.production.min.js
VITE_REACT_DOM_FILE_PATH=/react-dom@18.3.1/react-dom.production.min.js最后在
packages/app/index.html
和packages/app1/index.html
中引入环境变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<!-- 注意这里的 ignore 关键字 -->
<script ignore src="%VITE_REACT_FILE_PATH%"></script>
<script ignore src="%VITE_REACT_DOM_FILE_PATH%"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
优化启动(引入 CDN 链接)
打包项目
1 | # 在项目根目录执行 |
打包的文件中会引入 libs
中的文件,CICD
时 移除
或者 不拷贝
即可
CDN
方式打包
本地文件
方式打包
项目启动
1 | # 在项目根目录执行 |
只有在第一次加载子应用的时候,才会请求所有资源,后续加载会走缓存。如果想看子应用不走缓存的资源大小,可以 禁用缓存
总资源加载大小和不优化时大小是差不多的
CDN
方式启动
本地文件
方式启动