各位朋友,大家好!我是你们的老朋友,今天咱们来聊聊Vue微前端这事儿,保证让你听完之后,感觉像打通了任督二脉一样,思路清晰,下笔如有神!
开场白:微前端,前端的“分久必合,合久必分”?
话说天下大势,分久必合,合久必分。前端架构也一样,从最初的刀耕火种,到后来的模块化、组件化,再到现在的微前端,这简直就是一部前端架构的演进史诗啊!
微前端,顾名思义,就是把一个原本巨大的前端应用拆分成多个小型、自治的应用,每个应用可以独立开发、独立部署、独立运行。就像把一艘航空母舰拆成几艘巡洋舰,虽然单艘船的火力不如航母,但灵活性大大提升了。
第一部分:微前端架构设计,别让你的“航母”变成“泰坦尼克”!
微前端架构有很多种实现方式,各有优缺点,咱们先来盘点一下:
-
Iframe方案:最简单粗暴的朋友
- 优点: 天然隔离,技术栈无关,兼容性好。
- 缺点: 体验差,路由同步困难,通信复杂,性能损耗大。
Iframe就像在一个页面里开辟了一个新的世界,两个世界之间互不干扰,但也正是这种完全的隔离,导致了通信和体验上的问题。想象一下,你要在一个Iframe里的按钮点击后,改变父页面的标题,那得费多大劲儿啊!
适用场景:老旧系统改造,对体验要求不高,或者确实需要强隔离的场景。
-
Web Components方案:组件化的极致追求
- 优点: 真正的组件化,封装性好,复用性高。
- 缺点: 学习成本高,兼容性问题,生态不完善。
Web Components就像乐高积木,每个积木都是一个独立的组件,可以自由组合。但是,Web Components的坑也不少,polyfill、shadow DOM、事件穿透等等,都需要仔细研究。
适用场景:对组件化要求高,有长期技术投入的团队。
-
Webpack Module Federation方案:模块共享的理想主义者
- 优点: 代码共享,性能好,开发体验好。
- 缺点: 技术栈绑定,依赖管理复杂,安全性问题。
Module Federation就像一个共享的代码仓库,每个应用都可以从中获取需要的模块。但是,这也意味着你的所有应用都必须使用相同的技术栈(Webpack),而且依赖管理也变得非常复杂。
适用场景:技术栈统一,对性能要求高,开发团队协作紧密的场景。
-
Single-SPA方案:路由劫持的魔法师
- 优点: 技术栈无关,灵活,可渐进式改造。
- 缺点: 路由劫持,有一定的性能损耗,需要手动处理生命周期。
Single-SPA就像一个路由代理,它可以拦截所有的路由请求,并根据路由规则将请求转发到不同的子应用。这种方式可以实现技术栈无关,而且可以渐进式地改造现有应用。
适用场景:技术栈多样,需要渐进式改造现有应用,对灵活性要求高的场景。
结论:没有最好的方案,只有最合适的方案!
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Iframe | 天然隔离,技术栈无关,兼容性好 | 体验差,路由同步困难,通信复杂,性能损耗大 | 老旧系统改造,对体验要求不高,或者确实需要强隔离的场景 |
Web Components | 真正的组件化,封装性好,复用性高 | 学习成本高,兼容性问题,生态不完善 | 对组件化要求高,有长期技术投入的团队 |
Webpack MF | 代码共享,性能好,开发体验好 | 技术栈绑定,依赖管理复杂,安全性问题 | 技术栈统一,对性能要求高,开发团队协作紧密的场景 |
Single-SPA | 技术栈无关,灵活,可渐进式改造 | 路由劫持,有一定的性能损耗,需要手动处理生命周期 | 技术栈多样,需要渐进式改造现有应用,对灵活性要求高的场景 |
第二部分:Single-SPA实战,手把手教你打造微前端“航空母舰”!
咱们就以Single-SPA为例,手把手教你打造一个简单的Vue微前端架构。
1. 准备工作:安装Node.js、npm或yarn,并创建一个空的文件夹。
2. 创建根应用(Root Config):负责加载和注册子应用。
* 安装Single-SPA:
```bash
npm install single-spa --save
```
* 创建`index.html`:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Micro-Frontends Demo</title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/single-spa/lib/umd/single-spa.min.js"></script>
<script>
// 注册子应用
singleSpa.registerApplication(
'app1', // 子应用名称
() => import('./app1/app1.js'), // 子应用加载函数
location => location.pathname.startsWith('/app1') // 子应用激活函数
);
singleSpa.registerApplication(
'app2',
() => import('./app2/app2.js'),
location => location.pathname.startsWith('/app2')
);
// 启动Single-SPA
singleSpa.start();
</script>
</body>
</html>
```
* **代码解读:**
* `singleSpa.registerApplication`:注册子应用,需要三个参数:
* `name`:子应用名称,必须唯一。
* `loadingFunction`:加载子应用的函数,通常使用`import()`动态加载。
* `activityFunction`:激活子应用的函数,当满足条件时,子应用会被激活。
* `singleSpa.start()`:启动Single-SPA,开始监听路由变化。
3. 创建子应用(Sub Applications):独立的Vue应用。
* 创建`app1`和`app2`文件夹。
* 在每个文件夹中,使用`vue-cli`创建Vue项目:
```bash
cd app1
vue create . --default
cd ../app2
vue create . --default
```
* 修改`app1/src/main.js`:
```javascript
import Vue from 'vue'
import App from './App.vue'
import singleSpaVue from 'single-spa-vue';
Vue.config.productionTip = false
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
el: '#vue-app', // 挂载点
render: h => h(App)
}
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
```
* 修改`app1/public/index.html`:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="vue-app"></div>
<!-- built files will be auto injected -->
</body>
</html>
```
* **代码解读:**
* `single-spa-vue`:Single-SPA官方提供的Vue集成插件,可以方便地将Vue应用转换为Single-SPA子应用。
* `bootstrap`、`mount`、`unmount`:子应用的生命周期函数,Single-SPA会根据子应用的激活状态调用这些函数。
* 挂载点:在`appOptions`中指定挂载点,这里我们使用`#vue-app`。
* `app2`的配置也类似,只需要修改挂载点和路由即可。
4. 配置Webpack:让根应用能够加载子应用。
* 在根目录下创建一个`webpack.config.js`:
```javascript
const path = require('path');
module.exports = {
mode: 'development',
entry: './index.html',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
static: {
directory: path.join(__dirname, '/'),
},
compress: true,
port: 9000,
historyApiFallback: true, // 解决刷新404问题
},
module: {
rules: [
{
test: /.html$/i,
loader: "html-loader",
},
],
},
};
```
-
修改
app1
和app2
的vue.config.js文件,添加以下内容,是为了将子应用打包成umd格式module.exports = { configureWebpack: { output: { library: 'app1', // 对应singleSpa.registerApplication的name libraryTarget: 'umd', }, }, devServer: { port: 8081, // 修改端口,避免冲突 headers: { 'Access-Control-Allow-Origin': '*', }, }, };
-
代码解读:
webpack-dev-server
:提供本地开发服务器,方便调试。historyApiFallback
:解决刷新404问题,Single-SPA需要通过路由来控制子应用的激活状态。
-
5. 运行:启动根应用和子应用。
* 分别启动`app1`和`app2`:
```bash
cd app1
npm run serve
cd ../app2
npm run serve
```
* 启动根应用:
```bash
npm install webpack webpack-cli webpack-dev-server html-loader --save-dev
npx webpack serve --config webpack.config.js
```
* 在浏览器中访问`http://localhost:9000/app1`和`http://localhost:9000/app2`,就可以看到两个子应用了。
第三部分:子应用通信,让你的“巡洋舰”协同作战!
子应用之间需要通信,才能实现更复杂的功能。常见的通信方式有:
-
Custom Events:事件驱动的通信方式
- 优点: 简单易用,解耦性好。
- 缺点: 只能传递简单数据,无法共享状态。
Custom Events就像广播电台,一个应用可以发布一个事件,其他应用可以监听这个事件。
-
示例:
-
在
app1
中发布事件:window.dispatchEvent(new CustomEvent('app1-event', { detail: { message: 'Hello from App1!' } }));
-
在
app2
中监听事件:window.addEventListener('app1-event', (event) => { console.log(event.detail.message); // 输出 "Hello from App1!" });
-
-
Shared Global State:共享全局状态
- 优点: 可以共享复杂数据,方便管理。
- 缺点: 耦合性高,容易出现状态冲突。
Shared Global State就像一个公共的仓库,所有应用都可以访问和修改其中的数据。
-
示例:
-
创建一个共享状态:
window.sharedState = { user: { name: 'John Doe', age: 30 } };
-
在
app1
中修改状态:window.sharedState.user.name = 'Jane Doe';
-
在
app2
中访问状态:console.log(window.sharedState.user.name); // 输出 "Jane Doe"
-
-
Props:组件之间的属性传递
- 优点: 数据流清晰,易于维护。
- 缺点: 需要修改组件结构,不适合传递复杂数据。
Props就像组件之间的桥梁,可以将数据从一个组件传递到另一个组件。
-
示例:
-
在根应用中,将数据传递给子应用:
singleSpa.registerApplication( 'app1', () => import('./app1/app1.js'), location => location.pathname.startsWith('/app1'), { userName: 'John Doe' // 通过props传递数据 } );
-
在
app1
中接收数据:import Vue from 'vue' import App from './App.vue' import singleSpaVue from 'single-spa-vue'; Vue.config.productionTip = false const vueLifecycles = singleSpaVue({ Vue, appOptions: { el: '#vue-app', render: h => h(App, { props: { userName: singleSpa.getMountedApps()[0].customProps.userName // 获取props } }) } }); export const bootstrap = vueLifecycles.bootstrap; export const mount = vueLifecycles.mount; export const unmount = vueLifecycles.unmount;
-
第四部分:路由隔离,让你的“巡洋舰”各司其职!
路由隔离是微前端架构的关键,它可以确保每个子应用都有自己的路由空间,互不干扰。
-
URL Prefixing:URL前缀
- 原理: 为每个子应用分配一个URL前缀,例如
/app1
、/app2
。 - 优点: 简单易用,易于理解。
- 缺点: URL结构不美观,用户体验较差。
我们上面例子用的就是这种。
- 原理: 为每个子应用分配一个URL前缀,例如
-
Subdomain Routing:子域名路由
- 原理: 为每个子应用分配一个子域名,例如
app1.example.com
、app2.example.com
。 - 优点: URL结构美观,用户体验好。
- 缺点: 需要配置DNS,部署复杂。
- 原理: 为每个子应用分配一个子域名,例如
-
Abstracted Routing:抽象路由
- 原理: 使用一个统一的路由管理中心,将路由请求转发到不同的子应用。
- 优点: 灵活,可定制性强。
- 缺点: 实现复杂,需要维护一个路由管理中心。
第五部分:状态共享,让你的“巡洋舰”保持同步!
状态共享是微前端架构的难点,需要仔细设计,避免状态冲突。
-
Global Event Bus:全局事件总线
- 原理: 使用一个全局的事件总线,子应用可以通过事件总线发布和订阅事件。
- 优点: 解耦性好,易于扩展。
- 缺点: 容易出现事件冲突,难以追踪状态变化。
-
Centralized State Management:集中式状态管理
- 原理: 使用一个中心化的状态管理库,例如Redux、Vuex,所有子应用都可以访问和修改其中的状态。
- 优点: 状态管理清晰,易于追踪状态变化。
- 缺点: 耦合性高,需要所有子应用都使用相同的状态管理库。
-
Shared Module:共享模块
- 原理: 将共享的状态和逻辑封装成一个独立的模块,所有子应用都可以引入这个模块。
- 优点: 代码复用性高,易于维护。
- 缺点: 耦合性高,需要仔细设计模块接口。
总结:微前端,一种思维方式!
微前端不仅仅是一种技术架构,更是一种思维方式。它强调的是独立、自治、可组合。在设计微前端架构时,需要仔细考虑业务需求、团队结构、技术栈等因素,选择最合适的方案。
希望今天的讲解能帮助你更好地理解微前端,并在实际项目中应用它。记住,没有最好的方案,只有最合适的方案!
好了,今天的讲座就到这里,谢谢大家!下次有机会再和大家分享更多的技术心得。如果大家有什么问题,欢迎随时提问。