实现微前端,我们的大致思路应该如下:
- 监视页面路由的变化
- 根据当前页面路由匹配子应用
- 主应用加载子应用
- 特定容器渲染子应用
准备工作
在qinkun里,主应用的main.js中引入两个函数registerMicroApps, start,仿照qinkun,我们创建自己的微前端框架文件夹,命名micro-fe , 我们在主应用的main.js仿照qiankun做出如下配置
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { registerMicroApps, start } from './micro-fe';
registerMicroApps([
{
name: 'vue1', // app name registered
entry: 'http://localhost:9005/',
container: '#micro-container',
activeRule: '/vue1',
},
{
name: 'vue2',
entry: 'http://localhost:9005/',
container: '#micro-container',
activeRule: '/vue2',
},
{
name: 'react',
entry: 'http://localhost:9005/',
container: '#micro-container',
activeRule: '/react',
},
]);
start();
createApp(App).use(router()).mount('#app')
监视路由变化
我们的satrt函数里,应该是我们微前端框架的核心逻辑,需要实现监视页面路由的变化、根据当前页面路由匹配子应用、主应用加载子应用、特定容器渲染子应用四个核心功能。 首先,我们实现路由的监听,路由分为hash路由和history路由。
hash路由
对于hash路由的变化,我们可以使用window.onhashchange来监听,比较容易。本实例中,我们暂时不考虑。
history路由
对于history路由, 其history.go、history.back、history.forword 等方法我们可以使用popstate事件进行监听。 (浏览器的前进后退会触发history.go、history.back方法)。
// micro-fe\文件夹下创建rewrite-router.js
// 在start函数里(micro-fe\index.js)进行引入使用
根据当前路由匹配子应用
- 获取当前的路由路径
- 去 apps 里面查找
// micro-fe\文件夹下创建handle-router.js文件
加载子应用,获取并渲染html内容
- 使用ajax获取子应用对应域名下的html
- 获取要渲染html的dom元素
- 将获取的html挂载在获取的dom元素上
观察控制台,我们发现html已经被成功挂载在主应用的容器里,但是,页面并没有如我们预期的那样显示出来 原因在于,页面的渲染需要通过执行 js代码来生成内容,浏览器出于安全考虑,innerHTML中生成的script不会被加载。因此,script标签的解析及处理是微前端框架的第一步核心操作。
//3.加载子应用
// 使用 fetch 获取子应用对应域名下的html
html = await fetch(app.entry).then(res => res.text())
//获取要渲染html的dom元素
const container = document.querySelector(app.container)
//将获取的html挂载在获取的dom元素上
container.innerHTML = html
资源请求函数封装
在乾坤框架里,对于html的资源请求,阿里做了单独的插件。这个插件包含三个核心对象
- template:用于获取html模板
- getExternalScripts()函数:获取所有的script标签代码
- execScripts()函数:获取并执行所有的js脚本
类似的,我们也可以封装一个这样的函数。由于html和js内容都是通过fetch进行请求的,我们可以先将fetch函数进行封装。
//micro-fe\文件夹下创建fetch-resource.js文件
export const fetchResource = url => fetch(url).then(res => res.text())
// micro-fe\文件夹下创建import-html.js文件。
// 最后,我们将封装好的 importHT ML函数导出在micro-fe\handle-router.js中引入使用
获取所有的script标签及js代码
- 获取所有的script标签:
- 解析scripts报签内的js内容
观察浏览器控制台,可以看到,所有的js内容已经获取到了,虽然目前只是字符串形式
// 行内标签解析:直接使用innerHTML获取内容
// 外链标签解析:使用fecth函数请求内容
渲染子应用
现在,我们已经知道,执行完获取的子应用js代码后,页面上就应该能够渲染出我们的子应用界面。
// 执行获取的js内容
// 我们使用eval函数来执行这些脚本内容。
切换路由,我们会发现,我们的页面被成功渲染出来!!!但是,页面整体被替换成了vue2的内容,而不是渲染在我们指定的容器里。 这是为什么呢?其实很简单,当前我们的全局没有微前端的全局变量,vue2子应用在渲染时没有走微前端框架,直接渲染在了id = app这个容器里,而这个容器也是主应用的容器名称。
使用微前端框架执行render函数
要使子应用运行在微前端框架里,我们只需要在handle-router.js中添加变量window.MICRO_WEB = true即可
//配置全局变量
window.__MICRO_WEB__ = true
此时,切换路由就不会出现app根节点被替换的问题,但由于vue2子应用的render函数没有执行,所以页面依旧不会有内容。我们需要做的就是拿到子应用的生命周期函数,手动执行render函数。
umd
子打包的时候,我们做了如下配置
output: {
// 把子应用打包成 umd 库格式
libraryTarget: 'umd', //umd格式 支持comm.js引入 浏览器、node可以识别
filename: 'vue2.js', //打包出的文件名称
library: 'vue2', //全局可以通过window.vue2拿到的应用
jsonpFunction: `webpackJsonp_${name}`,
},
//通过umd,我们在微前端框架里可以使用window对象来获取子应用的生命周期函数。
//子应用的所有信息
console.log(window['vue2']);
执行子应用的生命周期函数
对获取到的生命周期函数信息,我们可以将其绑定在我们的配置列表app里,然后使用app.mount 对这个生命周期函数进行调用即可。为了便于函数逻辑的添加,我们可以把生命周期函数的调用都封装成函数。
//详情:handle-router.js
卸载前一个子应用
当我们切换路由时,会发现页面内容不断变长,原因在于原先的子应用并没有被卸载。因此,我们需要实现应用切换时,卸载上一个子应用,实现这个功能,我们就需要得到浏览器的历史记录。 浏览器由于安全问题,没有提供路由的历史记录获取API,我们需要自己在路由监视的代码中自己维护一个路由记录。
//micro-fe\rewrite-router.js
// micro-fe\handle-router.js
样式资源文件的加载
// 在微前端框架里的子应用图片资源是加载不到的。
// 因为图片的路径是子应用ip + url (9004 + url)
// 在主应用请求后就变成了主应用ip+ url (8080 + url)
// 要解决此问题,我们可以在子应用里把路径写死,像这样
module.exports = {
publicPath:'http://localhost:9004',
}
//webpack支持运行时的publicPath
import './public-path.js'
module.exports = {
publicPath:webpack_public_path,
}
//./public-path.js
if (window.__MICRO_WEB__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//配置全局变量
window.__MICRO_WEB__ = true
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ = app.entry + "/"