各位观众老爷,大家好!今天咱们不开车,聊点技术。相信大家或多或少都听过“微前端”这个词,它就像前端界的变形金刚,能把一个庞大的应用拆解成一个个小而精悍的模块,独立开发、独立部署,最后再组装起来。这好处嘛,那是杠杠的,团队协作更高效,代码维护更轻松,简直就是程序员的福音!
而要实现微前端,其中一个非常流行的技术就是 Webpack 的 Module Federation(模块联邦)。 它允许我们将应用拆分成独立的构建单元,这些单元可以动态地在运行时共享代码。 听起来是不是有点玄乎? 别怕,今天我就把这个“玄学”的东西,用大白话掰开了揉碎了讲清楚,保证你听完之后,也能玩转 Module Federation,让你的 Vue 项目飞起来!
咱们今天就以一个实际的 Vue 项目为例,手把手教你如何配置 Webpack 的 Module Federation,实现微前端架构下的代码共享。
一、 啥是 Module Federation? 为什么要用它?
在开始配置之前,咱们先来聊聊 Module Federation 到底是个啥玩意儿。 简单来说,它就是 Webpack 提供的一种代码共享机制,允许不同的 Webpack 构建之间共享模块。 想象一下,你有一个组件库,里面有很多常用的组件,如果每个微前端应用都自己维护一份,那得多浪费啊! 有了 Module Federation,我们就可以把这个组件库作为一个“远程模块”,让其他应用直接引用,既节省了空间,又保证了代码的一致性。
用表格来总结一下 Module Federation 的优点:
优点 | 描述 |
---|---|
代码共享 | 不同的应用可以共享代码,避免重复开发。 |
独立部署 | 每个微前端应用都可以独立部署,互不影响。 |
运行时集成 | 模块的加载和集成发生在运行时,更加灵活。 |
技术栈无关 | 可以与其他微前端技术(如 iframe、single-spa)结合使用。 |
二、 项目准备: 三个“小弟”
为了演示 Module Federation,我们需要准备三个 Vue 项目:
app-shell
(主应用): 负责应用的整体布局和路由管理。app-one
(微应用 1): 一个简单的计数器应用。app-two
(微应用 2): 显示用户信息和调用app-one
中计数器的一个应用。
你可以使用 Vue CLI 来快速创建这些项目:
vue create app-shell
vue create app-one
vue create app-two
每个项目都选择默认配置即可。
三、 app-shell
(主应用) 配置
app-shell
作为主应用,负责加载和渲染其他的微应用。 它的配置稍微复杂一些,需要安装 webpack
和 @module-federation/webpack-plugin
:
cd app-shell
npm install webpack @module-federation/webpack-plugin -D
然后,在 vue.config.js
文件中配置 Webpack:
const { defineConfig } = require('@vue/cli-service')
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin
module.exports = defineConfig({
transpileDependencies: true,
publicPath: 'http://localhost:8080/', // 端口号要和你的项目一致
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'app_shell', // 必须唯一,作为远程模块的名称
remotes: {
app_one: 'app_one@http://localhost:8081/remoteEntry.js', // 远程模块的名称和地址
app_two: 'app_two@http://localhost:8082/remoteEntry.js',
},
shared: ['vue', 'vue-router'] // 共享的依赖
})
]
},
devServer: {
port: 8080,
}
})
这段代码做了什么呢?
name
: 定义了当前应用的名称,其他应用可以通过这个名称来引用它。remotes
: 定义了需要引用的远程模块,包括模块的名称和remoteEntry.js
的地址。remoteEntry.js
是由远程模块暴露出来的入口文件,包含了模块的信息。shared
: 定义了需要共享的依赖,比如vue
和vue-router
。 这样可以避免重复加载,减少应用的大小。
重要提示: publicPath
的配置非常重要,它决定了应用在运行时加载资源的路径。 在开发环境下,我们通常使用 localhost
加端口号,而在生产环境下,则需要配置为实际的域名或 CDN 地址。
接下来,我们需要在 app-shell
中创建一个组件,用于加载和渲染微应用。 例如,我们可以创建一个名为 MicroApp.vue
的组件:
<template>
<div class="micro-app">
<h2>{{ name }}</h2>
<component :is="component" />
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
export default {
props: {
name: {
type: String,
required: true
},
module: {
type: String,
required: true
},
scope: {
type: String,
required: true
}
},
data() {
return {
component: null
}
},
mounted() {
this.loadModule()
},
methods: {
async loadModule() {
try {
this.component = defineAsyncComponent(() =>
import(/* webpackChunkName: "[request]" */ `${this.scope}/${this.module}`)
)
} catch (error) {
console.error('Failed to load module:', error)
}
}
}
}
</script>
<style scoped>
.micro-app {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
</style>
这个组件使用了 defineAsyncComponent
来异步加载远程模块。 scope
和 module
属性分别对应 webpack.config.js
中 remotes
的键名和要暴露的组件名.
最后,在 app-shell
的 App.vue
中使用 MicroApp
组件:
<template>
<div id="app">
<h1>Main App (app-shell)</h1>
<MicroApp name="App One" scope="app_one" module="./Counter" />
<MicroApp name="App Two" scope="app_two" module="./UserInfo" />
</div>
</template>
<script>
import MicroApp from './components/MicroApp.vue'
export default {
components: {
MicroApp
}
}
</script>
四、 app-one
(微应用 1) 配置
app-one
作为微应用,需要暴露一个组件供其他应用使用。 同样,我们需要安装 webpack
和 @module-federation/webpack-plugin
:
cd app-one
npm install webpack @module-federation/webpack-plugin -D
然后在 vue.config.js
文件中配置 Webpack:
const { defineConfig } = require('@vue/cli-service')
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin
module.exports = defineConfig({
transpileDependencies: true,
publicPath: 'http://localhost:8081/', // 端口号要和你的项目一致
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'app_one', // 必须唯一,作为远程模块的名称
filename: 'remoteEntry.js', // 暴露的入口文件名称
exposes: {
'./Counter': './src/components/Counter.vue' // 暴露的模块
},
shared: ['vue'] // 共享的依赖
})
]
},
devServer: {
port: 8081,
}
})
这段代码的关键在于 exposes
属性,它定义了需要暴露的模块,以及对应的文件路径。 在这里,我们将 src/components/Counter.vue
组件暴露为 ./Counter
模块。
接下来,创建一个简单的计数器组件 src/components/Counter.vue
:
<template>
<div>
<h2>Counter (app-one)</h2>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}
</script>
五、 app-two
(微应用 2) 配置
app-two
的配置与 app-one
类似,也需要安装 webpack
和 @module-federation/webpack-plugin
:
cd app-two
npm install webpack @module-federation/webpack-plugin -D
然后在 vue.config.js
文件中配置 Webpack:
const { defineConfig } = require('@vue/cli-service')
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin
module.exports = defineConfig({
transpileDependencies: true,
publicPath: 'http://localhost:8082/', // 端口号要和你的项目一致
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'app_two', // 必须唯一,作为远程模块的名称
filename: 'remoteEntry.js', // 暴露的入口文件名称
exposes: {
'./UserInfo': './src/components/UserInfo.vue' // 暴露的模块
},
remotes: {
app_one: 'app_one@http://localhost:8081/remoteEntry.js',
},
shared: ['vue'] // 共享的依赖
})
]
},
devServer: {
port: 8082,
}
})
注意,app-two
除了暴露 UserInfo
组件外,还引用了 app_one
的 Counter
组件。
接下来,创建 src/components/UserInfo.vue
组件:
<template>
<div>
<h2>User Info (app-two)</h2>
<p>Name: John Doe</p>
<p>Email: [email protected]</p>
<Counter />
</div>
</template>
<script>
import Counter from 'app_one/Counter' // 引用 app-one 的 Counter 组件
export default {
components: {
Counter
}
}
</script>
六、 启动项目,见证奇迹!
现在,我们可以分别启动这三个项目:
cd app-shell
npm run serve
cd app-one
npm run serve
cd app-two
npm run serve
打开 http://localhost:8080/
,你应该可以看到主应用 app-shell
成功加载了 app-one
的 Counter
组件和 app-two
的 UserInfo
组件。 在 app-two
中,你还可以看到 app-one
的 Counter
组件。
七、 踩坑指南
在配置 Module Federation 的过程中,你可能会遇到一些坑,这里给大家总结一下:
publicPath
配置错误:publicPath
决定了应用加载资源的路径,如果配置错误,会导致资源加载失败。 确保每个应用的publicPath
都指向正确的地址。- 依赖版本冲突: 如果不同的应用使用了不同版本的共享依赖,可能会导致运行时错误。 建议使用
shared
属性来共享依赖,并尽量保持依赖版本一致。 - 模块名称冲突: 如果不同的应用暴露了相同名称的模块,会导致模块加载失败。 确保每个应用的模块名称都是唯一的。
- 循环依赖: 避免出现循环依赖的情况,例如
app-one
依赖app-two
,而app-two
又依赖app-one
。 这样会导致模块加载死循环。 - CORS 问题: 如果你的微前端应用部署在不同的域名下,可能会遇到 CORS 问题。 你需要在服务器端配置 CORS 策略,允许跨域访问。
八、 更高级的玩法
Module Federation 的玩法还有很多,例如:
- 动态加载远程模块: 可以根据用户的行为或路由的变化,动态加载不同的远程模块。
- 共享状态管理: 可以使用 Redux、Vuex 等状态管理工具,在不同的微前端应用之间共享状态。
- 按需加载: 可以使用 Webpack 的代码分割功能,将远程模块拆分成更小的 chunk,按需加载,提高应用的性能。
- 版本控制: 通过维护不同版本的
remoteEntry.js
,可以实现微前端应用的版本控制和灰度发布。
九、 总结
今天,我们一起学习了如何使用 Webpack 的 Module Federation 来构建微前端应用。 希望通过今天的讲解,你能够掌握 Module Federation 的基本概念和配置方法,并在实际项目中灵活运用。
记住,技术是用来解决问题的,不要为了用技术而用技术。 在选择微前端架构时,一定要根据项目的实际情况,权衡利弊,选择最适合你的方案。
好了,今天的分享就到这里。 希望对大家有所帮助! 咱们下次再见!