官网地址:https://qiankun.umijs.org/
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略
·独立开发、独立部署
主应用
1. 安装 qiankun
$ yarnadd qiankun # 或者 npm i qiankun -S
2. 在主应用中注册子应用(主应用的main.js)
import{ registerMicroApps, start }from'qiankun';
registerMicroApps([
{
name:'react app',// app name registered
entry:'//localhost:7100', //子应用的地址(不包含标识和路由)
container:'#vue’,// 子应用挂载的div
activeRule:'/yourActiveRule', // 子应用的标识,路由中会详细说明
},
]);
// 启动 qiankun
start();
手动加载本项目暂不需要。当子应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的子应用就会被插入到指定的 container 中,同时依次调用子应用暴露出的生命周期钩子。
如果子应用不是直接跟路由关联的时候,你也可以选择手动加载子应用的方式:
import{ loadMicroApp }from'qiankun';
loadMicroApp(
{
name:'app',
entry:'//localhost:7100',
container:'#yourContainer',
}
);
子应用
子应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
1、入口文件 main.js修改,为了避免根 id #app与其他的 DOM 冲突,需要限制查找范围,在container范围中查找
import'./public-path';
import Vue from'vue';
import VueRouter from'vue-router';
import App from'./App.vue';
import routes from'./router';
import store from'./store';
Vue.config.productionTip =false;
let router =null;
let instance =null;
functionrender(props ={}){
//为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围
const{ container }= props;
router =newVueRouter({
//如果是子应用就设置标识
base:window.__POWERED_BY_QIANKUN__ ?'/app-vue/':'/',
mode:'history',
routes,
});
instance =newVue({
router,
store,
render: h =>h(App),
}).$mount(container ? container.querySelector('#app'):'#app');
}
// 是否独立运行
if(window.__POWERED_BY_QIANKUN__){
__webpack_public_path__ =window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}else{
render();
}
// 导出相应的生命周期钩子
exportasyncfunctionbootstrap(){
console.log('[vue] vue app bootstraped');
}
exportasyncfunctionmount(props){
console.log('[vue] props from main framework', props);
render(props);
}
exportasyncfunctionunmount(){
instance.$destroy();
instance.$el.innerHTML ='';
instance =null;
router =null;
}
2.打包配置修改(vue.config.js):
设置跨域和打包格式
const{ name }=require('./package');// 不重要。自己起也可以
module.exports ={
devServer:{
headers:{
'Access-Control-Allow-Origin':'*',// 设置跨域
},
},
configureWebpack:{
output:{
library:`${name}-[name]`,// 配置导出库的名称
libraryTarget:'umd',// 把子应用打包成 umd 库格式
jsonpFunction:`webpackJsonp_${name}`,//不是必须的
},
},
};
3.子应用生命周期钩子介绍
子应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount三个生命周期钩子,以供主应用在适当的时机调用。
/**
bootstrap 只会在子应用初始化的时候调用一次,下次子应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
exportasyncfunctionbootstrap(){
console.log('react app bootstraped');
}
/**
- 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
exportasyncfunctionmount(props){
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root'): document.getElementById('root'));
}
/**
- 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载子应用的应用实例
*/
exportasyncfunctionunmount(props){
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root'): document.getElementById('root'));
}
/**
- 可选生命周期钩子,仅使用 loadMicroApp 方式(手动挂载)加载子应用时生效
*/
exportasyncfunctionupdate(props){
console.log('update props', props);
}
父子通信(父子应用store分离的方案实现)
使用vuex的原因是为了获取主应用的实时数据,也为了实现子应用主应用的相互通信。任何项目中vuex都有base.js,用来存储公共信息。比如token、用户名等。(现项目token等存储在localstorage中)子应用单独启动时用自己的vuex中的base.js来存储信息,base.js只能主应用进行修改,同步到子应用。(不考虑子应用单独运行可以不在子应用维护base.js)
step1:主应用向子应用传递store实例。
step2:子应用使用主应用共享的store实例并设置只属于子应用的vuex
针对第一种情况,就是在入口文件中引入vuex,并使用该插件,进而在创建vue实例的时候,传入主应用共享的store。
Vuex正常使用的时候,所有的状态值都是响应式的,可以直接用于Vue页面之中,但是这里是非响应式的,导致这个的原因其实十分简单。这里的store实例是由主应用传递过来的,store中的状态对于主应用的vue实例而言是亲儿子,是响应式的,在子应用中,虽然可以使用共享store实例中的commit方法,但是对于子应用的实Vuex例而言,不是亲儿子,是非响应式的,这样分析之后,解决方案就十分明确:
在子应用中将共享的****store****实例进行响应式设置,这是****Vue****现有的****API****方法****Vue.observable(store)
子应用的文件夹格式和使用方式
step4:子应用之间通信
子应用之间的通信可以通过父应用的vuex来传递。
登录
1.1把子应用的登录页面移到主应用
1.2保留子应用的登录,保证可以单独启动(目前登录信息存储在localstorage中)
路由
1.3因为qiankun的特性主应用调用子应用是路由前要加子项目标识
列:子应用中路由为/index,子应用标识为/ child01,主应用的该路由就为/child01 /index
主应用和子应用设置标识必须相同
后台返回的权限路由都要加子应用标识
遇到的问题
1、应用的部署,需要通过nginx做反向代理,把指向子应用的访问路径转发到子应用的服务入口
2、子应用套孙子应用未考虑,一个页面多个子应用未考虑;
应用部署nginx
子应用中需配置,跨域
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested- With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
让微应用文件更新之后访问新的文件
问题描述:发版vue项目后,总是要强制刷浏览器才能生效
vue-cli里的默认配置,css和js的名字都加了哈希值,所以新版本css、js和就旧版本的名字是不同的,不会有缓存问题。
但是把打包好的index.html放到服务器里去的时候,index.html在服务器端可能是有缓存的,这需要在服务器配置不让缓存index.html
解决方法如下:
前端在index.html中添加:
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
nginx 配置如下:
location = /index.html {
add_header Cache-Control "no-cache, no-store";
}
开发规范
1.所有的资源(图片/音视频等)都应该放到src目录,不要放在public或者static
资源放 src 目录,会经过 webpack 处理,能统一注入 publicPath。否则在主项目中会404。
2.请给 axios 实例添加拦截器,而不是 axios 对象
// 正确做法:给 axios 实例添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {/.../});
// 错误用法:直接给 axios 对象添加拦截器
axios.interceptors.request.use(function () {/.../});
3.避免 css 污染
组件内样式的css-scoped是必须的。
对于一些插入到body的弹窗,无法使用scoped,请不要直接使用原class修改样式,请添加自己的class,来修改样式。尽量不将元素插入body
.el-dialog{
/* 不推荐使用组件原有的class */
}
.my-el-dialog{
/* 推荐使用自定义组件的class */
}
4.谨慎使用 position:fixed
在父项目中,这个定位未必准确,应尽量避免使用,确有相对于浏览器窗口定位需求,可以用position: sticky,但是会有兼容性问题(IE不支持)。如果定位使用的是bottom和right,则问题不大。
5.给 body 、 document 等绑定的事件,请在 unmount 周期清除
js 沙箱只劫持了 window.addEventListener。使用
document.body.addEventListener 或者 document.body.onClick 添加的事件并不会被沙箱移除,会对其他的页面产生影响,请在 unmount 周期清除