0%

React 微前端架构——模块化开发与集成实践

微前端改造

介绍

  随着前端应用的规模越来越大,传统的单体应用架构已经无法满足多团队协作、快速迭代的需求。微前端架构应运而生,它将前端应用拆分为多个独立的子应用,每个子应用可以独立开发、独立部署,最后由基座应用整合在一起。

  今天我们就来聊聊如何基于 qiankun 构建一套完整的微前端架构,并结合 Monorepo + Turbo 的工程化方案,实现高效的多项目管理。

技术栈选型

为什么选择这套技术栈

技术选择理由
React 18主流框架,生态丰富,团队熟悉
Typescript类型安全,提高代码质量和可维护性
Vite极速的开发体验,HMR 快如闪电
pnpm workspace节省磁盘空间,依赖管理更高效
Turbo增量构建,缓存优化,大幅提升构建速度
qiankun阿里开源微前端框架,稳定可靠
zustand轻量级状态管理,跨应用通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 技术选型对比 const techStack = {
buildTool: {
选择: 'Vite',
对比: ['Webpack', 'Rollup', 'esbuild'],
优势: '开发环境启动速度快 10 倍以上,HMR 几乎瞬时完成'
},
packageManager: {
选择: 'pnpm',
对比: ['npm', 'yarn'],
优势: '磁盘占用减少 50%,安装速度快 2 倍'
},
monorepo: {
选择: 'pnpm workspace + turbo',
对比: ['lerna', 'nx', 'rush'],
优势: '配置简单,构建速度快,缓存机制强大'
}
};

项目架构设计

整体目录结构

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
micro-frontend-project/
├── apps/ # 应用目录
│ ├── main/ # 主应用(基座)
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── main.tsx
│ │ │ ├── micro/ # 微前端配置
│ │ │ │ ├── apps.TS # 子应用注册配置
│ │ │ │ └── index.TS # qiankun 初始化
│ │ │ ├── router/ # 路由配置
│ │ │ ├── store/ # 全局状态
│ │ │ └── utils/
│ │ ├── vite.config.TS
│ │ └── package.Json
│ │
│ ├── sub-app-1/ # 子应用1
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── main.tsx
│ │ │ ├── public-path.TS # 动态 publicPath
│ │ │ └── qiankun/ # qiankun 生命周期
│ │ ├── vite.config.TS
│ │ └── package.Json
│ │
│ └── sub-app-2/ # 子应用2
│ └── ...

├── packages/ # 共享包目录
│ ├── shared/ # 共享工具库
│ │ ├── src/
│ │ │ ├── index.TS
│ │ │ ├── constants/ # 常量
│ │ │ ├── utils/ # 工具函数
│ │ │ └── types/ # 类型定义
│ │ └── package.Json
│ │
│ ├── ui-components/ # 共享 UI 组件库
│ │ ├── src/
│ │ │ ├── index.TS
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ └── ...
│ │ └── package.Json
│ │
│ └── eslint-config/ # ESLint 配置
│ ├── index.JS
│ └── package.Json

├── .husky/ # Git hooks
├── .vscode/ # VSCode 配置
├── turbo.Json # Turbo 配置
├── pnpm-workspace.yaml # pnpm workspace 配置
├── package.Json # 根 package.Json
├── tsconfig.Json # Typescript 配置
└── README.md

pnpm workspace 配置

1
2
3
4
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'

Turbo 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// turbo.Json
{
"$schema": "https://turbo.build/schema.Json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}

主应用(基座)实现

qiankun 配置与初始化

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
// apps/main/src/micro/apps.TS
import { MicroApp } from './types';

/**
* 子应用配置列表
*/
export const microApps: MicroApp[] = [
{
name: 'sub-app-1', // 应用名称 entry: '//localhost:3001', // 应用入口 container: '#subapp-container', // 应用挂载节点 activeRule: '/app1', // 激活路由规则 props: { // 传递给子应用的数据 routerBase: '/app1',
getGlobalState: () => globalStore.getState()
}
},
{
name: 'sub-app-2',
entry: '//localhost:3002',
container: '#subapp-container',
activeRule: '/app2',
props: {
routerBase: '/app2',
getGlobalState: () => globalStore.getState()
}
}
];

// apps/main/src/micro/index.TS
import { registerMicroApps, start, initGlobalState } from 'qiankun';
import { microApps } from './apps';
import { message } from 'antd';

/**
* 初始化全局状态
*/
const initialState = {
user: null,
token: localStorage.getItem('token') || '',
theme: 'light',
permissions: []
};

// 创建全局状态 const actions = initGlobalState(initialState);

// 监听全局状态变化 actions.onGlobalStateChange((state, prev) => {
console.log('主应用: 全局状态发生变化', state, prev);
});

/**
* 注册微应用
*/
export function registerApps() {
registerMicroApps(microApps, {
beforeLoad: [
(app) => {
console.log('[主应用] before load', app.name);
return Promise.resolve();
}
],
beforeMount: [
(app) => {
console.log('[主应用] before mount', app.name);
return Promise.resolve();
}
],
afterMount: [
(app) => {
console.log('[主应用] after mount', app.name);
return Promise.resolve();
}
],
beforeUnmount: [
(app) => {
console.log('[主应用] before unmount', app.name);
return Promise.resolve();
}
],
afterUnmount: [
(app) => {
console.log('[主应用] after unmount', app.name);
return Promise.resolve();
}
]
});
}

/**
* 启动微前端
*/
export function startQiankun() {
start({
sandbox: {
strictStyleIsolation: false, // 样式隔离 experimentalStyleIsolation: true
},
prefetch: true, // 预加载 singular: false, // 是否为单实例场景 fetch: (url, ...args) => {
// 自定义 fetch 方法 console.log('[主应用] fetch', url);
return window.fetch(url, ...args);
}
});

console.log('[主应用] qiankun started');
}

export { actions };

主应用路由配置

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
// apps/main/src/router/index.tsx
import { lazy, Suspense } from 'React';
import { BrowserRouter, Routes, Route, Navigate } from 'React-router-dom';
import Layout from '@/components/Layout';
import Loading from '@/components/Loading';

// 懒加载页面组件 const Home = lazy(() => import('@/pages/Home'));
const Login = lazy(() => import('@/pages/Login'));

const Router = () => {
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/login" element={<Login />} />

<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="/home" replace />} />
<Route path="home" element={<Home />} />

{/* 微应用路由占位 */}
<Route path="/app1/*" element={<div id="subapp-container" />} />
<Route path="/app2/*" element={<div id="subapp-container" />} />
</Route>

<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
</Suspense>
</BrowserRouter>
);
};

export default Router;

主应用入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// apps/main/src/main.tsx
import React from 'React';
import ReactDOM from 'React-dom/client';
import App from './App';
import { registerApps, startQiankun } from './micro';
import './index.Css';

// 注册微应用 registerApps();

// 渲染主应用 ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

// 启动 qiankun
startQiankun();

主应用 Vite 配置

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
// apps/main/vite.config.TS
import { defineConfig } from 'vite';
import React from '@vitejs/plugin-React';
import path from 'path';

export default defineConfig({
plugins: [React()],

resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@shared': path.resolve(__dirname, '../../packages/shared/src'),
'@ui': path.resolve(__dirname, '../../packages/ui-components/src')
}
},

server: {
port: 3000,
cors: true,
proxy: {
'/API': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/API/, '')
}
}
},

build: {
target: 'es2015',
outDir: 'dist',
minify: 'terser',
sourcemap: false
}
});

子应用实现

子应用 qiankun 生命周期

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
// apps/sub-app-1/src/qiankun/index.tsx
import React from 'React';
import ReactDOM from 'React-dom/client';
import { BrowserRouter } from 'React-router-dom';
import App from '../App';
import './public-path';

let root: ReactDOM.Root | null = null;

/**
* 渲染函数
*/
function render(props: any = {}) {
const { container } = props;

// 获取挂载容器 const containerElement = container
? container.querySelector('#sub-app-1-root')
: document.getElementById('sub-app-1-root');

if (!containerElement) {
console.error('挂载容器不存在');
return;
}

// 创建 React 根节点 root = ReactDOM.createRoot(containerElement);

root.render(
<React.StrictMode>
<BrowserRouter basename={props.routerBase || '/app1'}>
<App {...props} />
</BrowserRouter>
</React.StrictMode>
);
}

/**
* bootstrap 只会在微应用初始化的时候调用一次
* 通常在这里执行一些全局初始化
*/
export async function bootstrap() {
console.log('[子应用1] bootstrap');
}

/**
* 应用每次进入都会调用 mount 方法
* 通常在这里触发应用的渲染
*/
export async function mount(props: any) {
console.log('[子应用1] mount', props);
render(props);
}

/**
* 应用每次切出/卸载都会调用 unmount 方法
* 通常在这里执行一些清理操作
*/
export async function unmount(props: any) {
console.log('[子应用1] unmount', props);

if (root) {
root.unmount();
root = null;
}
}

/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props: any) {
console.log('[子应用1] update', props);
}

// 独立运行时直接挂载应用 if (!(window as any).__POWERED_BY_QIANKUN__) {
render({});
}

子应用动态 publicPath

1
2
3
4
5
6
7
8
9
// apps/sub-app-1/src/qiankun/public-path.TS
/**
* 动态设置 webpack publicPath
* 解决微应用静态资源加载问题
*/
if ((window as any).__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

子应用 Vite 配置

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
// apps/sub-app-1/vite.config.TS
import { defineConfig } from 'vite';
import React from '@vitejs/plugin-React';
import path from 'path';
import qiankun from 'vite-plugin-qiankun';

export default defineConfig({
plugins: [
React(),
qiankun('sub-app-1', {
useDevMode: true
})
],

resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@shared': path.resolve(__dirname, '../../packages/shared/src'),
'@ui': path.resolve(__dirname, '../../packages/ui-components/src')
}
},

server: {
port: 3001,
cors: true,
origin: 'http://localhost:3001'
},

build: {
target: 'es2015',
outDir: 'dist',
minify: 'terser',
sourcemap: false,
// qiankun 需要的配置 lib: {
name: 'sub-app-1',
entry: './src/qiankun/index.tsx',
formats: ['umd']
}
}
});

跨应用通信方案

基于 zustand 的全局状态管理

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
// packages/shared/src/store/global.TS
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

interface User {
id: string;
name: string;
avatar?: string;
email: string;
}

interface GlobalState {
// 用户信息 user: User | null;
setUser: (user: User | null) => void;

// 认证 token
token: string;
setToken: (token: string) => void;

// 权限列表 permissions: string[];
setPermissions: (permissions: string[]) => void;

// 主题 theme: 'light' | 'dark';
toggleTheme: () => void;

// 语言 locale: 'zh-CN' | 'en-US';
setLocale: (locale: 'zh-CN' | 'en-US') => void;

// 登出 logout: () => void;
}

export const useGlobalStore = create<GlobalState>()(
devtools(
persist(
(set) => ({
// 初始状态 user: null,
token: '',
permissions: [],
theme: 'light',
locale: 'zh-CN',

// Actions
setUser: (user) => set({ user }),

setToken: (token) => {
set({ token });
localStorage.setItem('token', token);
},

setPermissions: (permissions) => set({ permissions }),

toggleTheme: () =>
set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),

setLocale: (locale) => set({ locale }),

logout: () => {
set({
user: null,
token: '',
permissions: []
});
localStorage.removeItem('token');
}
}),
{
name: 'global-store',
partialize: (state) => ({
theme: state.theme,
locale: state.locale,
token: state.token
})
}
),
{ name: 'GlobalStore' }
)
);

qiankun 官方通信方式

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
// apps/main/src/micro/communication.TS
import { initGlobalState, MicroAppStateActions } from 'qiankun';

/**
* 定义全局状态类型
*/
interface GlobalState {
user: any;
token: string;
permissions: string[];
[key: string]: any;
}

/**
* 初始化全局状态
*/
const initialState: GlobalState = {
user: null,
token: localStorage.getItem('token') || '',
permissions: []
};

/**
* 创建全局状态管理实例
*/
const actions: MicroAppStateActions = initGlobalState(initialState);

/**
* 监听全局状态变化
*/
actions.onGlobalStateChange((newState, prevState) => {
console.log('[主应用] 状态变化:', newState, prevState);

// 同步到 localStorage
if (newState.token) {
localStorage.setItem('token', newState.token);
}
});

/**
* 设置全局状态
*/
export const setGlobalState = (state: Partial<GlobalState>) => {
actions.setGlobalState(state);
};

/**
* 获取全局状态
*/
export const getGlobalState = (): GlobalState => {
return actions.getGlobalState();
};

export { actions };

// 子应用中监听全局状态
// apps/sub-app-1/src/App.tsx
export default function App(props: any) {
const { onGlobalStateChange, setGlobalState } = props;

useEffect(() => {
// 监听全局状态变化 if (onGlobalStateChange) {
onGlobalStateChange((state: any, prev: any) => {
console.log('[子应用1] 全局状态变化:', state, prev);
}, true);
}
}, [onGlobalStateChange]);

// 修改全局状态 const handleUpdateState = () => {
if (setGlobalState) {
setGlobalState({
user: { name: '张三', id: '123' }
});
}
};

return <div>子应用内容</div>;
}

自定义事件通信

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
// packages/shared/src/utils/event-bus.TS
type EventHandler = (...args: any[]) => void;

class EventBus {
private events: Map<string, EventHandler[]> = new Map();

/**
* 订阅事件
*/
on(event: string, handler: EventHandler): () => void {
if (!this.events.has(event)) {
this.events.set(event, []);
}

const handlers = this.events.get(event)!;
handlers.push(handler);

// 返回取消订阅函数 return () => this.off(event, handler);
}

/**
* 订阅一次性事件
*/
once(event: string, handler: EventHandler): void {
const onceHandler: EventHandler = (...args) => {
handler(...args);
this.off(event, onceHandler);
};

this.on(event, onceHandler);
}

/**
* 取消订阅
*/
off(event: string, handler: EventHandler): void {
const handlers = this.events.get(event);
if (!handlers) return;

const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}

// 如果没有监听器了,删除该事件 if (handlers.length === 0) {
this.events.delete(event);
}
}

/**
* 触发事件
*/
emit(event: string, ...args: any[]): void {
const handlers = this.events.get(event);
if (!handlers) return;

handlers.forEach(handler => {
try {
handler(...args);
} catch (error) {
console.error(`EventBus: Error in ${event} handler:`, error);
}
});
}

/**
* 清空所有事件
*/
clear(): void {
this.events.clear();
}

/**
* 获取事件列表
*/
getEvents(): string[] {
return Array.from(this.events.keys());
}
}

// 导出单例 export const eventBus = new EventBus();

// 使用示例
// 主应用中触发事件 eventBus.emit('user:login', { id: '123', name: '张三' });

// 子应用中监听事件 const unsubscribe = eventBus.on('user:login', (user) => {
console.log('用户登录:', user);
});

// 组件卸载时取消订阅
// unsubscribe();

权限管理

RBAC 权限模型

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
// packages/shared/src/auth/permission.TS
/**
* 权限检查工具
*/
export class PermissionManager {
private permissions: Set<string> = new Set();

/**
* 设置权限列表
*/
setPermissions(permissions: string[]): void {
this.permissions = new Set(permissions);
}

/**
* 检查是否有某个权限
*/
hasPermission(permission: string): boolean {
return this.permissions.has(permission);
}

/**
* 检查是否有任一权限
*/
hasAnyPermission(permissions: string[]): boolean {
return permissions.some(p => this.permissions.has(p));
}

/**
* 检查是否有所有权限
*/
hasAllPermissions(permissions: string[]): boolean {
return permissions.every(p => this.permissions.has(p));
}

/**
* 清空权限
*/
clearPermissions(): void {
this.permissions.clear();
}

/**
* 获取所有权限
*/
getAllPermissions(): string[] {
return Array.from(this.permissions);
}
}

// 导出单例 export const permissionManager = new PermissionManager();

// React Hook 封装 import { useGlobalStore } from '../store/global';

export function usePermission() {
const permissions = useGlobalStore(state => state.permissions);

const hasPermission = (permission: string) => {
return permissions.includes(permission);
};

const hasAnyPermission = (perms: string[]) => {
return perms.some(p => permissions.includes(p));
};

const hasAllPermissions = (perms: string[]) => {
return perms.every(p => permissions.includes(p));
};

return {
permissions,
hasPermission,
hasAnyPermission,
hasAllPermissions
};
}

权限组件

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
// packages/ui-components/src/Permission/index.tsx
import React from 'React';
import { usePermission } from '@shared/auth/permission';

interface PermissionProps {
// 需要的权限 permission?: string;
// 需要任一权限 anyPermissions?: string[];
// 需要所有权限 allPermissions?: string[];
// 没有权限时的替代内容 fallback?: React.ReactNode;
// 子组件 children: React.ReactNode;
}

/**
* 权限控制组件
*/
export const Permission: React.FC<PermissionProps> = ({
permission,
anyPermissions,
allPermissions,
fallback = null,
children
}) => {
const { hasPermission, hasAnyPermission, hasAllPermissions } = usePermission();

// 检查权限 let hasAuth = true;

if (permission) {
hasAuth = hasPermission(permission);
} else if (anyPermissions) {
hasAuth = hasAnyPermission(anyPermissions);
} else if (allPermissions) {
hasAuth = hasAllPermissions(allPermissions);
}

return hasAuth ? <>{children}</> : <>{fallback}</>;
};

// 使用示例
<Permission permission="user:edit">
<button>编辑用户</button>
</Permission>

<Permission
anyPermissions={['user:edit', 'user:delete']}
fallback={<div>无权限访问</div>}
>
<div>用户管理内容</div>
</Permission>

多语言支持

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
// packages/shared/src/i18n/index.TS
import i18n from 'i18next';
import { initReactI18next } from 'React-i18next';
import zhCN from './locales/zh-CN.Json';
import enUS from './locales/en-US.Json';

/**
* 初始化 i18n
*/
i18n
.use(initReactI18next)
.init({
resources: {
'zh-CN': {
translation: zhCN
},
'en-US': {
translation: enUS
}
},
lng: localStorage.getItem('locale') || 'zh-CN',
fallbackLng: 'zh-CN',
interpolation: {
escapeValue: false
}
});

export default i18n;

// 使用示例 import { useTranslation } from 'React-i18next';

function App() {
const { t, i18n } = useTranslation();

const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
localStorage.setItem('locale', lng);
};

return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => changeLanguage('zh-CN')}>中文</button>
<button onClick={() => changeLanguage('en-US')}>English</button>
</div>
);
}

环境配置管理

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
// packages/shared/src/config/env.TS
/**
* 环境变量配置
*/
export interface EnvConfig {
// API 基础地址 apiBaseUrl: string;
// 是否开发环境 isDev: boolean;
// 是否生产环境 isProd: boolean;
// 应用版本 appVersion: string;
}

/**
* 获取环境配置
*/
export function getEnvConfig(): EnvConfig {
const mode = import.meta.env.MODE;

return {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL || '',
isDev: mode === 'development',
isProd: mode === 'production',
appVersion: import.meta.env.VITE_APP_VERSION || '1.0.0'
};
}

export const env = getEnvConfig();

// .env.development
// VITE_API_BASE_URL=http://localhost:8080/API
// VITE_APP_VERSION=1.0.0

// .env.production
// VITE_API_BASE_URL=https://API.example.com
// VITE_APP_VERSION=1.0.0

代码规范配置

ESLint 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// packages/eslint-config/index.JS
module.exports = {
extends: [
'eslint:recommended',
'plugin:React/recommended',
'plugin:React-hooks/recommended',
'plugin:@Typescript-eslint/recommended',
'prettier'
],
parser: '@Typescript-eslint/parser',
plugins: ['React', '@Typescript-eslint', 'React-hooks'],
rules: {
'React/React-in-jsx-scope': 'off',
'@Typescript-eslint/no-explicit-any': 'warn',
'@Typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'React-hooks/rules-of-hooks': 'error',
'React-hooks/exhaustive-deps': 'warn'
},
settings: {
React: {
version: 'detect'
}
}
};

Prettier 配置

1
2
3
4
5
6
7
8
9
// .prettierrc.JS
module.exports = {
semi: true,
singleQuote: true,
printWidth: 100,
tabWidth: 2,
trailingComma: 'none',
arrowParens: 'always'
};

Husky + lint-staged 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.Json
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{TS,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{Json,md}": [
"prettier --write"
]
}
}
1
2
3
4
5
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm lint-staged

常用命令脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// package.Json
{
"scripts": {
"dev": "turbo run dev --parallel",
"dev:main": "pnpm --filter main dev",
"dev:sub1": "pnpm --filter sub-app-1 dev",
"dev:sub2": "pnpm --filter sub-app-2 dev",

"build": "turbo run build",
"build:main": "pnpm --filter main build",

"lint": "turbo run lint",
"lint:fix": "turbo run lint -- --fix",

"clean": "turbo run clean && rm -rf node_modules",

"type-check": "turbo run type-check"
}
}

AI 可理解的架构说明

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
# 微前端架构 AI 说明文档

## 系统架构
- 主应用: 提供基础框架、路由、认证、全局状态管理
- 子应用: 独立业务模块,可独立开发部署
- 共享包: 公共工具、UI 组件、类型定义

## 通信机制
1. qiankun 官方 API: initGlobalState
2. zustand 全局状态: 跨应用状态共享
3. 自定义 EventBus: 事件驱动通信

## 开发流程
1. 安装依赖: pnpm install
2. 启动主应用: pnpm dev:main
3. 启动子应用: pnpm dev:sub1
4. 构建项目: pnpm build

## 添加新子应用步骤
1. apps 目录下创建新应用
2. 配置 qiankun 生命周期
3. 主应用中注册子应用
4. 配置路由和权限

## 部署策略
- 主应用和子应用独立部署
- Nginx 反向代理统一访问
- CDN 加速静态资源

总结

  • 微前端架构解决了大型前端应用的协作和维护问题
  • qiankun 提供了稳定可靠的微前端解决方案,支持样式隔离、沙箱机制
  • Monorepo + Turbo 提供了高效的多项目管理和构建方案
  • 通过 zustand、qiankun API、EventBus 等方式实现跨应用通信
  • 完善的权限管理、多语言、环境配置让应用更加健壮
  • 规范的代码风格和 Git 提交规范保证项目质量

研究了一周的微前端,总算搞明白了这套架构。虽然前期搭建比较复杂,但是对于大型项目的长期维护来说,绝对是值得的投入。周末加班写这篇博客,算是对自己学习的一个总结吧!

扩展阅读

  • qiankun 官方文档
  • 微前端架构设计
  • Turbo 官方文档
  • pnpm workspace
  • Vite 官方文档

参考资料

  • 从零到一搭建微前端架构
  • Monorepo 最佳实践
  • React 18 新特性
bulb