当前位置: 首页 > 后端技术 > Node.js

深度:从零开始写一个微前端框架

时间:2023-04-03 21:24:43 Node.js

开头写的是:手写框架系统文章,手写vue和微前端框架文章不足,今天补了微前端框架,觉得写的不错,记得关注+观看,转发更好对源码感兴趣的可以看我之前的微前端框架如何导入加载子应用的手写源码系列文章【3000字精读】原创:带你从零开始看Node源码createServer和负载均衡全过程原文:从零开始实现一个简单版的React(附源码)精读:10个案例让你全面了解Reacthooks的渲染逻辑原文:如何自己实现一个简单的webpack构建工具【附源码】从零开始解析webRTC.ioServer源码正式入手:微前端,最近好像很火。之前也发过很多微前端框架的文章。框架qiankun和源码下面我们需要手写一个微前端框架。首先让大家知道什么是微前端。现在微前端的模型有很多种,但大多数都是一个基础+多个子应用的模式。根据子应用注册规则,展示子应用。这就是目前微前端框架基础加载模式的原理。它基于单个spa包。我看到许多公司使用Vue作为加载器(具有自然保持活动),以及角度和Web组件。技术集成第一个工程基础建设,这里使用parcel:mkdirpanguyarninit//输入一系列信息yarnaddparcel@next新建index.html文件作为基础文档新建index.js文件,加载配置文件为基础,新建src文件夹,作为盘古框架的源代码文件夹,新建一个examplecasefolder现在项目结构很长,因为是手写的,不依赖任何其他第三方库。我们首先需要重写hashchangepopstate这两个事件,因为微前端的base需要监听这两个事件来根据注册规则加载不同的子应用,其实现必须在React的切换之前以及Vue子应用路由组件,单页面路由源码原理实现。其实也是通过这两个事件实现的。之前写过一篇关于单页实现原理的文章。不熟悉的可以去https://segmentfault.com/a/1190000019936510constHIJACK_EVENTS_NAME=/^(hashchange|popstate)$/i;constEVENTS_POOL={hashchange:[],popstate:[],};window.addEventListener('hashchange',loadApps);窗口。addEventListener('popstate',loadApps);原点lAddEventListener=window.addEventListener;constoriginalRemoveEventListener=window.removeEventListener;window.addEventListener=function(eventName,handler){if(eventName&&HIJACK_EVENTS_NAME.test(eventName)&&typeofhandler==='function'){EVENTS_POOL[事件名称].indexOf(handler)===-1&&EVENTS_POOL[eventName].push(handler);}returnoriginalAddEventListener.apply(this,arguments);};window.removeEventListener=function(eventName,handler){if(eventName&&HIJACK_EVENTS_NAME.test(eventName)){让eventsList=EVENTS_POOL[eventName];eventsList.indexOf(handler)>-1&&(EVENTS_POOL[eventName]=eventsList.filter((fn)=>fn!==handler));}returnoriginalRemoveEventListener.apply(this,arguments);};functionmockPopStateEvent(state){returnnewPopStateEvent('popstate',{state});}//截取history的方法,因为pushState和replaceState方法并不会触发onpopstate事情,所以我们即便reroute方法是在onpopstate期间执行的,reroute方法应该也在这里执行constoriginalPushState=window.history.pushState;constoriginalReplaceState=window.history.replaceState;window.history.pushState=function(state,title,url){letresult=originalPushState.apply(this,arguments);重新路由(mockPopStateEvent(状态));返回结果;};window.history.replaceState=function(state,title,url){letresult=originalReplaceState.apply(this,arguments);重新路由(mockPopStateEvent(状态));returnresult;};//在执行完load、mount、unmout操作后,执行该函数,保证微前端的逻辑总是先执行。然后App中的Vue或者React相关的Router就可以接收到Location事件了。导出函数callCapturedEvents(eventArgs){如果(!eventArgs){返回;}if(!Array.isArray(eventArgs)){eventArgs=[eventArgs];}letname=eventArgs[0].type;如果(!HIJACK_EVENTS_NAME.test(名称)){返回;}EVENTS_POOL[name].forEach((handler)=>handler.apply(window,eventArgs));}上面的代码很简单,创建两个队列,使用数组实现constEVENTS_POOL={hashchange:[],popstate:[],};如果检测到hashchange和popstate这两个事件,并且队列中不存在它们对应的回调函数,则将它们放入队列中。(相当于redux中间件的原理)然后每次检测到路由变化,都会调用reroute函数:functionreroute(){invoke([],arguments);}这样每次路由切换,base最先知道变化等,base同步执行(阻塞)后,子应用的vue-Rourer或react-router-dom等库可以接手实现单页逻辑。那么,路由变化时如何加载子应用呢?像有些微前端框架会用到import-html之类的这些库,我们手写吧。逻辑大概是这样的。一共有四个端口。nginx反向代理命中基础服务器监听的端口(用户必须先根据域名访问),然后到不同子应用下的服务器拉取静态资源并加载。提示:所有子应用加载完成后,只加载到base的一个div标签中。实现原理与ReactDom.render()的源码相同。请参考我之前的文章原文:从头实现一个简单版的React(附源码)然后我们先写一个registerApp方法,接受一个入口参数,然后根据url变化加载子应用(第二个参数activeRulepassedin)/****@param{string}entry*@param{string}function*/constApps=[]//子应用队列函数registryApp(entry,activeRule){Apps.push({entry,activeRule})}注册后,需要找到需要加载的appexport。异步函数loadApp(){constshouldMountApp=Apps.filter(shouldBeActive);console.log(shouldMountApp,'shouldMountApp');//constres=awaitaxios.get(shouldMountApp.entry);fetch(shouldMountApp.entry).then(function(response){returnresponse.json();}).then(function(myJson){console.log(myJson,'myJson');});}shouldBeActive判断是否此时需要根据传入的规则进行挂载:exportfunctionshouldBeActive(app){returnapp.activeRule(window.location)}此时的res数据就是我们通过get请求获取到的子应用相关数据.现在我们添加subapp1和subapp2文件夹来模拟部署的子应用程序。我们使用静态资源服务器运行subapp1.js作为subapp1的静态资源服务器constexpress=require('express');subapp2.js作为subapp2的静态资源服务器constexpress=require('express');constapp=express();const{resolve}=require('path');app.use(express.static(resolve(__dirname,'../subapp1')));app.listen(8889,(err)=>{!err&&console.log('8889端口成功');});现在文件目录是这样的:base座index.html运行在1234端口,subapp1部署在8889端口,subapp2部署在8890端口,这样我们从base拉取资源的时候会跨域,所以静态资源服务器、webpack热更新服务器等服务器都需要添加cors头,允许跨域constexpress=require('express');constapp=express();const{resolve}=require('path');//设置跨域访问app.all('*',function(req,res,next){res.header('Access-Control-Allow-Origin','*');res.header('Access-Control-Allow-Headers','X-Requested-With');res.header('访问-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS');res.header('X-Powered-By','3.2.1')??;res.header('Content-Type','application/json;charset=utf-8');next();});app.use(express.static(resolve(__dirname,'../subapp1')));app.listen(8889,(err)=>{!err&&console.log('8889端口成功');});??:如果是dev模式,记得在webpack的热更新服务器配置跨域。如果你对webpack不是很熟悉,可以看我之前的文章:万字硬核从零开始实现webpack热更新HMR原文:如何自己实现一个简单的webpack构建工具【附源码】这里我使用nodemon开启静态资源servers,主要是简单,如果没有下载的话,可以:npminodemon-g或者yarnaddnodemonglobal这样,我们先访问8889和8890端口,看能不能访问。8889和8890都可以访问相应的资源,成功正式启用我们的微前端框架盘古。封装启动方法,启用需要挂载的APP。exportfunctionstart(){loadApp()}注册subapp1,subapp2,手动开启微前端import{registryApp,start}from'./src/index';registryApp('localhost:8889',(location)=>location.pathname==='/subapp1');registryApp('localhost:8890',(location)=>location.pathname==='/subapp2');start()修改index.html文件:Document

Base

子应用1
子应用2
ok,运行代码,发现挂了,为什么会挂?因为那里返回的是html文件,所以我这里使用的fetch请求是无法被json解析的,所以我们看看别人的微前端和第三方库的源码,比如库import-html-entry因为之前解析过qiankun这个微前端框架的源码,这里就不过多解释了,他们做了一个text()exportasyncfunctionloadApp(){constshouldMountApp=Apps.filter(shouldBeActive);console.log(shouldMountApp,'shouldMountApp');//constres=awaitaxios.get(shouldMountApp.entry);fetch(shouldMountApp.entry).then(function(response){returnresponse.text();}).then(function(myJson){console.log(myJson,'myJson');});}然后我们就可以了获取抓取的html文件(此时为字符串)由于实际项目,一般这个html文件会包含js和css的import标签,也就是我们当前的单页项目,类似如下:所以我们需要将脚本、样式和html文件分开。想用一个对象存储来拷贝某个微前端框架的源码,没想到就这样写了。今天主要说说原理,或者写一个可以自己运行的。毕竟html文件都回来了,数据处理也就不难了exportasyncfunctionloadApp(){constshouldMountApp=Apps.filter(shouldBeActive);console.log(shouldMountApp,'shouldMountApp');//constres=awaitaxios.get(shouldMountApp.entry);fetch(shouldMountApp[0].entry).then(function(response){returnresponse.text();}).then(function(text){constdom=document.createElement('div');dom.innerHTML=text;console.log(dom,'dom');});}先改造一下,打印DOM,发现可以获取到dom节点,那我就先处理一下,让它显示在baseexportasyncfunctionloadApp(){constshouldMountApp=Apps.filter(shouldBeActive);console.log(shouldMountApp,'shouldMountApp');//constres=awaitaxios.get(shouldMountApp.entry);fetch(shouldMountApp[0].entry).then(function(response){returnresponse.text();}).then(function(text){constdom=document.createElement('div');dom.innerHTML=文本;const内容=dom.querySelector('h1');constsubapp=document.querySelector('#subApp-content');subapp&&subapp.appendChild(content);});}至此,我们已经可以加载不同版本的微前端框架完成,后续会逐步完善所有功能,向主流微前端框架靠拢,完美支持IE11。记得它叫:盘古推荐阅读之前的手写ws协议:深度:手写一个WebSocket协议【7000字】最后欢迎加我微信(CALASFxiaotan),拉你进技术群,长期交流学习...欢迎关注“前端巅峰”,认真学习前端,做一个专业的技术人...点赞支持我好不好转发