摘要
本文基于 Webpack Module Federation 技术,精简梳理微前端架构的接入流程、核心能力与最佳实践,聚焦开发者实际落地需求,助力快速上手并完成企业级部署。
一、接入前准备:选对方案少走弯路
接入前优先根据项目场景选择合适方案,新项目直接使用官方模板,旧项目按需改造,避免无效配置。
1.1 优先使用官方模板(强烈推荐)
全新主应用/子应用,直接用官方模板开箱即用,无需复杂配置:
bash
npm i -g @spc-fe-common/cli
spc-cli create projectName
优势:集成最佳实践、持续更新、含完整示例,大幅提升开发效率。
1.2 本文适用场景
仅针对改造/集成场景,非以下场景建议优先用模板:
- 主子应用版本差异:新主应用加载旧子应用、旧主应用集成新子应用
- 能力升级:从 Qiankun 迁移、集成国际化/请求/监控等企业级能力
- 特定能力接入:通用国际化翻译、通用 CDN、通用请求、通用打印、通用监控、通用跨框架互通等
二、核心概览
基于 Webpack Module Federation,核心目标是实现新旧主应用快速加载任意 MF 子应用,具备技术栈无关、向后兼容、渐进式接入三大核心优势。
2.1 核心兼容性
| 特性 | 说明 |
|---|---|
| 技术栈无关 | 支持任意技术栈 MF 子应用,主子应用技术栈差异不影响使用 |
| 向后兼容 | 完全兼容 Qiankun 子应用,主应用无需改造 |
| 渐进式接入 | 从最小化配置到企业级功能,可平滑升级 |
2.2 核心架构与快速接入流程
基于 spc-fe-common MonoRepo 构建,采用"核心必须包+可选增强包"架构,快速接入流程如下:
主应用
安装核心包
loader - 加载引擎
context - 上下文管理
i18n - 国际化
配置 Loader
加载子应用
完成接入
可选增强包集成
核心必须包(最小化接入必备)
| 包名 | 功能 |
|---|---|
| @spc-fe-common/loader | 子应用加载核心引擎 |
| @spc-fe-common/context | 全局上下文与实例共享 |
| @spc-fe-common/i18n | 多语言翻译支持 |
可选增强包(按需集成)
通用国际化翻译、通用 CDN、通用请求、[通用打印、通用监控、通用跨框架互通 等。
三、快速接入(最小化配置)
前提:子应用已配置 Module Federation 并符合暴露规范(新子应用推荐用模板)。
3.1 安装核心包
bash
pnpm i @spc-fe-common/{loader,context,i18n}@latest
3.2 初始化全局上下文
在主应用入口(src/index.ts)配置,注入国际化实例:
typescript
import { setContext } from '@spc-fe-common/context';
import I18n from '@spc-fe-common/i18n';
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
// 初始化全局上下文,注入国际化实例
const initGlobalContext = async () => {
const i18nInstance = I18n.getInstance({
resourceIds: [xxx], // 替换为项目实际翻译资源 ID
projects: ['your-project'], // 替换为当前项目名称
});
await i18nInstance.initialization();
setContext('i18n', i18nInstance, true); // 全局共享
};
// 启动应用,先初始化上下文再渲染
export const runApp = async () => {
await initGlobalContext();
const appContainer = document.getElementById('app');
if (appContainer) {
createRoot(appContainer).render(<App />);
}
};
runApp();
3.3 配置 Loader
创建 src/loader.ts,统一管理子应用加载规则:
typescript
import { useLoader as originalUseLoader } from '@spc-fe-common/loader';
// 封装子应用加载器,返回路由组件供主应用挂载
export const useSubAppLoader = () => {
const { ReactRoutes } = originalUseLoader({
routerMode: 'history', // 可选 hash 模式
modules: [
{
name: 'YourSubApp', // 与子应用 MF 配置 name 一致
entryUrl: '/subapp/remoteEntry.js', // 子应用远程入口
}
],
});
return { ReactRoutes };
};
3.4 渲染子应用路由
在主应用根组件(src/App.tsx)挂载子应用路由:
tsx
import React from 'react';
import { BrowserRouter as Router, Switch } from 'react-router-dom';
import { useSubAppLoader } from './loader';
function App() {
const { ReactRoutes } = useSubAppLoader();
return (
<Router>
<Switch>
{ReactRoutes} {/* 挂载子应用路由 */}
{/* 可添加主应用自身路由 */}
</Switch>
</Router>
);
}
export default App;
至此,主应用已具备加载 MF 子应用的核心能力。
四、主应用企业级配置
需集成跨框架互通、通用请求等企业级能力时,参考以下配置,兼顾可维护性与扩展性。
4.1 安装完整依赖
bash
# 核心包(必装)
pnpm i @spc-fe-common/{loader,context,i18n}@latest
# 推荐增强包
pnpm i @spc-fe-common/{bridge,request,types}@latest
pnpm i -D @spc-fe-common/cdn@latest
4.2 完整上下文初始化与启动配置
4.2.1 启动文件拆分(推荐)
分层拆分启动文件,避免逻辑冗余,保障共享依赖加载正常:
typescript
// src/main.ts(启动入口,动态导入避免阻塞)
import('./bootstrap');
typescript
// src/bootstrap.ts(启动核心,统一初始化与渲染)
import './index';
import { runEnterpriseApp } from './index';
runEnterpriseApp();
4.2.2 全局上下文初始化(核心)
注入请求、国际化实例,供主/子应用共享,统一全局能力:
typescript
import { ReactRender } from '@spc-fe-common/bridge';
import { setContext } from '@spc-fe-common/context';
import I18n from '@spc-fe-common/i18n';
import { RequestFactory } from '@spc-fe-common/request';
import React from 'react';
import App from './App';
// 初始化全局上下文,注入核心实例
const initEnterpriseContext = async () => {
// 1. 全局请求实例(统一接口配置)
const globalRequest = RequestFactory.getInstance({
baseURL: process.env.REACT_APP_API_BASE_URL,
timeout: 10000,
requestInterceptors: [(config) => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
}]
});
setContext('request', globalRequest, true);
// 2. 国际化实例(同步项目多语言配置)
const i18nInstance = I18n.getInstance({
resourceIds: [4726], // 替换为项目实际 ID
projects: ['your-project'] // 替换为当前项目名称
});
await i18nInstance.initialization();
setContext('i18n', i18nInstance, true);
};
// 企业级应用启动方法,适配多框架集成
export const runEnterpriseApp = async () => {
try {
await initEnterpriseContext();
ReactRender({ el: '#app', Component: <App /> }); // 桥接渲染
} catch (error) {
console.error('企业级应用启动失败:', error);
throw error;
}
};
4.3 Webpack 高级配置
优先使用统一构建包简化配置,需高度自定义时采用传统配置,保障热更新等关键能力。
4.3.1 统一构建包配置(推荐)
内置热更新、CDN 管理等能力,零配置开箱即用:
javascript
// sc.react.config.js(统一构建包核心配置)
module.exports = {
moduleName: 'HostApp', // 主应用唯一模块名
uniqueName: 'hostapp', // 主应用唯一标识
webpackConfig: {
plugins: [/* 按需添加自定义插件 */],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.less', '.css']
}
},
};
核心优势:自动集成热更新插件、CDN 控制、全局变量注入,与 spc-fe-common 生态无缝兼容
4.3.2 传统 Webpack 配置(自定义场景)
需高度自定义时使用,核心配置微前端必备能力:
javascript
// webpack.config.js(传统自定义配置)
const webpack = require('webpack');
const { LoaderReactRefreshPlugin } = require('@spc-fe-common/loader/webpack/plugins');
module.exports = {
mode: process.env.NODE_ENV || 'development',
plugins: [
new LoaderReactRefreshPlugin(), // 微前端热更新核心(必配)
new webpack.DefinePlugin({
'process.env.REACT_APP_API_BASE_URL': JSON.stringify(process.env.REACT_APP_API_BASE_URL)
})
],
devServer: {
hot: true, // 开启热更新,与插件配合
port: 8080,
historyApiFallback: true, // 解决 history 路由刷新 404
proxy: { /* 按需配置接口代理 */ }
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: { '@': path.resolve(__dirname, 'src') }
}
};
关键注意:LoaderReactRefreshPlugin 与 devServer.hot 需同时配置,否则热更新失效;传统配置需手动管理 CDN、全局变量,推荐优先使用统一构建包。
4.4 Loader 高级配置
Loader 是子应用加载核心,支持 3 种配置方式,根据业务需求选择。
4.4.1 useLoader Hook 配置(推荐)
基于 Hook 封装,简洁易用,支持 MF 子应用与 Qiankun 旧应用共存:
typescript
import { i18n } from '@spc-fe-common/context';
import { Lang } from '@spc-fe-common/i18n';
import { useLoader as originalUseLoader } from '@spc-fe-common/loader';
// 企业级子应用加载器,同步全局配置
export const useEnterpriseSubAppLoader = () => {
const { ReactRoutes, appRoutes } = originalUseLoader({
routerMode: 'history', // 与主应用路由模式一致
lang: i18n.lang as Lang, // 同步全局语言
globalContext: {
userInfo: i18n.context.get('userInfo'), // 透传全局用户信息
appEnv: process.env.NODE_ENV
},
modules: [
// 标准 MF 子应用(新开发首选)
{ name: 'YourSubApp', entryUrl: '/subapp/remoteEntry.js', type: 'react' },
// Qiankun 旧应用兼容(无需改造旧应用)
{
name: 'LegacyReactApp',
entryUrl: '/legacy-react/index.html',
loadType: 'sandbox', // 沙箱隔离,避免冲突
type: 'react',
sandbox: { activeRule: '/legacy-react', container: 'legacy-react-container' }
},
],
});
return { ReactRoutes, appRoutes };
};
4.4.2 直接 new Loader 配置(自定义场景)
适用于需高度定制加载逻辑的场景(如加载状态监控):
typescript
import { Loader } from '@spc-fe-common/loader';
// 自定义 Loader 初始化,支持回调与错误处理
export const initCustomLoader = async () => {
const loader = new Loader({
isDev: process.env.NODE_ENV === 'development',
routerMode: 'history',
lang: 'en',
modules: [{ name: 'YourSubApp', entryUrl: '/subapp/remoteEntry.js', type: 'react' }],
onModuleLoaded: (data) => console.log('模块加载完成:', data), // 加载回调
onError: (error) => console.error('子应用加载失败:', error), // 错误处理
});
try {
loader.run();
await loader.cacheHandler.getAllModulesDonePromise();
const appRoutes = loader.getModulesRoutes('federation');
const ReactRoutes = loader.getModulesRoutesDOM('federation', 'react');
return { loader, appRoutes, ReactRoutes };
} catch (error) {
console.error('Loader 初始化失败:', error);
throw error;
}
};
4.4.3 应用中心模式
实现子应用分级管理与动态加载:
typescript
import { Loader } from '@spc-fe-common/loader';
// 应用中心模式 Loader 初始化
export const initLoaderWithPortal = async () => {
const loader = new Loader({
isDev: process.env.NODE_ENV === 'development',
mode: 'react',
routerMode: 'history',
lang: 'en',
portalId: 'your-portal-id', // 门户唯一标识
name: 'your-app-name', // 应用中心名称
region: 'your-region', // 多区域部署用
});
try {
await loader.runAsync({ portalId: 'your-portal-id', name: 'your-app-name', region: 'your-region' });
await loader.cacheHandler.getAllModulesDonePromise();
const appCenterConfigs = loader.getAppCenterPortalData();
const appRoutes = loader.getModulesRoutes('federation');
const ReactRoutes = loader.getModulesRoutesDOM('federation', 'react');
return { loader, appCenterConfigs, appRoutes, ReactRoutes };
} catch (error) {
console.error('应用中心模式 Loader 初始化失败:', error);
throw error;
}
};
4.5 主应用挂载子应用路由(企业级优化)
结合 spc-ui-react 组件库,优化加载状态与国际化适配:
typescript
import '@/common/styles/index.less';
import { i18n } from '@spc-fe-common/context';
import { PlainObject } from '@spc-fe-common/types';
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch } from 'react-router-dom';
import { ConfigProvider, Spin } from 'spc-ui-react';
import { useEnterpriseSubAppLoader } from './loader';
// 同步 spc-ui-react 国际化配置
const useSpcUILocale = () => {
const [locale, setLocale] = useState<PlainObject>({});
useEffect(() => {
const loadLocale = async () => {
try {
const { locales } = __DEV__ ? require('spc-ui-react/dist/spc-ui-react-with-locales') : await import('spc-ui-react');
const spcUILocales = {
en: locales.en_US, id: locales.id_ID, 'ms-my': locales.ms_MY,
'pt-br': locales.pt_BR, th: locales.th_TH, ph: locales.tl_PH,
vi: locales.vi_VN, 'zh-tw': locales.zh_TW, 'zh-cn': locales.zh_CN
};
setLocale(spcUILocales[i18n?.locale || i18n?.lang] || spcUILocales.en);
} catch (e) {
console.error('spc-ui-react 国际化资源加载失败:', e);
setLocale({});
}
};
loadLocale();
}, [i18n?.lang, i18n?.locale]);
return locale;
};
// 主应用根组件(企业级优化)
const App = () => {
const { ReactRoutes } = useEnterpriseSubAppLoader();
const [loading, setLoading] = useState(false);
const spcUILocale = useSpcUILocale();
return (
<React.StrictMode>
<ConfigProvider
prefixCls={__SPCUI_REACT_PREFIX__} // 统一 UI 前缀,避免样式冲突
rcPrefixCls={__SPCUI_REACT_RC_PREFIX__}
locale={spcUILocale}
>
<Spin size="large" mask tips="Loading" spinning={loading}>
<Router>
<Switch>
{ReactRoutes} {/* 挂载子应用路由 */}
{/* 主应用自身路由可在此添加 */}
</Switch>
</Router>
</Spin>
</ConfigProvider>
</React.StrictMode>
);
};
export default App;
五、子应用标准配置
子应用需基于 Module Federation 构建,支持独立运行和集成运行两种模式。
5.1 安装依赖
bash
# 核心包
pnpm i @spc-fe-common/{loader,context,i18n}@latest
# 构建工具
pnpm i -D @spc-fe-common/build@latest
# 推荐增强包
pnpm i @spc-fe-common/{bridge,request,types}@latest
# React 依赖(固定版本,避免冲突)
pnpm i react@17.0.0 react-dom@17.0.0 react-router-dom@5.3.0
pnpm i spc-ui-react@latest
pnpm i -D @types/react@17.0.0 @types/react-dom@17.0.0 @types/react-router-dom@5.3.0
5.2 Module Federation 配置
javascript
// sc.react.config.js(子应用 Webpack 配置)
const webpack = require('webpack');
const { ModuleFederationPlugin } = webpack.container;
module.exports = {
moduleName: 'SubAppReact', // 子应用模块名称
uniqueName: 'subapp-react', // 子应用唯一标识
webpackConfig: {
plugins: [
new ModuleFederationPlugin({
name: 'SubAppReact', // 与主应用加载时的名称一致
filename: 'remoteEntry.react.js', // 远程入口文件名
exposes: { './module': './src/moduleEntries/react-project' } // 暴露核心模块
})
]
}
};
5.3 暴露对象与路由配置
暴露对象(src/moduleEntries/react-project.tsx)
typescript
import { depends } from '@spc-fe-common/loader';
import { reactRoutes } from '@/common/router';
import packageInfo from '../../package.json';
// 子应用暴露对象,供主应用加载时获取
export default {
routerMode: 'history', // 与主应用路由模式一致
reactRoutes, // 子应用路由配置
packageInfo, // 版本信息,用于版本管理
depends // 依赖配置,告知主应用需共享的依赖
};
路由配置(src/common/router/react.tsx)
typescript
import { ReactRouterFactory } from '@spc-fe-common/bridge';
import routeModules from './modules';
// 生成标准化子应用路由
export const { routes: reactRoutes, Routes: ReactRoutes } = new ReactRouterFactory({
uniquename: process.env.REACT_APP_UNIQUENAME || 'SubApp',
basename: process.env.REACT_APP_ROUTE_NAME_SPACE || '', // 可选路由前缀
routeModules, // 子应用页面路由集合
hash: false // 与主应用一致,使用 history 模式
});
5.4 独立运行配置
App.tsx 根组件
tsx
import { ReactRouterFactory } from '@spc-fe-common/bridge';
import { PlainObject } from '@spc-fe-common/types';
import React, { useEffect, useState } from 'react';
import { ConfigProvider, Spin } from 'spc-ui-react';
import routeModules from '@/common/router/modules';
import '@/common/styles/index.less';
export function App() {
const [loading, setLoading] = useState(false);
const [locale, setLocale] = useState<PlainObject>({});
// 初始化路由工厂
const { Routes: ReactRoutes } = new ReactRouterFactory({
uniquename: process.env.REACT_APP_UNIQUENAME || 'SubApp',
basename: process.env.REACT_APP_ROUTE_NAME_SPACE || '',
routeModules,
hash: false,
});
// 加载 UI 组件国际化资源
useEffect(() => {
const loadLocale = async () => {
try {
const { locales } = __DEV__ ? require('spc-ui-react') : await import('spc-ui-react');
setLocale(locales.en_US);
} catch (e) {
console.error('Failed to load locales:', e);
setLocale({});
}
};
loadLocale();
}, []);
return (
<React.StrictMode>
<ConfigProvider prefixCls="spc-react" rcPrefixCls="spc-react-rc" locale={locale}>
<Spin size="large" mask tips="Loading" spinning={loading}>
<ReactRoutes />
</Spin>
</ConfigProvider>
</React.StrictMode>
);
}
export default App;
index.ts 启动文件
typescript
import { ReactRender } from '@spc-fe-common/bridge';
import React from 'react';
import App from './App';
// 子应用启动方法,适配独立/集成两种模式
export const runSubApp = async () => {
ReactRender({ el: '#app', Component: <App /> });
};
// 独立运行时直接启动(非 Qiankun、非 MF 集成环境)
if (!window.__POWERED_BY_QIANKUN__ && !window.__WEBPACK_SHARE_SCOPES__) {
runSubApp();
}
六、环境变量与工程化配置
6.1 内置全局变量
使用 @spc-fe-common/build 时,自动注入以下核心全局变量:
| 全局变量 | 说明 |
|---|---|
| UNIQUENAME | 应用唯一标识 |
| DEV | 开发环境标识 |
| IS_ALONE | 独立运行模式标识 |
6.2 CDN 优化配置
在 .env 文件中配置,减少本地打包体积:
bash
# .env 配置(CDN 优化)
USE_REACT_CDN=true # 生产环境推荐开启,启用 React CDN 加载
NODE_ENV=production # 环境标识:production/development
ALONE=true # 独立运行模式:true(独立开发)/false(集成运行)
6.3 故障排查
| 问题 | 解决方案 |
|---|---|
| 全局变量未定义 | 检查 sc.react.config.js 或 .env 配置 |
| CDN 加载失败 | 关闭 USE_REACT_CDN,降级到本地资源 |
| 样式冲突 | 统一使用 SPCUI_REACT_PREFIX 前缀 |
七、高级特性
7.1 全局上下文通信
全应用共享数据/方法,支持监听数据变化,避免内存泄漏:
typescript
import { request, i18n, userInfo, $gt, onContextChange } from '@spc-fe-common/context';
import { useEffect } from 'react';
// 监听用户信息变更
useEffect(() => {
const unsubscribe = onContextChange('userInfo', (event) => {
console.log('用户信息变更:', event.value);
});
return () => unsubscribe(); // 组件卸载取消监听
}, []);
// 核心实例说明:
// request:全局请求实例(直接调用 get/post)
// i18n:全局国际化实例(可调用 changeLanguage 切换语言)
// $gt:全局翻译函数(如 $gt('common.confirm'))
// userInfo:全局用户信息
7.2 自定义请求配置
创建自定义请求实例,适配特殊接口需求:
typescript
import { setContext } from '@spc-fe-common/context';
import { RequestFactory } from '@spc-fe-common/request';
// 创建自定义请求实例
const customRequest = RequestFactory.getInstance({
baseURL: '/api/custom', // 自定义基础地址
timeout: 10000,
requestInterceptors: [(config) => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
}]
});
// 全局共享自定义请求实例
setContext('customRequest', customRequest, true);
7.3 多请求实例管理
区分业务请求与文件上传请求,避免冲突,提升可维护性:
typescript
import { setContext } from '@spc-fe-common/context';
import { RequestFactory } from '@spc-fe-common/request';
// 业务请求实例
const businessRequest = RequestFactory.getInstance({
baseURL: '/api/business',
timeout: 5000
});
// 文件上传请求实例(适配大文件,延长超时时间)
const uploadRequest = RequestFactory.getInstance({
baseURL: '/api/upload',
timeout: 30000,
headers: { 'Content-Type': 'multipart/form-data' }
});
// 注入全局上下文,供全应用调用
setContext('businessRequest', businessRequest);
setContext('uploadRequest', uploadRequest);
7.4 热更新配置
LoaderReactRefreshPlugin 保障微前端热更新兼容性,配置如下:
主应用配置
javascript
const { LoaderReactRefreshPlugin } = require('@spc-fe-common/loader/webpack/plugins');
module.exports = {
plugins: [new LoaderReactRefreshPlugin()],
devServer: { hot: true }
};
子应用配置
javascript
const { LoaderModuleFedearionShared, LoaderReactRefreshPlugin } = require('@spc-fe-common/loader/webpack/plugins');
module.exports = {
plugins: [
new ModuleFederationPlugin({ shared: LoaderModuleFedearionShared }),
new LoaderReactRefreshPlugin()
]
};
7.5 沙箱模式配置
适配旧应用隔离需求,避免环境冲突:
typescript
{
name: 'LegacyReactApp',
entryUrl: '/legacy-react/remoteEntry.js',
loadType: 'sandbox',
type: 'react',
sandbox: {
activeRule: '/legacy-react', // 访问该路径激活
container: 'legacy-react-container', // 挂载容器 ID
experimentalStyleIsolation: true, // 样式隔离
strictStyleIsolation: false,
loose: false
}
}
八、Qiankun 迁移
8.1 迁移三阶段
- 主应用改造:沙箱模式加载现有 Qiankun 子应用
- 子应用升级:逐步将非核心子应用改造为 MF 模式
- 完全迁移:所有子应用完成 MF 改造,移除沙箱配置
8.2 兼容配置
typescript
{
name: 'LegacyQiankunApp', // 旧应用唯一名称
entryUrl: '/legacy-app/index.html', // 旧应用入口 HTML
loadType: 'sandbox', // 沙箱隔离
type: 'react', // 旧应用技术栈
sandbox: { activeRule: '/legacy-app', container: 'legacy-app-container' }
}
九、常见问题排查
- 子应用加载失败:检查 entryUrl 正确性、子应用 MF 配置与主应用名称一致
- Hook 调用错误:确保主子应用 React 版本一致,检查共享依赖配置
- 热更新不生效:确认 LoaderReactRefreshPlugin 配置,devServer.hot 设为 true
- 样式冲突:启用 CSS 隔离,统一 UI 组件前缀
十、总结
10.1 接入策略选择
| 场景 | 推荐方案 |
|---|---|
| 快速试验/急需加载子应用 | 最小化接入(loader、context、i18n 核心包) |
| 新项目开发 | 标准接入(核心包+bridge+request+types) |
| 企业级生产项目 | 完整接入全生态能力 |
10.2 核心优势
快速接入、技术栈无关、向后兼容 Qiankun、渐进式增强、完整生态覆盖开发全链路,助力企业级微前端高效落地。