热点新闻
116.学习微前端架构-乾坤
2023-07-24 15:54  浏览:246  搜索引擎搜索“养老服务网”
温馨提示:信息一旦丢失不一定找得到,请务必收藏信息以备急用!本站所有信息均是注册会员发布如遇到侵权请联系文章中的联系方式或客服删除!
联系我时,请说明是在养老服务网看到的信息,谢谢。
展会发布 展会网站大全 报名观展合作 软文发布



学习微前端架构-乾坤.png

目的:拆分应用,减轻一个庞大应用的压力,从另一个方面将公司所有的系统进行整合,形成一个整体。

微前端出现以前,每个系统都是独立的,用户需要保存不同系统的网址,使用微前端后,记录一个地址就好了。在用户层面进了整合,从开发层面减轻了系统压力。

主应用(不限技术栈)和子应用(目前vite项目的支持度不是很好)

接入案例,参考官网qiankun

首先需要搭建子应用,以vue2项目和react项目为例,主应用使用vue3

微应用实现

需要实现两个函数registerMicroAppsstartregisterMicroApps用来实现注册子应用。
实现入口
在主应用下新建micro-fe目录,新建index.js文件,需要导出两个方法

import {rewriteRouter} from "./rewrite-router.js" import {handleRouter} from "./handle-router.js" let apps = [] export getApps = ()=>{return apps} export const registerMicroApps = (_apps)=>{ apps = _apps } export const start = ()=>{ rewriteRouter(apps) //可能会出现直接打开子路由的情况,因此默认要执行一次处理路由的方法 handleRouter() }

新建rewrite-router.js文件,实现重写路由方法

微应用内部是通过监听路由的变化实现的,hash模式,通过重写onHashChange方法监听,history模式通过重写改变history方法的方式实现。我们可以在路由改变时使用当前路径和微应用提供的activeRule进行匹配,匹配上之后根据提供的entry加载子应用即可,再将加载到的资源追加到主应用提供的容器中进行展示。

实现路由监听

import {handleRouter} from "./handle-router.js" export const rewriteRouter = (apps)=>{ //监听replaceState方法和pushState方法,为了不直接改变原生方法,我们使用apply实现重写这些方法 const _replaceState = window.history.replaceState window.history.replaceState = (...args)=>{ _replaceState.apply(window.history,args) //下一步,加载子应用 handleRouter() } const _pushState = window.history.pushState window.history.pushState = (...args)=>{ __pushState.apply(window.history,args) handleRouter() } }

实现路由匹配,子应用加载

新建handle-router.js

import {getApps} from "./index.js" export const handleRouter = ()=>{ //根据当前地址匹配子应用 const pathname = window.location.pathname const app = getApps() && getApps().find((item)=>{ return pathname.startsWith(item.activeRule) }) if(!app) return //有匹配的,进行渲染 const container = document.querySelector("#subapp-container") //加载匹配到的子应用资源 const response = await fetch(app.entry); const body = await response.text(); //将加载到的资源追加到主应用提供的容器中 container.innerHTML = body }

发现,路由已经切换了,内容也插入了但是页面并没有改变,是因为所有的资源都在app.js,运行app.js才会展示页面,因此需要获取到script标签中的资源





1667107193439.png


加载app.js资源handle-router.js

import {getApps} from "./index.js" import {importHtml}from "import-html.js" export const handleRouter = async()=>{ //根据当前地址匹配子应用 const pathname = window.location.pathname const app = getApps() && getApps().find((item)=>{ return pathname.startsWith(item.activeRule) }) if(!app) return //有匹配的,进行渲染 const container = document.querySelector("#subapp-container") //加载匹配到的子应用资源 const {template,execscript} = await importHtml(app.entry) //执行获取到的js资源,拿到子应用的mount、unmount、bootstrap方法 const remoteApp = await execscript() app.bootstrap = result.bootstrap; app.mount = result.mount; app.unmount = result.unmount; //渲染页面,调用bootstrap和mount方法 await bootstrap(app) await mount(app) } async function bootstrap(app) { //执行子应用的bootstrap方法,传入app对象,app里包含的属性{name,entry,container,activeRule,bootstrap,mount,unmount},传入子应用里面,如果子应用里找到container了,挂载的时候挂载到指定页面 app.bootstrap && app.bootstrap(app); } async function mount(app) { app.mount && app.mount({ container: document.querySelector(app.container), }); } async function unmount() {}

import-html.js

// import importHTML from "import-html-entry"; import { fetchResource } from "./fetchResource"; //解析html模块代码 export const importHtml = async (url) => { //解析html文件,加载app.js文件 //截取script标签 const html = await fetchResource(url); const template = document.createElement("div"); template.innerHTML = html; //查找所有script标签 const scripts = template.querySelectorAll("script"); async function getExternalscripts() { return Promise.all( Array.from(scripts).map((script) => { const src = script.getAttribute("src"); if (!src) { return Promise.resolve(script.innerHTML); } else { //拼接src属性 return fetchResource(src.startsWith("http") ? "src" : `${url}${src}`); } }) ); } //执行scripts async function execscript() { const scripts = await getExternalscripts(); //console.log(scripts) //手动构建commonjs规范 const module = { exports: {} }; const exports = module.exports; //控制子应用挂载问题 window.__POWERED_BY_QIANKUN__ = true; window.__POWERED_BY_QIANKUN__ = app.entry + "/"; //子应用样式隔离问题,通过打包library库实现umd格式iife //umd兼容commonjs amd和es module scripts.forEach((code) => { eval(code); }); return module.exports; } return { template, getExternalscripts, execscript }; };


1667108558375.png


这是请求子应用里面script中的资源,将获取到的代码暴露出一个模块,

mount加载的时候,props为空,render时,挂载到的dom是#app,而不是container.querySelector("#app"),从而导致主应用的页面会被替换掉

function render(props = {}) { const { container } = props; router = route; instance = new Vue({ router, render: h => h(App) }).$mount(container ? container.querySelector("#app") : "#app"); } if (!window.__POWERED_BY_QIANKUN__) { render({}); } export async function mount(props) { console.log("[vue] props from main framework", props); render(props); }

使用umd打包格式解决,参考

新建import-html.js

export const importHtml =async (url)=>{ const html = await fetchResource(url) const template = document.createElement("div"); template.innerHTML = html; //获取所有的script标签 }


1667120092557.png


问题:点击切换之后发现之前的菜单还在,需要卸载之后再挂载,怎么知道之前的路由是哪个进行卸载,需要自己进行记录

rewriteRouter.js

import { handleRouter } from "./handleRouter"; export const getPrevRoute = () => { return _prevRouter; }; export const getNextRoute = () => { return _nextRouter; }; //记录前一个路由 let _prevRouter = null; //记录当前路由 let _activeRouter = window.location.pathname; export const rewriteRouter = () => { const _replaceState = window.history.replaceState; window.history.replaceState = (...args) => { _prevRouter = window.location.pathname; _replaceState.apply(window.history, args); _activeRouter = window.location.pathname; handleRouter(); }; const _pushState = window.history.pushState; window.history.pushState = (...args) => { _prevRouter = window.location.pathname; _pushState.apply(window.history, args); _activeRouter = window.location.pathname; handleRouter(); }; };

解决图片加载路径问题:

接入微应用时,引入的public-path.js文件

if (window.__POWERED_BY_QIANKUN__) { //修改webpack publicPath路径 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }

在importHtml.js中修改__INJECTED_PUBLIC_PATH_BY_QIANKUN__的值

async function execscript() { const scripts = await getExternalscripts(); //手动构建commonjs规范 const module = { exports: {} }; const exports = module.exports; //控制子应用挂载问题 window.__POWERED_BY_QIANKUN__ = true; //重新指定PublicPath window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ = url + "/"; //子应用样式隔离问题,通过打包library库实现umd格式iife //umd兼容commonjs amd和es module scripts.forEach((code) => { eval(code); }); return module.exports; // return window["app-vue2-app"]; }

此时发现已经可正常加载图片了。





1667140608145.png


另一个问题:react应用接入微应用怎么解决打包格式问题

样式隔离官放文档有提供一个sandbox参数可配置,设置了sandbox:true不管用,使用严格隔离模式。
怎么实现的还没看懂,单独设置沙箱模式和设置严格模式有什么区别?

//开启沙箱 start({ sandbox: { strictStyleIsolation: true } });

使用:@rescripts/cli插件或者react-app-rewired重写webpack配置文件解决
乾坤框架系列学习
01.学习微前端架构-乾坤
02.资源加载过程分析
03.乾坤css加载机制
04.乾坤js隔离机制
乾坤沙箱实现原理

发布人:43f1****    IP:117.173.23.***     举报/删稿
展会推荐
让朕来说2句
评论
收藏
点赞
转发