作者:dtd3795290 | 来源:互联网 | 2023-09-04 18:49
一、为什么要学习微前端什么是微前端微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。微前端的核心在于拆,拆完后在合!为什么要使用微前端不同团
一、为什么要学习微前端
什么是微前端
微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。
微前端的核心在于拆, 拆完后在合!
为什么要使用微前端
- 不同团队间开发同一个应用技术栈不同怎么破?
- 希望每个团队都可以独立开发,独立部署怎么破?
- 项目中还需要老的应用代码怎么破?
我们是不是可以将一个应用划分成若干个子应用,将子应用打包成一个个的lib。当路径切换时加载同的子应用。这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前端协同开发问题
怎样落地微前端
微前端的灵感来源于,计算机上的应用,每一次用户打开一个应用,就相当于打开了一个新的页面
- 2018年 Single-SPA诞生了, single-spa 是一个用于前端微服务化的 Javascript 前端解决方案 (本身没有处理样式隔离, js 执行隔离) 实现了路由劫持和应用加载
- 2019年 qiankun 基于Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技术栈无关、并且接入简单(像iframe 一样简单)
总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,技术栈无关,靠的是协议接入(子应用必须导出 bootstrap、mount、unmount方法)
这里先回答下大家的问题:
微前端架构具备以下几个核心价值:
-
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权
-
独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
-
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
-
独立运行时
每个微应用之间状态隔离,运行时状态不共享
二、SingleSpa 实战
single-spa官网
构建子应用
我们需要父应用加载子应用,需要暴露三个方法
1. bootstrap
2. mount
3. unmount
- 构建子应用
vue create single-child
npm i --save single-spa-vue
import singleSpaVue from 'single-spa-vue'const appOptions = {el: '#vue', router,render: h => h(App)
}const vueLifeCycle = singleSpaVue({ Vue,appOptions
});
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
- 配置子应用中的打包路径
module.exports = {configureWebpack: {output: {library: 'singleVue',libraryTarg: 'umd'},devServer: {port: 10000}}
};
- 配置子应用的路由
const router = new VueRouter({mode: 'history',base: '/vue', routes
})
- 父应用搭建
vue create single-parent
npm i --save single-spa // 注意这里是 single-spa
- 将子应用挂载到
id="vue"
的容器中
<div id&#61;"app"><router-link to&#61;"/vue">加载vue引用router-link><router-view/><div id&#61;"vue">div>
div>
- 配置父应用加载子应用
import { registerApplication, start } from &#39;single-spa&#39;async function loadScript(url) { return new Promise((resolve, reject) &#61;> {let script &#61; document.createElement(&#39;script&#39;);script.src &#61; url;script.onload &#61; resolve;script.onerror &#61; reject;document.head.appendChild(script);});
}
registerApplication(&#39;myVueApp&#39;,async () &#61;> {console.log(&#39;加载模块&#39;);await loadScript(&#96;http://localhost:10000/js/chunk-vendors.js&#96;)await loadScript(&#96;http://localhost:10000/js/app.js&#96;)return window.singleVue},location &#61;> location.pathname.startsWith(&#39;/vue&#39;), { a: 1 }
);start();
- 配置子应用的路径
if (window.singleSpaNavigate) { __webpack_public_path__ &#61; &#39;http://localhost:10000/&#39;
}
- 希望子应用可以独立运行&#xff0c;在子应用中添加一个配置
if(!window.singleSpaNavigate){delete appOptions.el; new Vue(appOptions).$mount(&#39;#app&#39;);
}
singleSpa 缺陷
- 不能动态加载JS文件
- 样式不隔离
- 全局对象&#xff0c;没有JS沙箱的机制
三、qiankun 实战
qiankun官网
特点
1. 简单&#xff1a;任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单&#xff0c;但实际不是 iframe。
2. 完备&#xff1a;几乎包含所有构建微前端系统时所需要的基本能力&#xff0c;如 样式隔离、js 沙箱、预加载等。
3. 生产可用&#xff1a;已在蚂蚁内外经受过足够大量的线上系统的考验及打磨&#xff0c;健壮性值得信赖。
项目构建
- 主应用搭建
qiankun-base
vue create qiankun-base
npm i --save qiankun
import Vue from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;
import ElementUI from &#39;element-ui&#39;;
import &#39;element-ui/lib/theme-chalk/index.css&#39;;import {registerMicroApps, start} from &#39;qiankun&#39;;Vue.config.productionTip &#61; false
Vue.use(ElementUI);const apps &#61; [{name: &#39;vueApp&#39;, entry: &#39;http://localhost:10000/&#39;, container: &#39;#vue&#39;, activeRule: &#39;/vue&#39; },{name: &#39;reactApp&#39;,entry: &#39;http://localhost:20000/&#39;,container: &#39;#react&#39;,activeRule: &#39;/react&#39;}
];registerMicroApps(apps);
start(); new Vue({router,render: h &#61;> h(App)
}).$mount(&#39;#app&#39;)
<template><div><el-menu :router&#61;"true" mode&#61;"horizontal"><el-menu-item index&#61;"/">首页el-menu-item><el-menu-item index&#61;"/vue">vue应用el-menu-item><el-menu-item index&#61;"/react">react应用el-menu-item>el-menu><router-view v-show&#61;"$route.name">router-view><div id&#61;"vue">div><div id&#61;"react">div>div>
template>
- 搭建Vue子项目
vue create qiankun-vue
// 子项目中不需要安装任何依赖&#xff0c;父组件会给window设置一些环境变量
import Vue from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;Vue.config.productionTip &#61; false
let instance &#61; null;
function render(props) {instance &#61; new Vue({router,render: h &#61;> h(App)}).$mount(&#39;#app&#39;)
}if (!window.__POWERED_BY_QIANKUN__) { render();
}
if(window.__POWERED_BY_QIANKUN__){ __webpack_public_path__ &#61; window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
export async function bootstrap(props) {};
export async function mount(props) {render(props);
};
export async function unmount(props) {instance.$destroy();
};
const router &#61; new VueRouter({mode: &#39;history&#39;,base: &#39;/vue&#39;,routes
})
module.exports &#61; {devServer: {port: 10000,headers:{&#39;Access-Control-Allow-Origin&#39;: &#39;*&#39; }},configureWebpack: {output: {library: &#39;vueApp&#39;,libraryTarget: &#39;umd&#39;}}
};
- 搭建React项目
npx create-react-app qiankun-react
npm i --save-dev react-app-rewired
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;function render(){ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById(&#39;root&#39;));
}
if(!window.__POWERED_BY_QIANKUN__){render();
}
export async function bootstrap(){}
export async function mount() {render()
}
export async function unmount(){ReactDOM.unmountComponentAtNode( document.getElementById(&#39;root&#39;));
}
module.exports &#61; {webpack:(config)&#61;>{config.output.library &#61; &#39;reactApp&#39;;config.output.libraryTarget &#61; &#39;umd&#39;;config.output.publicPath &#61; &#39;http://localhost:20000/&#39;;return config;},devServer:(configFunction)&#61;>{return function (proxy,allowedHost){const config &#61; configFunction(proxy,allowedHost);config.headers &#61; {"Access-Control-Allow-Origin":&#39;*&#39;}return config}}
}
添加react环境变量 .envPORT&#61;20000
WDS_SOCKET_PORT&#61;20000
import { BrowserRouter, Route, Link } from "react-router-dom"
const BASE_NAME &#61; window.__POWERED_BY_QIANKUN__ ? "/react" : "";
function App() {return (<BrowserRouter basename&#61;{BASE_NAME}><Link to&#61;"/">首页</Link><Link to&#61;"/about">关于</Link><Route path&#61;"/" exact render&#61;{() &#61;> <h1>hello home</h1>}></Route><Route path&#61;"/about" render&#61;{() &#61;> <h1>hello about</h1>}></Route></BrowserRouter>);
}