TypeScript模块联邦在微前端架构中的配置
微前端架构允许将一个大型前端应用拆分为多个独立开发、部署的小型应用。TypeScript结合Webpack 5的模块联邦(Module Federation)插件,能高效实现跨应用共享代码与组件。以下步骤教你从零开始配置。
准备工作
- 确保环境:安装 Node.js
>=16和 npm>=8。 - 创建两个项目:一个作为宿主应用(Host),另一个作为远程应用(Remote)。例如:
mkdir host-app remote-app - 分别初始化项目:
cd host-app && npm init -y cd ../remote-app && npm init -y
配置远程应用(提供共享模块)
-
安装依赖:
cd remote-app npm install typescript ts-loader webpack webpack-cli html-webpack-plugin --save-dev npm install react react-dom # 假设使用 React,可替换为其他框架 -
创建 TypeScript 配置文件
tsconfig.json:{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "outDir": "./dist" } } -
创建入口文件
src/bootstrap.tsx(避免直接暴露顶层组件):import React from 'react'; import ReactDOM from 'react-dom/client'; const RemoteButton = () => <button>来自 Remote 的按钮</button>; export { RemoteButton }; // 仅在独立运行时挂载 if (!window.remoteAppLoaded) { const root = ReactDOM.createRoot(document.getElementById('root')!); root.render(<RemoteButton />); window.remoteAppLoaded = true; } -
配置 Webpack
webpack.config.js:const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { entry: './src/bootstrap.tsx', mode: 'development', devServer: { port: 3001, historyApiFallback: true, }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: 'ts-loader', }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), new ModuleFederationPlugin({ name: 'remoteApp', filename: 'remoteEntry.js', exposes: { './Button': './src/bootstrap.tsx', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ], }; ``` 5. **创建 `public/index.html`**: ```html <!DOCTYPE html> <html> <head><title>Remote App</title></head> <body><div id="root"></div></body> </html> ``` 6. **添加启动脚本到 `package.json`**: ```json "scripts": { "start": "webpack serve" } ``` 7. **启动远程应用**: ```bash npm start ``` 此时应用运行在 `http://localhost:3001`,并生成联邦入口 `http://localhost:3001/remoteEntry.js`。 --- ## 配置宿主应用(消费远程模块) 1. **安装相同依赖**(版本需一致): ```bash cd host-app npm install typescript ts-loader webpack webpack-cli html-webpack-plugin --save-dev npm install react react-dom ``` 2. **创建 `tsconfig.json`**(内容同远程应用)。 3. **创建入口文件 `src/index.tsx`**: ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; // 动态导入远程模块 const loadRemoteButton = async () => { const remoteModule = await import('remoteApp/Button'); return remoteModule.RemoteButton; }; const App = () => { const [RemoteButton, setRemoteButton] = React.useState<any>(null); React.useEffect(() => { loadRemoteButton().then(setRemoteButton); }, []); return ( <div> <h1>宿主应用</h1> {RemoteButton ? <RemoteButton /> : <p>加载中...</p>} </div> ); }; const root = ReactDOM.createRoot(document.getElementById('root')!); root.render(<App />); ``` 4. **配置 Webpack `webpack.config.js`**: ```js const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { entry: './src/index.tsx', mode: 'development', devServer: { port: 3000, }, resolve: { extensions: ['.ts', '.tsx', '.js'], alias: { // 确保类型检查通过(开发时) 'remoteApp/Button': 'remoteApp/Button', }, }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: 'ts-loader', }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), new ModuleFederationPlugin({ name: 'hostApp', remotes: { remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ], }; -
创建
public/index.html(内容同远程应用)。 -
添加启动脚本到
package.json:"scripts": { "start": "webpack serve" } -
声明远程模块类型(解决 TypeScript 报错)
创建src/types.d.ts:declare module 'remoteApp/Button' { export const RemoteButton: React.FC; } -
启动宿主应用:
npm start访问
http://localhost:3000,页面将显示“宿主应用”和“来自 Remote 的按钮”。
关键配置说明
模块联邦的核心在于 ModuleFederationPlugin 的参数:
| 参数 | 作用 | 示例值 |
|---|---|---|
name |
当前应用的全局唯一名称 | 'hostApp' |
filename |
(Remote 端)生成的联邦入口文件名 | 'remoteEntry.js' |
exposes |
(Remote 端)对外暴露的模块路径映射 | { './Button': './src/bootstrap.tsx' } |
remotes |
(Host 端)声明可消费的远程应用 | { remoteApp: 'remoteApp@http://.../remoteEntry.js' } |
shared |
共享依赖,避免重复加载 | { react: { singleton: true, requiredVersion: '^18.0.0' } } |
类型安全增强
为避免 any 类型,可提取共享组件的接口:
-
在远程应用中创建
src/components/Button.tsx:import React from 'react'; export interface ButtonProps { onClick?: () => void; } export const RemoteButton: React.FC<ButtonProps> = ({ onClick }) => ( <button onClick={onClick}>远程按钮</button> ); -
修改
bootstrap.tsx导出:export { RemoteButton, type ButtonProps } from './components/Button'; -
在宿主应用的
types.d.ts中同步类型:declare module 'remoteApp/Button' { import type { ButtonProps } from 'remoteApp/Button'; export const RemoteButton: React.FC<ButtonProps>; export type { ButtonProps }; } -
在宿主应用中使用强类型:
import type { ButtonProps } from 'remoteApp/Button'; const MyButton: React.FC<ButtonProps> = (props) => { const RemoteButton = ...; // 从异步加载获取 return RemoteButton ? <RemoteButton {...props} /> : null; };
生产环境注意事项
-
动态远程地址:不要硬编码
http://localhost:3001。可通过环境变量注入:// webpack.config.js const remoteUrl = process.env.REMOTE_URL || 'http://localhost:3001'; new ModuleFederationPlugin({ remotes: { remoteApp: `remoteApp@${remoteUrl}/remoteEntry.js`, }, }); -
错误边界处理:远程模块加载失败时降级:
const App = () => { const [error, setError] = React.useState<Error | null>(null); const [RemoteButton, setRemoteButton] = React.useState<any>(null); React.useEffect(() => { loadRemoteButton() .then(setRemoteButton) .catch(setError); }, []); if (error) return <div>加载远程组件失败</div>; return <>{RemoteButton ? <RemoteButton /> : <p>加载中...</p>}</>; }; -
共享依赖版本对齐:所有微应用必须使用兼容的
react和react-dom版本,否则因singleton: true导致运行时冲突。
启动宿主与远程应用后,浏览器控制台无报错且按钮正常渲染,即表示配置成功。

暂无评论,快来抢沙发吧!