今天主要跟大家系统的分享微前端的相关知识,以及跟大家介绍在实践项目中如何使用微前端,另外,微前端不是银弹,具体的架构方案需要根据自身的项目去落地。
介绍本次文章的主要内容:
一、关于微前端
二、 微前端的架构方案
三、微前端的实践
四、总结
一、关于微前端?
Micro Frontends官网中,微前端的概念源自于微服务,摈弃大型单体应用,将前端整体分解成小而简单的模块,这些模块可以单独开发,测试和部署,同时可以聚合成一个产品。微前端并不是一种新技术,它可以理解成将多个可独立交付的小型前端应用聚合为一个整体的技术策略。
1.1 微前端的特点:
独立部署
增量迁移
团队自治
松耦合代码
\
1.2 什么项目需要微前端?
基于以上特点不难看出微前端项目的优势在于:
巨型项目拆解
历史项目增量迁移
二、微前端架构方案
目前,微前端的架构方案主要有三种:
自由组合模式
基座模式
去中心化模式
值得一提的是,基座模式是目前使用最多最火的方案,single-spa和基于single-spa的qiankun采取的都是基座模式。然而,我认为webpack5.x的Module Federation,也叫模块联邦可能才是未来的趋势,此处按下不表,在后文展开细讲。
三、微前端实践
3.1 system.js + webpack
采用system.js(通用模块加载器)结合webpack打包加载子应用模块
3.2 single-spa
其本质也是通过路径匹配成功后加载对应的子应用模块
single-spa中重要概念:
single-spa-application/parcel:微前端架构中的微应用,可以使用vu,react,angular等框架
single-spa root config:创建微前端容器应用
utility modules:公共模块应用,非渲染组件,用于跨应用共享JavaScript逻辑的微应用
接下来创建一个single-spa容器:
全局安装single-spa并创建
npm install --global create-single-spa
create-single-spa
根据交互提示选择
single-spa root config
选择合适的包管理工具以及组织名字
下一步容器就生成好了,在项目目录下运行npm run start就可以访问http://localhost:9000/
需要注意的是src目录下面的xxx-root-config文件,xxx就是组织名称。可以看到配置文件里面从single-spa中引用了两个方法registerApplication和start
registerApplication(注册)可以接受一个对象:
name:String,"@组织名称/应用名称"
app:Function,返回promise,引入system打包好的微前端应用模块
activeWhen:路由匹配时激活应用,可以使用customProps来接受自定义参数,在生命周期钩子执行时作为参数传入。实现数据通信
src下的index.ejs时基座的入口文件,通常会提取公共模块如react,react-dom等,引入应用的地址,默认加载在
中接下来创建子应用react-dom和vue-dom
运行create-single-spa select single-spa-application/parcel
修改端口号后运行npm run start/serve
然后我们需要通过容器去访问这两个应用,需要怎么做呢?
打开容器src/index.ejs
<script type="systemjs-importmap">
{
"imports": {
"@xxx/react-demo": "//localhost:8081/xxx-react-demo.js"
"@xxx/vue-demo": "//localhost:8082/xxx-vue-demo.js"
}
}
</script>
然后需要在xxx-root-config文件中注册
registerApplication({
name: "@xxx/react-demo",
app:() => System.import('@xxx/react-demo'),
activeWhen:["/react"]
})
registerApplication({
name: "@xxx/vue-demo",
app:() => System.import('@xxx/vue-demo'),
activeWhen:["/vue"]
})
最后,需要将react,react-dom以及single-spa的cdn地址添加到index.ejs的importmap中
这里仅仅介绍了single-spa最基础的内容,在官网中可以看到它其他属性和事件。
3.3 qiankun
qiankun是基于single-spa的,因此两者的api非常相似,理解了single-spa就很容易上手了
安装:npm install qiankun
引入:import {registerMicroApps,start} from 'qiankun';
注册:
const apps = [ { name:'vueApp', entry: '//loaclhost:10000' // 默认会加载这个html 解析里面的js 动态执行(子应用必须支持跨域) container: '#vue', activeRules: '/vue' }, .... ]
registerMicroApps(apps)
start()
子应用改造:
main.js
let instance = null
function render(props) {
instance = new Vue({
///
})
}
// qiankun-常见问题--使用webpack中的public_path
// qiankun-常见问题--如何独立运行子应用
export async function bootstrap(props) {};
export async function mount(props) {
render(props)
};
export async function unmount(props) {
instance.$destroy()
};
配置vue.cinfig.js
module.exports = {
devServer: {
port:10000,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output:{
library: 'vueApp',
libraryTarget: 'umd'
}
}
}
这里仅仅是接入的基础介绍,更多知识点移步qiankun.umijs.org/zh/guide
4.webpack5模块联邦
之前为什么说它是未来趋势?
基于webpack生态,大多数项目已经使用,代码侵入性小,学习实施成本低
天然的工程化,npm包随意发挥
官网配置说明人性化,无关框架
4.1 理解模块联邦
联邦模块是webpack5提供的一个新特性,通过原生提供的ModuleFederationPlugin插件来实现的,其出现的动机是解决微前端架构中多个子应用共享资源的问题。
其中有三个重要的功能:
exposes :声明会暴露那些模块供远程使用
remotes:声明从哪里来导入远程资源
shared:声明哪些模块会在主项目和远程项目中共享
两个主要概念:
Host:消费其他Remote,需要配置remote列表和shared模块
Remote:被Host消费,需要配置项目名name,打包方式library,打包后的文件名filename,提供的模块exposes,和Host共享的模块shared
注意,每个项目既可以是Host,也可以是Remote。
4.2 导入远程模块
vue-demo1:暴露一个Navbar组件模块;
vue-demo2:暴露一个LeftSlider组件模块,引入vue-demo1的navbar;
vue-demo3: 引入vue-demo1的navbar,引入vue-demo2的LeftSlider;
const { ModuleFederationPlugin } = require("webpack").container;
vue-demo1的webopack--plugins:
new ModuleFederationPlugin({
name: "vue-demo1",
filename: "remoteEntry.js",
exposes: {
"./Navbar": "./src/Navbar",
},
shared: {
vue: { singleton: true },
},
})
vue-demo2的webopack--plugins:
new ModuleFederationPlugin({
name: "vue-demo2",
filename: "remoteEntry.js",
exposes: {
"./LeftSlider": "./src/LeftSlider",
},
remotes: {
vue-demo1: "vue-demo1@http://localhost:3001/remoteEntry.js",
},
shared: {
vue: { singleton: true }
},
})
vue-demo3的webopack--plugins:
new ModuleFederationPlugin({
name: "vue-demo3",
remotes: {
vue-demo1: "vue-demo1@http://localhost:3001/remoteEntry.js",
vue-demo2: "vue-demo2@http://localhost:3002/remoteEntry.js",
},
shared: { vue: { singleton: true } },
})
在vue-demo3中使用
import { defineAsyncComponent } from 'vue'
const Navbar = defineAsyncComponent(() =>
import('vue-demo1/Navbar')
)
4.3 ModuleFederationPlugin的用法
- name,必须,唯一 ID,作为输出的模块名(容器),使用的时通过 name/{name}/name/{expose} 的方式使用;
- library,可选,打包方式,默认
{ type: "var", name: options.name },其中这里的 name 为作为 umd 的 name,是挂载在全局下的变量名; - filename,可选,打包后的文件名;
- remotes,可选,表示当前应用是一个 Host,可以引用 Remote 中 expose 的模块;
- remoteType,可选,默认
var,("var"|"module"| "assign"|"this"|"window"|"self"|"global"|"commonjs"|"commonjs2"| "commonjs-module"|"amd"|"amd-require"|"umd"|"umd2"|"jsonp"|"system"|"promise"|"import"|"script"),远程容器的外部类型; - exposes,可选,表示当前应用是一个 Remote,exposes 内的模块可以被其他的 Host 引用,引用方式为 import(name/{name}/name/{expose});
- shared,可选,主要是用来避免项目出现多个公共依赖,若是配置了这个属性,webpack在加载的时候会先判断本地应用是否存在对应的包,若是不存在,则加载远程应用的依赖包;
- shareScope,可选,用于所有共享模块的共享作用域名
4.4 ModuleFederationPlugin的原理
参考developer.aliyun.com/article/755…,文章说的非常详细和清楚。
四、总结
每一种技术方案的出现都是为了解决相对应的技术问题。目前团队正在新建组件应用服务去管理全部应用和组件, 其他相关的业务场景根据自身业务要去加载相应的组件和功能模块是契合webpack模块联邦方案的。另外在可视化场景或者需要模块化开发的复杂应用场景也可以去尝试webpack5的模块联邦新特性。