
本系列大纲
- 微前端模块联邦实践系列(1) - 微前端入门
- 微前端模块联邦实践系列(2) - 微前端与模块联邦
- 微前端模块联邦实践系列(3) - 第一个案例
- 微前端模块联邦实践系列(4) - subApp之间共享libraries
- 微前端模块联邦实践系列(5) - Path to Production
- 微前端模块联邦实践系列(6) - 集成React & Vue
- 微前端模块联邦实践系列(7) - 路由管理
- 微前端模块联邦实践系列(8) - 消息通信
微前端中各个应用由于相互独立,因此技术栈的选择具有高度的自由性。正如我们在 微前端模块联邦实践系列(1) - 微前端入门 中提到的:
技术栈无关:各个微前端模块可以使用不同的技术栈(React、Vue、Angular等),这为团队选择最适合的工具提供了灵活性。
因此在本案例中的三个App,我采用的技术栈为(个人对React相对熟悉一点,Vue can do):
- container: React
- posts: React
- albums: Vue
代码变更
下面主要以container app变更为例,post & albums app的变更大同小异,详情可以参照 github mfe-demo
webpack配置改造
以其中的container app为例,将以前的webpack.dev.js以及webpack.prod.js进行相同部分提取,创建webpack.common.js
// apps/container/config/webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
}),
],
}
dev以及prod文件只需要关注它本身不同的部分即可
// apps/container/config/webpack.dev.js
module.exports = merge(commonConfig, {
mode: 'development',
devServer: {
port: 8080,
hot: true,
historyApiFallback: true,
},
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
postsApp: 'posts@http://localhost:8081/remoteEntry.js',
albumsApp: 'albums@http://localhost:8082/remoteEntry.js',
},
shared: packageJson.dependencies,
}),
],
})
// apps/container/config/webpack.prod.js
module.exports = merge(commonConfig, {
mode: 'production',
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
postsApp: `posts@${process.env.MFE_POSTS_DOMAIN}remoteEntry.js`,
albumsApp: `albums@${process.env.MFE_ALBUMS_DOMAIN}remoteEntry.js`,
},
shared: packageJson.dependencies,
}),
],
})
bootstrap变为React方式渲染
const App = () => {
return (
<div>
<h1>Container App</h1>
<Button />
</div>
)
}
const root = createRoot(document.getElementById('containerRoot'))
root.render(<App />)
最终效果
CSS冲突
更改posts以及albums中button的css样式,如下:
/* apps/posts/src/components/button.css */
.button {
padding: 10px 20px;
background-color: #424eb9;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #983669;
}
这里我将posts中的button背景颜色设置为蓝色,className为.button
/* apps/albums/src/components/Button.vue */
<style>
.button {
padding: 10px 20px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #369870;
}
</style>
在albums中的button设置同样的className,颜色为绿色,最终的效果为:
可以看到posts中的button颜色被覆盖了,原因是因为我们是首先加载的posts app,然后才是albums。
// apps/container/src/bootstrap.js
const App = () => {
return (
<div>
<h1>Container App</h1>
<Button />
<PostsApp />
<AlbumsApp />
</div>
)
}
const root = createRoot(document.getElementById('containerRoot'))
root.render(<App />)
解决上述问题,可以参考 微前端模块联邦实践系列(2) - 微前端与模块联邦 中的相关办法,这里介绍几种常用的
BEM
BEM(Block Element Modifier)是一种命名约定方法,用于编写可维护、可复用的 CSS 代码。BEM 代表 Block(块) 、Element(元素) 和 Modifier(修饰符)。
分别给posts中的button以及albums中的button添加块级标识符,类似于命名空间,例如
.posts__button {}
.albums__botton {}
启用CSS Modules
在posts的webpack配置中,开启css modules功能
// apps/posts/config/webpack.common.js
{
test: /.module.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
}
使用css modules的方式引入样式
// apps/posts/src/components/Button.js
import * as styles from './button.module.css'
const Button = () => {
return <button className={styles.button}>Click me!</button>
}
在dom中生成的css class为一串随机的hash值,不过这个也是可以配置的,具体请自行搜索。
Scoped CSS (特定框架限定)
在某些框架中,比如vue,可以声明scoped css,这样的css类的范围被局限在组件内部,因此不会覆盖全局样式
<style scoped>
...
</style>
集成shadcn ui
ShadCN UI 是一个基于 React 和 Radix UI 的现代化 UI 组件库,旨在提供一组高度可定制、无障碍且易于使用的 UI 组件,帮助开发者快速构建美观且功能丰富的用户界面。
为什么选它?没有理由,纯粹个人觉得好看而已。
详情参看github提交记录,revision id: 8556d9420842529a6ff108e93f62bf088c034cd1
container & posts app
由于这两个app,采用的是React技术栈,而且纯手撸,因此shadcn-ui的集成还有点问题。具体可以follow以下步骤,传送门
- 按照shadcn-ui的手动配置先执行一遍,Manual Installation
- 添加
jsconfig文件,配置路径alias,主要是为了使用shadcn的CLI来自动生成组件 - webpack中添加path alias
- 配置postcss用来做css预处理
{ test: /.css$/, include: path.resolve(__dirname, '../src'), use: ['style-loader', 'css-loader', 'postcss-loader'], } - 编写
components.json,理论上使用npx shadcn@latest init会自动检测生成这个文件,但是由于我们纯手撸,所以shadcn不能检测出我们当前使用了什么freamwork,因此无法生成,没关系,手写一个就是这里的aliases需要和之前配置的绝对路径匹配。{ "$schema": "https://ui.shadcn.com/schema.json", "style": "default", "rsc": false, "tsx": false, "tailwind": { "config": "tailwind.config.js", "css": "src/main.css", "baseColor": "neutral", "cssVariables": true }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui" } } - 使用shadcn CLI来生成第一个component,
npx shadcn@latest add button,运行之后,会在apps/posts/src/components/ui下面生成一个button.jsx的组件
albums
Albums app 使用的是vue,对应的有 shadcn-vue 与之对应。步骤基本和上述类似,稍微有点变化的是对应的CLI,例如添加button组件的方式为:
npx shadcn-vue@latest add button
最终的效果如下: