温锦福 發表於 2021-10-29 14:01:00

微前端开发(Vue)

<h2>一、<strong>微前端概述</strong></h2>
<h3>1.&nbsp;<strong>&nbsp;</strong><strong>什么是微前端?</strong></h3>
<p>  为了解决庞大的一整块后端服务带来的变更与扩展方面的限制,出现了微服务架构。<span style="font-family: 宋体">然而,越来越重的前端工程也面临同样的问题,自然地想到了将微服务思想应用(照搬)到前端,于是有了</span>“微前端(micro-frontends)”的概念。即,一种由独立交付的多个前端应用组成整体的架构风格。具体的,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品。</p>
<p>  我们常见后台项目通常长这样:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133248803-214033379.png" alt="" loading="lazy"></p>
<p>  如果我们的项目需要开发某个新的功能,而这个功能另一个项目已经开发好,我们想直接复用时。</p>
<p><strong>  说明</strong><strong>:</strong>我们需要的只是别人项目的这个功能页面的内容部分,不需要别人项目的顶部导航和菜单。</p>
<p><span style="font-family: 宋体">一个比较笨的办法就是直接把别人项目这个页面的代码拷贝过来,但是万一别人不是</span> <span style="font-family: Calibri">vue </span><span style="font-family: 宋体">开发的,或者说 </span><span style="font-family: Calibri">vue </span><span style="font-family: 宋体">版本、</span><span style="font-family: Calibri">UI </span><span style="font-family: 宋体">库等不同,以及别人的页面加载之前操作(路由拦截,鉴权等)我们都需要拷贝过来,更重要的问题是,别人代码有更新,我们如何做到同步更新。</span>甚至当别的项目采用其它技术栈时,如何集成?显然复制代码是行不通的。</p>
<p><span style="font-family: 宋体">  以前端组件的概念作类比,我们可以把每个被拆分出的子应用看作是一个应用级组件,每个应用级组件专门实现某个特定的业务功能(如商品管理、订单管理等)。这里实际上谈到了微前端拆分的原则:即以业务功能为基本单元。经过拆分后,整个系统的结构也发生了变化:</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133313437-739287941.png" alt="" loading="lazy"></p>
<p>  如上图所示,<span style="font-family: 宋体">左侧是传统大型单页应用的前端架构,所有模块都在一个应用内,由应用本身负责路由管理,是应用分发路由的方式;而右侧是基座模式下的系统架构,各个子应用互不相关,单独运行在不同的服务上,由基座应用根据路由选择加载哪个应用到页面内,是路由分发应用的方式。这种方式使得各个模块的耦合性大大降低,而微前端需要解决的主要问题就是如何拆分和组织这些子应用。</span></p>
<p><span style="font-family: 宋体">典型的基于</span><span style="font-family: Calibri">vue-router</span><span style="font-family: 宋体">的</span><span style="font-family: Calibri">Vue</span><span style="font-family: 宋体">应用与这种架构存在着很大的相似性:</span></p>
<p><span style="font-family: 宋体"><img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133324924-1025601231.png" alt="" loading="lazy"></span></p>
<p>&nbsp;</p>
<h3>2.&nbsp;<strong>&nbsp;</strong><strong>巨无霸项目的</strong><strong>存在的问题</strong></h3>
<ul>
<li><span style="font-family: 宋体">代码越来越多,打包越来越慢,部署升级麻烦,一些插件的升级和公共组件的修改需要考虑的更多,很容易牵一发而动全身</span>。</li>
<li><span style="font-family: 宋体">项目太大,参与人员越多,代码规范比较难管理,代码冲突也频繁。</span></li>
<li><span style="font-family: 宋体">产品功能齐全,但是客户往往只需要其中的部分功能。剥离不需要的代码后,需要独立制定版本,独立维护,增加人力成本。</span></li>
</ul>
<p><span style="font-family: 宋体">  微前端的诞生也是为了解决以上问题:</span></p>
<ul>
<li><span style="font-family: 宋体">  复用(嵌入)别人的项目页面,但是别人的项目运行在他自己的环境之上。</span></li>
<li><span style="font-family: 宋体">  巨无霸应用拆分成一个个的小项目,这些小项目独立开发部署,又可以自由组合进行售卖。</span></li>
</ul>
<p><span style="font-family: 宋体">  使用微前端的好处:</span></p>
<ul>
<li> &nbsp; &nbsp;<span style="font-family: 宋体">技术栈无关,各个子项目可以自由选择框架,可以自己制定开发规范。</span></li>
<li> &nbsp;&nbsp;<span style="font-family: 宋体">快速打包,独立部署,互不影响,升级简单。</span></li>
<li> &nbsp;&nbsp;<span style="font-family: 宋体">可以很方便的复用已有的功能模块,避免重复开发。</span></li>
</ul>
<h2><strong>二、常见微前端方案</strong></h2>
<p>  目前主流的微前端方案包括以下几个:</p>
<ul>
<li>&nbsp;<span style="font-family: Calibri">iframe</span></li>
<li><span style="font-family: 宋体">基座模式,主要基于路由分发,</span><span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">和</span><span style="font-family: Calibri">single-spa</span><span style="font-family: 宋体">就是基于这种模式</span></li>
<li><span style="font-family: 宋体">组合式集成,即单独构建组件,按需加载,类似</span><span style="font-family: Calibri">npm</span><span style="font-family: 宋体">包的形式</span></li>
<li><span style="font-family: Calibri">EMP</span><span style="font-family: 宋体">,主要基于</span><span style="font-family: Calibri">Webpack5 Module Federation</span></li>
<li><span style="font-family: Calibri">Web Components</span></li>
</ul>
<p><span style="font-family: Calibri">  iframe</span><span style="font-family: 宋体">:是传统的微前端解决方案,基于</span><span style="font-family: Calibri">iframe</span><span style="font-family: 宋体">标签实现,技术难度低,隔离性和兼容性很好,但是性能和使用体验比较差,多用于集成第三方系统;</span></p>
<p>  基座模式:主要基于路由分发,即由一个基座应用来监听路由,并按照路由规则来加载不同的应用,以实现应用间解耦;</p>
<p>  组合式集成:把组件单独打包和发布,然后在构建或运行时组合。</p>
<p><span style="font-family: Calibri">  EMP</span><span style="font-family: 宋体">:基于</span><span style="font-family: Calibri">Webpack5 Module Federation</span><span style="font-family: 宋体">,一种去中心化的微前端实现方案,它不仅能很好地隔离应用,还可以轻松实现应用间的资源共享和通信</span>。</p>
<p><span style="font-family: Calibri">  Web Components</span><span style="font-family: 宋体">:是官方提出的组件化方案,它通过对组件进行更高程度的封装,来实现微前端,但是目前兼容性不够好,尚未普及。</span></p>
<p><span style="font-family: 宋体">  总的来说,</span><span style="font-family: Calibri">iframe</span><span style="font-family: 宋体">主要用于简单并且性能要求不高的第三方系统;组合式集成目前主要用于前端组件化,而不是微前端;基座模式、</span><span style="font-family: Calibri">EMP</span><span style="font-family: 宋体">和</span><span style="font-family: Calibri">Web Components</span><span style="font-family: 宋体">是目前主流的微前端方案。</span></p>
<p><span style="font-family: 宋体">  目前微前端</span>最常用的<span style="font-family: 宋体">有两种解决方案:</span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">方案和 </span>基座模式<span style="font-family: 宋体">方案</span>。</p>
<h3>1.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">iframe方案</span></strong></h3>
<p><span style="font-family: Calibri">  iframe </span><span style="font-family: 宋体">大家都很熟悉,使用简单方便,提供天然的 </span><span style="font-family: Calibri">js/css </span><span style="font-family: 宋体">隔离,也带来了数据传输的不便,一些数据无法共享(主要是本地存储、全局变量和公共插件),两个项目不同源(跨域)情况下数据传输需要依赖 </span><span style="font-family: Calibri">postMessage </span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: Calibri">  iframe </span><span style="font-family: 宋体">有很多坑,但是大多都有解决的办法:</span></p>
<p>  1. 页面加载问题</p>
<p><span style="font-family: Calibri">  iframe </span><span style="font-family: 宋体">和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载,阻塞 </span><span style="font-family: Calibri">onload </span><span style="font-family: 宋体">事件。每次点击都需要重新加载,虽然可以采用 </span><span style="font-family: Calibri">display:none </span><span style="font-family: 宋体">来做缓存,但是页面缓存过多会导致电脑卡顿。</span><span style="font-family: Calibri">(</span><span style="font-family: 宋体">无法解决</span><span style="font-family: Calibri">)</span></p>
<p>  2. 布局问题</p>
<p><span style="font-family: Calibri">  iframe </span><span style="font-family: 宋体">必须给一个指定的高度,否则会塌陷。</span></p>
<p><span style="font-family: 宋体">  解决办法:子项目实时计算高度并通过</span> <span style="font-family: Calibri">postMessage </span><span style="font-family: 宋体">发送给主页面,主页面动态设置 </span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">高度。有些情况会出现多个滚动条,用户体验不佳。</span></p>
<p>  3. 弹窗及遮罩层问题</p>
<p><span style="font-family: 宋体">  弹窗只能在</span> <span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">范围内垂直水平居中,没法在整个页面垂直水平居中。</span></p>
<p><span style="font-family: 宋体">  解决办法</span><span style="font-family: Calibri">1</span><span style="font-family: 宋体">:通过与框架页面消息同步解决,将弹窗消息发送给主页面,主页面来弹窗,对原项目改动大且影响原项目的使用。</span></p>
<p><span style="font-family: 宋体">  解决办法</span><span style="font-family: Calibri">2</span><span style="font-family: 宋体">:修改弹窗的样式:隐藏遮罩层,修改弹窗的位置。</span></p>
<p>  4.&nbsp;<span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">内的 </span><span style="font-family: Calibri">div </span><span style="font-family: 宋体">无法全屏</span></p>
<p>  弹窗的全屏,指的是在浏览器可视区全屏。这个全屏指的是占满用户屏幕。</p>
<p><span style="font-family: 宋体">  全屏方案,原生方法使用的是</span> <span style="font-family: Calibri">Element.requestFullscreen()</span><span style="font-family: 宋体">,插件:</span><span style="font-family: Calibri">vue-fullscreen</span><span style="font-family: 宋体">。当页面在 </span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">里面时,全屏会报错,且 </span><span style="font-family: Calibri">dom </span><span style="font-family: 宋体">结构错乱。</span></p>
<p><span style="font-family: 宋体">  解决方案:</span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">标签设置 </span><span style="font-family: Calibri">allow="fullscreen" </span><span style="font-family: 宋体">属性即可</span></p>
<p>  5.&nbsp;<span style="font-family: 宋体">浏览器前进</span><span style="font-family: Calibri">/</span><span style="font-family: 宋体">后退问题</span></p>
<p><span style="font-family: Calibri">  iframe </span><span style="font-family: 宋体">和主页面共用一个浏览历史,</span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">会影响页面的前进后退。大部分时候正常,</span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">多次重定向则会导致浏览器的前进后退功能无法正常使用。并且 </span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">页面刷新会重置(比如说从列表页跳转到详情页,然后刷新,会返回到列表页),因为浏览器的地址栏没有变化,</span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">的 </span><span style="font-family: Calibri">src </span><span style="font-family: 宋体">也没有变化。</span></p>
<p>  6.&nbsp;<span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">加载失败的情况不好处理</span></p>
<p><span style="font-family: 宋体">  非同源的</span> <span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">在火狐及 </span><span style="font-family: Calibri">chorme </span><span style="font-family: 宋体">都不支持 </span><span style="font-family: Calibri">onerror </span><span style="font-family: 宋体">事件。</span></p>
<p><span style="font-family: 宋体">  解决办法</span><span style="font-family: Calibri">1</span><span style="font-family: 宋体">:</span><span style="font-family: Calibri">onload </span><span style="font-family: 宋体">事件里面判断页面的标题,是否 </span><span style="font-family: Calibri">404 </span><span style="font-family: 宋体">或者 </span><span style="font-family: Calibri">500</span></p>
<p><span style="font-family: 宋体">  解决办法</span><span style="font-family: Calibri">2</span><span style="font-family: 宋体">:使用 </span><span style="font-family: Calibri">try catch </span><span style="font-family: 宋体">解决此问题,尝试获取 </span><span style="font-family: Calibri">contentDocument </span><span style="font-family: 宋体">时将抛出异常。</span></p>
<h3>2.&nbsp;<strong>&nbsp;</strong><strong>基座模式</strong><strong>方案</strong></h3>
<p><span style="font-family: 宋体">  基座模式方案以</span><span style="font-family: Calibri">single-spa</span><span style="font-family: 宋体">和</span><span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">为代表,这里我选择</span><span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">。</span></p>
<p>  qiankun <span style="font-family: 宋体">是蚂蚁金服开源的一款框架,它是基于 </span><span style="font-family: Calibri">single-spa </span><span style="font-family: 宋体">的。他在 </span><span style="font-family: Calibri">single-spa </span><span style="font-family: 宋体">的基础上,实现了开箱即用,除一些必要的修改外,子项目只需要做很少的改动,就能很容易的接入。</span></p>
<p><span style="font-family: Calibri">  qiankun</span><span style="font-family: 宋体">框架官网:</span><span style="text-decoration: underline"><span style="font-family: Calibri">https://qiankun.umijs.org/zh/</span><span style="font-family: 宋体">。</span></span></p>
<p><span style="font-family: 宋体">  微前端中子项目的入口文件常见的有两种方式:</span><span style="font-family: Calibri">JS entry </span><span style="font-family: 宋体">和 </span><span style="font-family: Calibri">HTML entry。</span><span style="font-family: 宋体">纯</span> <span style="font-family: Calibri">single-spa </span><span style="font-family: 宋体">采用的是 </span><span style="font-family: Calibri">JS entry</span><span style="font-family: 宋体">,而 </span><span style="font-family: Calibri">qiankun </span><span style="font-family: 宋体">既支持 </span><span style="font-family: Calibri">JS entry</span><span style="font-family: 宋体">,又支持 </span><span style="font-family: Calibri">HTML entry</span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: Calibri">  JS entry </span><span style="font-family: 宋体">的要求比较苛刻:</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">1</span><span style="font-family: 宋体">)将 </span><span style="font-family: Calibri">css </span><span style="font-family: 宋体">打包到 </span><span style="font-family: Calibri">js </span><span style="font-family: 宋体">里面</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">2</span><span style="font-family: 宋体">)去掉 </span><span style="font-family: Calibri">chunk-vendors.js</span><span style="font-family: 宋体">,</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">3</span><span style="font-family: 宋体">)去掉文件名的 </span><span style="font-family: Calibri">hash </span><span style="font-family: 宋体">值</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">4</span><span style="font-family: 宋体">)将 </span><span style="font-family: Calibri">single-spa </span><span style="font-family: 宋体">模式的入口文件</span><span style="font-family: Calibri">( app.js )</span><span style="font-family: 宋体">放置到 </span><span style="font-family: Calibri">index.html </span><span style="font-family: 宋体">目录,其他文件不变,原因是要截取 </span><span style="font-family: Calibri">app.js </span><span style="font-family: 宋体">的路径作为 </span><span style="font-family: Calibri">publicPath</span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: 宋体">  建议使用</span> <span style="font-family: Calibri">HTML entry </span><span style="font-family: 宋体">,使用起来和 </span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">一样简单,但是用户体验比 </span><span style="font-family: Calibri">iframe </span><span style="font-family: 宋体">强很多。</span><span style="font-family: Calibri">qiankun </span><span style="font-family: 宋体">请求到子项目的 </span><span style="font-family: Calibri">index.html </span><span style="font-family: 宋体">之后,会先用正则匹配到其中的 </span><span style="font-family: Calibri">js/css </span><span style="font-family: 宋体">相关标签,然后替换掉,它需要自己加载 </span><span style="font-family: Calibri">js </span><span style="font-family: 宋体">并运行,然后去掉 </span><span style="font-family: Calibri">html/head/body </span><span style="font-family: 宋体">等标签,剩下的内容原样插入到子项目的容器中 </span>。</p>
<h2>二、<strong>微前端方案实践</strong></h2>
<p><span style="font-family: 宋体">  以</span>“大数据分析”项目为例,将客户特有的需求,如“电子路单”、“数据填报”单独提取为可独立运行的子项目。</p>
<p>  大数据分析项目改造为主应用基座,代码仓库地址:<span style="text-decoration: underline"><span style="font-family: Calibri">http://192.168.1.102/zouqiongjun/big-data-web.git</span><span style="font-family: 宋体">。</span></span></p>
<p>  客户自定义需求单独作为子应用项目,代码仓库地址:<span style="text-decoration: underline"><span style="font-family: Calibri">http://192.168.1.102/zouqiongjun/zibo-custom-web.git</span><span style="font-family: 宋体">。</span></span></p>
<h3>1.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">各应用</span></strong><strong>工程代码结构</strong></h3>
<p><span style="font-family: Calibri">  sass-base-web</span><span style="font-family: 宋体">:主仓库,主要存放一些批量操作的脚本,用于聚合管理仓库和一键编译、一键部署。</span></p>
<p>  仓库代码结构如下图所示:</p>
<p><img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133414347-1561924370.png" alt="" loading="lazy">&nbsp;</p>
<p><span style="font-family: Calibri">  big-data-web</span>:大数据分析主应用</p>
<p><span style="font-family: Calibri">  zibo-custom-web</span>:客户自定义需求,微应用仓库</p>
<p><span style="font-family: 宋体">  子应用可以独立运行,但是当前子应用是直接嵌套在主应用的</span><span style="font-family: Calibri">main</span><span style="font-family: 宋体">内容区域,所以暂时并没有单独提供左侧菜单导航,后续如有需要可以扩展和补充此功能。</span></p>
<h3>2.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">主应用</span>big-data-web改造</strong></h3>
<p><span style="font-family: 宋体">  将普通的项目改造成</span> <span style="font-family: Calibri">qiankun </span><span style="font-family: 宋体">主应用基座,需要进行三步操作:</span></p>
<p>  (1)&nbsp;<span style="font-family: 宋体">创建微应用容器</span> <span style="font-family: Calibri">- </span><span style="font-family: 宋体">用于承载微应用,渲染显示微应用;</span></p>
<p>  (2)&nbsp;<span style="font-family: 宋体">注册微应用</span> <span style="font-family: Calibri">- </span><span style="font-family: 宋体">设置微应用激活条件,微应用地址等等;</span></p>
<p>  (3)&nbsp;<span style="font-family: 宋体">启动</span> <span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">;</span></p>
<p><span style="font-family: 宋体">  注意:由于</span><span style="font-family: Calibri">big-data-web</span><span style="font-family: 宋体">主应用的路由采用的是</span><span style="font-family: Calibri">hash</span><span style="font-family: 宋体">模式,所以子应用的路由也应该采用</span><span style="font-family: Calibri">hash</span><span style="font-family: 宋体">模式。</span></p>
<h4><strong>1</strong><strong><span style="font-family: Arial">.</span></strong><strong><span style="font-family: Arial">1</span></strong><strong>&nbsp;<span style="font-family: 黑体">安装</span> <span style="font-family: Arial">qiankun</span></strong></h4>
<div class="cnblogs_code">
<pre>$ yarn add qiankun # 或者 npm i qiankun -S</pre>
</div>
<h4><strong><span style="font-family: Arial">1.</span></strong><strong>2. <span style="font-family: 黑体">在主应用中注册微应用</span></strong></h4>
<p><span style="font-family: 宋体">  为了使用</span><span style="font-family: Calibri">keepAlive</span><span style="font-family: 宋体">缓存,这里我们采用</span><span style="font-family: 宋体">手动加载微应用的方式</span>。</p>
<p><span style="font-family: 宋体">  当微应用信息注册完之后,一旦浏览器的</span> <span style="font-family: Calibri">url </span><span style="font-family: 宋体">发生变化,便会自动触发 </span><span style="font-family: Calibri">qiankun </span><span style="font-family: 宋体">的匹配逻辑,所有 </span><span style="font-family: Calibri">activeRule </span><span style="font-family: 宋体">规则匹配上的微应用就会被插入到指定的 </span><span style="font-family: Calibri">container </span><span style="font-family: 宋体">中,同时依次调用微应用暴露出的生命周期钩子。</span></p>
<p><span style="font-family: 宋体">  在</span><span style="font-family: Calibri">views</span><span style="font-family: 宋体">目录下,新建</span><span style="font-family: Calibri">AppVueHash.vue</span><span style="font-family: 宋体">,作为子应用的容器,代码如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="zibo-custom-web"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
   <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="zibo-custom-web"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="app-view-box"</span><span style="color: rgba(0, 0, 255, 1)">&gt;&lt;/</span><span style="color: rgba(128, 0, 0, 1)">div</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">div</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">script</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">
export </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)">default</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)"> {};
</span><span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">script</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">style </span><span style="color: rgba(255, 0, 0, 1)">lang</span><span style="color: rgba(0, 0, 255, 1)">="scss"</span><span style="color: rgba(255, 0, 0, 1)"> scoped</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
.zibo-custom-web</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)">
position</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> relative</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span>
<span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<p><span style="font-family: 宋体">  这个</span><span style="font-family: Calibri">id</span><span style="font-family: 宋体">属性要唯一,最终子应用的内容将会挂载在这里。</span></p>
<p>  ContainerOther.vue代码改造:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">&lt;!--</span><span style="color: rgba(0, 128, 0, 1)"> 主体视图层 </span><span style="color: rgba(0, 128, 0, 1)">--&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="avue-view-contain"</span><span style="color: rgba(255, 0, 0, 1)"> v-show</span><span style="color: rgba(0, 0, 255, 1)">="!isSearch"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">keep-alive</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">router-view
            </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="avue-view keep-alive"</span><span style="color: rgba(255, 0, 0, 1)">
            v-if</span><span style="color: rgba(0, 0, 255, 1)">="$route.meta.keepAlive &amp;&amp; isActiveRoute"</span><span style="color: rgba(255, 0, 0, 1)">
            v-show</span><span style="color: rgba(0, 0, 255, 1)">="!showAppVueHash"</span>
            <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">keep-alive</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">router-view
            </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="avue-view"</span><span style="color: rgba(255, 0, 0, 1)">
            v-if</span><span style="color: rgba(0, 0, 255, 1)">="!$route.meta.keepAlive &amp;&amp; isActiveRoute"</span><span style="color: rgba(255, 0, 0, 1)">
            v-show</span><span style="color: rgba(0, 0, 255, 1)">="!showAppVueHash"</span>
          <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">AppVueHash </span><span style="color: rgba(255, 0, 0, 1)">v-show</span><span style="color: rgba(0, 0, 255, 1)">="showAppVueHash"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">div</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<p><span style="font-family: Calibri">js</span><span style="font-family: 宋体">代码:</span></p>
<div class="cnblogs_code">
<pre>import router from "@/router/router"<span style="color: rgba(0, 0, 0, 1)">;
import store from </span>"@/store"<span style="color: rgba(0, 0, 0, 1)">;
import AppVueHash from </span>"@/views/AppVueHash.vue"<span style="color: rgba(0, 0, 0, 1)">;
import { loadMicroApp } from </span>"qiankun"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">子项目路由前缀</span><span style="color: rgba(0, 0, 0, 1)">
const isChildRoute </span>= path =&gt; website.childRoute.some(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> path.startsWith(item));
const apps </span>=<span style="color: rgba(0, 0, 0, 1)"> [
{
    name: </span>"/zibo-custom-web"<span style="color: rgba(0, 0, 0, 1)">,
    entry: window.configs.VUE_APP_ZIBO_CUSTOM_URL,
    container: </span>"#zibo-custom-web"<span style="color: rgba(0, 0, 0, 1)">,
    props: { data: { store, router } },
    sandbox: {
      strictStyleIsolation: </span><span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启样式隔离</span><span style="color: rgba(0, 0, 0, 1)">
    }
}
];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">控制微应用手动加载</span><span style="color: rgba(0, 0, 0, 1)">
    ctrlMicroApp(path){
       </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isChildRoute(path)) {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.showAppVueHash = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.$nextTick(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">手动加载</span>
          <span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.mounted){
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.loadApps = apps.map(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> loadMicroApp(item))
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mounted=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
          }
      });
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.showAppVueHash = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }</span></pre>
</div>
<p><span style="font-family: 宋体">  这里的</span><span style="font-family: Calibri">container</span><span style="font-family: 宋体">属性值,必须和</span><span style="font-family: Calibri">AppVueHash.vue</span><span style="font-family: 宋体">组件中的</span><span style="font-family: Calibri">id</span><span style="font-family: 宋体">值保持一致。</span></p>
<p><span style="font-family: 宋体">  根据</span><span style="font-family: Calibri">url</span><span style="font-family: 宋体">地址判断是否是子应用,如果是子应用,则手动加载,否则隐藏子应用容器,只加载主应用的</span><span style="font-family: Calibri">router-view</span><span style="font-family: 宋体">。</span></p>
<p>  在ContainerOther第一次加载或路由变化时手动加载微应用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">mounted() {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.init();
    setTheme(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.themeName);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.ctrlMicroApp(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$route.path)
},
watch: {
    $route(val) {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.ctrlMicroApp(val.path);
},
    tagList(newVal,oldVal){
      let starts</span>=''<span style="color: rgba(0, 0, 0, 1)">;
      const childRoute </span>=<span style="color: rgba(0, 0, 0, 1)"> website.childRoute;
      childRoute.forEach((n,i)</span>=&gt;<span style="color: rgba(0, 0, 0, 1)">{
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(i&lt;childRoute.length-1<span style="color: rgba(0, 0, 0, 1)">){
          starts</span>+=`^${n}|<span style="color: rgba(0, 0, 0, 1)">`;
      }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
          starts</span>+=`^<span style="color: rgba(0, 0, 0, 1)">${n}`;
      }
      })
      const patt </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RegExp(`${starts}`);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">之前存在子应用页签</span><span style="color: rgba(0, 0, 0, 1)">
      const before </span>= oldVal.some(item=&gt;<span style="color: rgba(0, 0, 0, 1)">{
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> patt.test(item.value);
      });
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">现在存在子应用页签</span><span style="color: rgba(0, 0, 0, 1)">
      const now </span>= newVal.some(item=&gt;<span style="color: rgba(0, 0, 0, 1)">{
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> patt.test(item.value);
      });
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(before &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">now){
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mounted=<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.loadApps.forEach(app=&gt;<span style="color: rgba(0, 0, 0, 1)">{
          app.unmount();
      })
      }
    } </span></pre>
</div>
<p><span style="font-family: 宋体">  监听</span><span style="font-family: Calibri">tab</span><span style="font-family: 宋体">页签变化,关闭页签时,需要卸载子应用。</span></p>
<h3>3.&nbsp;<strong>&nbsp;</strong><strong>qiankun 子项目zibo-custom-web</strong></h3>
<ol>
<li><span style="font-family: 宋体">在</span> <span style="font-family: Calibri">src </span><span style="font-family: 宋体">目录新增文件 </span><span style="font-family: Calibri">public-path.js</span><span style="font-family: 宋体">:</span></li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (window.__POWERED_BY_QIANKUN__) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> eslint-disable-next-line no-undef</span><span style="color: rgba(0, 0, 0, 1)">
    __webpack_public_path__ </span>=<span style="color: rgba(0, 0, 0, 1)"> window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}</span></pre>
</div>
<ol>
<li><span style="font-family: 宋体">修改</span> <span style="font-family: Calibri">index.html </span><span style="font-family: 宋体">中项目初始化的容器,不要使用 </span><span style="font-family: Calibri">#app </span><span style="font-family: 宋体">,避免与其他的项目冲突,建议小驼峰写法</span></li>
</ol>
<div class="cnblogs_code">
<pre>    &lt;div id="appVueHash"&gt;&lt;/div&gt;</pre>
</div>
<ol>
<li><span style="font-family: 宋体">修改入口文件</span> <span style="font-family: Calibri">main.js</span><span style="font-family: 宋体">:</span></li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -----------子应用微前端start-------------------</span><span style="color: rgba(0, 0, 0, 1)">
let router </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
let instance </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">function</span> render({ data = {} , container } =<span style="color: rgba(0, 0, 0, 1)"> {}) {
router </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> VueRouter({
    routes,
});
instance </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Vue({
    router,
    store,
    data(){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      parentRouter: data.router,
      parentVuex: data.store,
      }
    },
    render: h </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> h(App),
}).$mount(container </span>? container.querySelector('#appVueHash') : '#appVueHash'<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">window.__POWERED_BY_QIANKUN__) {
render();
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">测试全局变量污染</span><span style="color: rgba(0, 0, 0, 1)">
console.log(</span>'window.a'<span style="color: rgba(0, 0, 0, 1)">,window.a)
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> bootstrap() {
console.log(</span>'vue app bootstraped'<span style="color: rgba(0, 0, 0, 1)">);
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> mount(props) {
console.log(</span>'props from main framework'<span style="color: rgba(0, 0, 0, 1)">, props.data);
render(props);
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> unmount() {
instance.$destroy();
instance.$el.innerHTML </span>= ""<span style="color: rgba(0, 0, 0, 1)">;
instance </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
router </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -----------子应用微前端end-------------------</span></pre>
</div>
<p align="justify"><span style="font-family: 宋体">  主要改动是:引入修改</span> <span style="font-family: Calibri">publicPath </span><span style="font-family: 宋体">的文件和 </span><span style="font-family: Calibri">export </span><span style="font-family: 宋体">三个生命周期。</span></p>
<p align="justify"><span style="font-family: 宋体">  注意:</span></p>
<ul>
<li>webpack <span style="font-family: 宋体">的 </span><span style="font-family: Calibri">publicPath </span><span style="font-family: 宋体">值只能在入口文件修改,之所以单独写到一个文件并在入口文件最开始引入,是因为这样做可以让下面所有的代码都能使用这个。</span></li>
<li><span style="font-family: 宋体">路由文件需要</span> <span style="font-family: Calibri">export </span><span style="font-family: 宋体">路由数据,而不是实例化的路由对象,路由的钩子函数也需要移到入口文件。</span></li>
<li><span style="font-family: 宋体">在</span> <span style="font-family: Calibri">mount </span><span style="font-family: 宋体">生命周期,可以拿到父项目传递过来的数据,</span><span style="font-family: Calibri">router </span><span style="font-family: 宋体">用于跳转到主项目</span><span style="font-family: Calibri">/</span><span style="font-family: 宋体">其他子项目的路由,</span><span style="font-family: Calibri">store </span><span style="font-family: 宋体">是父项目的实例化的 </span><span style="font-family: Calibri">Vuex</span><span style="font-family: 宋体">(也可以传递其他数据过来)。</span></li>
</ul>
<ol>
<li><span style="font-family: 宋体">修改打包配置</span> <span style="font-family: Calibri">vue.config.js:</span></li>
</ol>
<div class="cnblogs_code">
<pre>const { name } = require("./package"<span style="color: rgba(0, 0, 0, 1)">);
module.exports </span>=<span style="color: rgba(0, 0, 0, 1)"> {
outputDir: </span>"../sass-base-web/cicd-config/test/zibo-custom-web"<span style="color: rgba(0, 0, 0, 1)">,
devServer: {
    port: </span>9010<span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭主机检查,使微应用可以被 fetch</span>
    disableHostCheck: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置跨域请求头,解决开发环境的跨域问题</span>
<span style="color: rgba(0, 0, 0, 1)">    headers: {
      </span>"Access-Control-Allow-Origin": "*"<span style="color: rgba(0, 0, 0, 1)">,
    },
    proxy: {
      </span>"/api"<span style="color: rgba(0, 0, 0, 1)">: {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">本地服务接口地址</span>
      target: "http://192.168.10.112:10067", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">cas</span>
      ws: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
      pathRewrite: {
          </span>"^/api": "/"<span style="color: rgba(0, 0, 0, 1)">,
      },
      },
    },
},
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下配置可以修复一些字体文件加载路径问题</span>
chainWebpack: (config) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">忽略的打包文件</span>
<span style="color: rgba(0, 0, 0, 1)">    config.externals({
      vue: </span>"Vue"<span style="color: rgba(0, 0, 0, 1)">,
      </span>"vue-router": "VueRouter"<span style="color: rgba(0, 0, 0, 1)">,
      vuex: </span>"Vuex"<span style="color: rgba(0, 0, 0, 1)">,
      axios: </span>"axios"<span style="color: rgba(0, 0, 0, 1)">,
      </span>"element-ui": "ELEMENT"<span style="color: rgba(0, 0, 0, 1)">,
    });
    config
      .plugin(</span>'html'<span style="color: rgba(0, 0, 0, 1)">)
      .tap(args </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      args[</span>0].name =<span style="color: rgba(0, 0, 0, 1)"> name;
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> args
      });
    config.module
      .rule(</span>"fonts"<span style="color: rgba(0, 0, 0, 1)">)
      .test(</span>/.(ttf|otf|eot|woff|woff2)$/<span style="color: rgba(0, 0, 0, 1)">)
      .use(</span>"url-loader"<span style="color: rgba(0, 0, 0, 1)">)
      .loader(</span>"url-loader"<span style="color: rgba(0, 0, 0, 1)">)
      .tap((options) </span>=&gt; ({ name: "/fonts/.."<span style="color: rgba(0, 0, 0, 1)"> }))
      .end();
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 自定义webpack配置</span>
<span style="color: rgba(0, 0, 0, 1)">configureWebpack: {
    output: {
      library: `${name}</span>-`, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 微应用的包名,这里与主应用中注册的微应用名称一致</span>
      libraryTarget: "umd", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 把子应用打包成 umd 库格式</span>
      jsonpFunction: `webpackJsonp_${name}`, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">webpack打包之后保存在window中的key,各个子应用不一致</span>
<span style="color: rgba(0, 0, 0, 1)">    },
},
};</span></pre>
</div>
<p align="justify"><span style="font-family: 宋体">  这里主要就两个配置,一个是允许跨域,另一个是打包成</span> <span style="font-family: Calibri">umd </span><span style="font-family: 宋体">格式。为什么要打包成 </span><span style="font-family: Calibri">umd </span><span style="font-family: 宋体">格式呢?是为了让 </span><span style="font-family: Calibri">qiankun </span><span style="font-family: 宋体">拿到其 </span><span style="font-family: Calibri">export </span><span style="font-family: 宋体">的生命周期函数。</span></p>
<p align="justify"><strong>  注意</strong><strong><span style="font-family: 宋体">:</span></strong>&nbsp;<span style="font-family: 宋体">这个</span> <span style="font-family: Calibri">name </span><span style="font-family: 宋体">默认从 </span><span style="font-family: Calibri">package.json </span><span style="font-family: 宋体">获取,可以自定义,只要和父项目注册时的 </span><span style="font-family: Calibri">name </span><span style="font-family: 宋体">保持一致即可。</span></p>
<p align="justify">  vue.config.js中</p>
<div class="cnblogs_code">
<pre>outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web",</pre>
</div>
<p align="justify"><span style="font-family: 宋体">  这里我子应用项目编译后会将打包文件打包到</span><span style="font-family: Calibri">sass-base-web</span><span style="font-family: 宋体">项目中的</span><span style="font-family: Calibri">zibo-custom-web</span><span style="font-family: 宋体">下。</span></p>
<ol>
<li>路由动态加载</li>
</ol>
<p align="justify"><span style="font-family: 宋体">  需要给子项目所有的路由都添加一个前缀,子项目的路由跳转如果之前使用的是</span> <span style="font-family: Calibri">path </span><span style="font-family: 宋体">也需要修改,用 </span><span style="font-family: Calibri">name </span><span style="font-family: 宋体">跳转则不用</span>。</p>
<p align="justify">  avue-router.js:</p>
<div class="cnblogs_code">
<pre> const oRouter =<span style="color: rgba(0, 0, 0, 1)"> {
          path: </span>"/zibo-custom-web"<span style="color: rgba(0, 0, 0, 1)">,
          name: </span>"RouterView"<span style="color: rgba(0, 0, 0, 1)">,
          component(resolve) {
            require([</span>"@/components/RouterView.vue"<span style="color: rgba(0, 0, 0, 1)">], resolve);
          },

          children: [
            {
            path: path,
            component(resolve) {
                require([`..</span>/${component}.vue`], resolve);
<span style="color: rgba(0, 0, 0, 1)">            },
            name: name,
            meta: meta,
            },
          ],
      };
      aRouter.push(oRouter);</span></pre>
</div>
<h3>4.&nbsp;<strong>&nbsp;</strong><strong>状态管理,主应用和微应用之间的通信</strong></h3>
<p align="justify">  qiankun <span style="font-family: 宋体">通过 </span><span style="font-family: Calibri">initGlobalState</span><span style="font-family: 宋体">: 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 </span><span style="font-family: Calibri">props </span><span style="font-family: 宋体">获取通信方法</span><span style="font-family: Calibri">;</span></p>
<p align="justify">  onGlobalStateChange<span style="font-family: 宋体">: 在当前应用监听全局状态,有变更触发 </span><span style="font-family: Calibri">callback;</span></p>
<p align="justify">  setGlobalState<span style="font-family: 宋体">: 按一级属性设置全局状态,微应用中只能修改已存在的一级属性</span><span style="font-family: Calibri">; </span><span style="font-family: 宋体">换句话说只能修改主用于预先定义的属性,后面添加的属性无效</span>。</p>
<p align="justify"><span style="font-family: 宋体">  官方</span>例<span style="font-family: 宋体">子</span>:<span style="font-family: 宋体">发布</span><span style="font-family: Calibri">-</span><span style="font-family: 宋体">订阅的设计模式:</span></p>
<p align="justify">  主应用:</p>
<div class="cnblogs_code">
<pre>import { initGlobalState, MicroAppStateActions } from 'qiankun'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化 state</span>
const actions: MicroAppStateActions =<span style="color: rgba(0, 0, 0, 1)"> initGlobalState(state);

actions.onGlobalStateChange((state, prev) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> state: 变更后的状态; prev 变更前的状态</span>
<span style="color: rgba(0, 0, 0, 1)">console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();</span></pre>
</div>
<p align="justify">  子应用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从生命周期 mount 中获取通信方法,使用方式和 master 一致</span>
export <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> mount(props) {
    props.onGlobalStateChange((state, prev) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> state: 变更后的状态; prev 变更前的状态</span>
<span style="color: rgba(0, 0, 0, 1)">      console.log(state, prev);
    });
    props.setGlobalState(state);
}</span></pre>
</div>
<p align="justify"><span style="font-family: 宋体">  如果主应用和子应用都是</span><span style="font-family: Calibri">vue</span><span style="font-family: 宋体">技术栈,可以在子应用中把数据传递给子应用,例如</span><span style="font-family: Calibri">store</span><span style="font-family: 宋体">。</span></p>
<p>&nbsp;&nbsp;&nbsp;props:&nbsp;{&nbsp;data:&nbsp;{&nbsp;store,&nbsp;router&nbsp;}&nbsp;},</p>
<h3>5.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">各应用之间的独立仓库以及聚合管理</span></strong></h3>
<p align="justify"><span style="font-family: 宋体">  实际开发中项目存储在公司仓库中,以</span> <span style="font-family: Calibri">gitLab </span><span style="font-family: 宋体">为例</span><span style="font-family: Calibri">, </span><span style="font-family: 宋体">当子应用一多,全部放在一个仓库下面, 这时候就显得很臃肿了,也很庞大,大大的增加了维护成本,和开发效率;</span></p>
<p align="justify"><span style="font-family: 宋体">我们可以通过</span> <span style="font-family: Calibri">sh </span><span style="font-family: 宋体">脚本, 初始只需要克隆主仓库代码, 然后通过 </span><span style="font-family: Calibri">sh </span><span style="font-family: 宋体">脚本去一键拉取所有子应用</span>代码。</p>
<p>  这里我将单独创建一个用来编译和打包的主仓库项目sass-base-web,仓库地址:<span style="text-decoration: underline"><span style="font-family: Calibri">http://192.168.1.102/zouqiongjun/sass-base-web.git</span><span style="font-family: 宋体">。</span></span></p>
<p align="justify">  项目根目录下,<span style="font-family: 宋体">新建</span> <span style="font-family: Calibri">script/clone-all.sh </span><span style="font-family: 宋体">文件</span>,<span style="font-family: 宋体">内容如下</span>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 子服务 gitLab 地址</span><span style="color: rgba(0, 0, 0, 1)">
SUB_SERVICE_GIT</span>=('http://192.168.1.102/zouqiongjun/big-data-web.git' 'http://192.168.1.102/zouqiongjun/zibo-custom-<span style="color: rgba(0, 0, 0, 1)">web.git')
SUB_SERVICE_NAME</span>=('big-data-web' 'zibo-custom-<span style="color: rgba(0, 0, 0, 1)">web')

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 子服务</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> [ ! -d <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sub-service</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> ]; then
echo '创建sub</span>-<span style="color: rgba(0, 0, 0, 1)">service目录...'
mkdir sub</span>-<span style="color: rgba(0, 0, 0, 1)">service
fi
echo '进入sub</span>-<span style="color: rgba(0, 0, 0, 1)">service目录...'
cd sub</span>-<span style="color: rgba(0, 0, 0, 1)">service

</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 遍历克隆微服务</span>
<span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span> ${!SUB_SERVICE_NAME[@<span style="color: rgba(0, 0, 0, 1)">]}
</span><span style="color: rgba(0, 0, 255, 1)">do</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> [ ! -d ${SUB_SERVICE_NAME[<span style="color: rgba(128, 0, 128, 1)">$i</span><span style="color: rgba(0, 0, 0, 1)">]} ]; then
    echo '克隆微服务项目'</span>${SUB_SERVICE_NAME[<span style="color: rgba(128, 0, 128, 1)">$i</span><span style="color: rgba(0, 0, 0, 1)">]}
    git clone </span>${SUB_SERVICE_GIT[<span style="color: rgba(128, 0, 128, 1)">$i</span><span style="color: rgba(0, 0, 0, 1)">]}
fi
done

echo '脚本结束...'
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 克隆完成</span></pre>
</div>
<p>  当我们启动主项目的时候,如果想要使用所有子应用的功能,子应用,需要一个一个启动,这样无论是开发还是编译都十分不便。</p>
<p><span style="font-family: 宋体">  考虑到国内使用</span><span style="font-family: Calibri">npm</span><span style="font-family: 宋体">装包可能会由于网络的原因安装失败,可以让</span><span style="font-family: Calibri">npm</span><span style="font-family: 宋体">使用国内淘宝镜像。</span></p>
<p>  执行命令:npm config set registry https://registry.npm.taobao.org。</p>
<p><span style="font-family: 宋体">  在这个主仓库项目里面,我们只需要安装一个</span><span style="font-family: Calibri">npm-run-all</span><span style="font-family: 宋体">插件。</span></p>
<p><span style="font-family: 宋体">  运行:</span><span style="font-family: Calibri">yarn add npm-run-all -D</span><span style="font-family: 宋体">或者</span><span style="font-family: Calibri">npm i -D</span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: 宋体">  该项目下只有一个</span><span style="font-family: Calibri">package.json</span><span style="font-family: 宋体">文件,用于配置编译和打包命令,代码如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"name": "sass-big-data-web",
"version": "1.0.0",
"description": "`qiankun`来实现`vue`技术栈的前端微服务",
"main": "index.js",
"scripts"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"clone:all": "bash ./scripts/clone-all.sh",
    "install:zibo": "cd ./sub-service/zibo-custom-web &amp;&amp; npm install",
    "install:main": "cd ./sub-service/big-data-web &amp;&amp; npm install",
    "install-all": "npm-run-all install:*",
    "start:zibo": "cd ./sub-service/zibo-custom-web &amp;&amp; npm run serve ","<span style="color: rgba(0, 0, 0, 1)">,
    </span>"<span style="color: rgba(0, 0, 255, 1)">start</span><span style="color: rgba(128, 0, 0, 1)">:main</span>": "<span style="color: rgba(0, 0, 255, 1)">cd</span> ./sub-service/big-data-web &amp;&amp; npm <span style="color: rgba(0, 0, 255, 1)">run</span> serve"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"<span style="color: rgba(0, 0, 255, 1)">start</span>-all": "npm-<span style="color: rgba(0, 0, 255, 1)">run</span>-all --parallel <span style="color: rgba(0, 0, 255, 1)">start</span>:*"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"serve-all": "npm-<span style="color: rgba(0, 0, 255, 1)">run</span>-all --parallel <span style="color: rgba(0, 0, 255, 1)">start</span>:*"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"build<span style="color: rgba(128, 0, 0, 1)">:zibo</span>": "<span style="color: rgba(0, 0, 255, 1)">cd</span> ./sub-service/zibo-custom-web &amp;&amp; npm <span style="color: rgba(0, 0, 255, 1)">run</span> build"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"build<span style="color: rgba(128, 0, 0, 1)">:main</span>": "<span style="color: rgba(0, 0, 255, 1)">cd</span> ./sub-service/big-data-web &amp;&amp; npm <span style="color: rgba(0, 0, 255, 1)">run</span> build"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"build-all": "npm-<span style="color: rgba(0, 0, 255, 1)">run</span>-all --parallel build:*"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"repository"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"<span style="color: rgba(0, 0, 255, 1)">type</span>": "git"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"url": "http://192.168.1.102/zouqiongjun/sass-big-data-web.git"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"keywords"<span style="color: rgba(0, 0, 0, 1)">: [],
</span>"author": ""<span style="color: rgba(0, 0, 0, 1)">,
</span>"license": "ISC"<span style="color: rgba(0, 0, 0, 1)">,
</span>"devDependencies"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"npm-<span style="color: rgba(0, 0, 255, 1)">run</span>-all": "^4.1.5"<span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p>  要执行yarn clone:all<span style="font-family: 宋体">,需要用到</span><span style="font-family: Calibri">bash</span><span style="font-family: 宋体">,跳转到</span>sass-base-web<span style="font-family: 宋体">目录,右键鼠标打开</span><span style="font-family: Calibri">Git bash</span><span style="font-family: 宋体">窗口,如下图所示:</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133519344-1580587434.png" alt="" loading="lazy"></p>
<p><span style="font-family: 宋体">  这样当我们把各个代码仓库的代码都拉取到</span><span style="font-family: Calibri">sub-service</span><span style="font-family: 宋体">这个目录下面来,如下图所示:</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133529174-1598341760.png" alt="" loading="lazy"></p>
<p align="justify"><span style="font-family: 宋体">  代码拉取完成后,</span> 紧接着就是下载各个项目的依赖及运行。</p>
<p><span style="font-family: 宋体">  运行</span><span style="font-family: Calibri">npm run serve-all</span><span style="font-family: 宋体">则可以自动执行</span><span style="font-family: Calibri">package.json</span><span style="font-family: 宋体">中配置的命令,这个命令最终会执行以下三个执行命令:</span></p>
<div class="cnblogs_code">
<pre>    "start:zibo": "cd ../zibo-custom-web &amp;&amp; npm run serve ",
    "start:main": "cd ../big-data-web &amp;&amp; npm run serve",</pre>
</div>
<p>  这样就不需要我们自己一个一个单独的去运行各个项目了。</p>
<p align="justify">  总体运行<span style="font-family: 宋体">步骤:</span> 第一步 <span style="font-family: Calibri">clone </span><span style="font-family: 宋体">主应用, 然后依次执行 </span><span style="font-family: Calibri">yarn clone:all --&gt; yarn install</span><span style="font-family: Calibri">-all</span>&nbsp;--&gt; yarn start<span style="font-family: Calibri">-all</span>&nbsp;<span style="font-family: 宋体">即可运行整个项目</span>。</p>
<p align="justify">  build-all:可以编译整个项目。</p>
<p align="justify">  sub-service<span style="font-family: 宋体">目录,将其添加到</span><span style="font-family: Calibri">.gitignore</span><span style="font-family: 宋体">当中,因为在</span>sass-base-web项目当中,我们只需要配置和编译及打包用,并不需要真正的将所有子应用的代码都提交到sass-base-web项目中,各子应用都有自己私有的仓库。</p>
<h3>6.&nbsp;<strong>&nbsp;</strong><strong>子项目开发的一些注意事项</strong></h3>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">1</span><span style="font-family: 宋体">)</span><span style="font-family: 宋体">所有的资源(图片</span><span style="font-family: Calibri">/</span><span style="font-family: 宋体">音视频等)都应该放到 </span><span style="font-family: Calibri">src </span><span style="font-family: 宋体">目录,不要放在 </span><span style="font-family: Calibri">public </span><span style="font-family: 宋体">或者</span><span style="font-family: Calibri">static</span>。<span style="font-family: 宋体">资源放</span> <span style="font-family: Calibri">src </span><span style="font-family: 宋体">目录,会经过 </span><span style="font-family: Calibri">webpack </span><span style="font-family: 宋体">处理,能统一注入 </span><span style="font-family: Calibri">publicPath</span><span style="font-family: 宋体">。否则在主项目中会</span><span style="font-family: Calibri">404</span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">2</span><span style="font-family: 宋体">)</span><span style="font-family: 宋体">避免</span> <span style="font-family: Calibri">css </span><span style="font-family: 宋体">污染。</span><span style="font-family: 宋体">组件内样式的</span> <span style="font-family: Calibri">css-scoped </span><span style="font-family: 宋体">是必须的。</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">3</span><span style="font-family: 宋体">)</span><span style="font-family: 宋体">给</span> <span style="font-family: Calibri">body </span><span style="font-family: 宋体">、 </span><span style="font-family: Calibri">document </span><span style="font-family: 宋体">等绑定的事件,请在 </span><span style="font-family: Calibri">unmount </span><span style="font-family: 宋体">周期清除</span></p>
<p><span style="font-family: 宋体">  (</span><span style="font-family: Calibri">4</span><span style="font-family: 宋体">)</span><span style="font-family: 宋体">谨慎使用</span> <span style="font-family: Calibri">position</span><span style="font-family: 宋体">:</span><span style="font-family: Calibri">fixed</span></p>
<p><span style="font-family: 宋体">  在父项目中,这个定位未必准确,应尽量避免使用,确有相对于浏览器窗口定位需求,可以用</span> <span style="font-family: Calibri">position: sticky</span><span style="font-family: 宋体">,但是会有兼容性问题(</span><span style="font-family: Calibri">IE</span><span style="font-family: 宋体">不支持)。</span></p>
<p><span style="font-family: 宋体">  常见问题见官网:</span><span style="font-family: Calibri">https://qiankun.umijs.org/zh/faq</span></p>
<h3>7.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">部署</span></strong></h3>
<h4>1.&nbsp;<strong>&nbsp;<span style="font-family: 黑体">常规部署</span></strong></h4>
<p><span style="font-family: 宋体">  主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务</span>。</p>
<p><span style="font-family: 宋体">  场景:主应用和微应用部署到同一个服务器(同一个</span><span style="font-family: Calibri">IP</span><span style="font-family: 宋体">和端口)</span></p>
<p><span style="font-family: 宋体">  如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。通常的做法是主应用部署在一级目录,微应用部署在二</span><span style="font-family: Calibri">/</span><span style="font-family: 宋体">三级目录。</span></p>
<p>  若<span style="font-family: 宋体">微应用想部署在非根目录,在微应用打包之前需要做两件事:</span></p>
<ul>
<li><span style="font-family: 宋体">必须配置</span> <span style="font-family: Calibri">webpack </span><span style="font-family: 宋体">构建时的 </span><span style="font-family: Calibri">publicPath </span><span style="font-family: 宋体">为目录名称,更多信息请看 </span><span style="font-family: Calibri">webpack </span><span style="font-family: 宋体">官方说明 和 </span><span style="font-family: Calibri">vue-cli3 </span><span style="font-family: 宋体">的官方说明</span>。</li>
<li>history <span style="font-family: 宋体">路由的微应用需要设置 </span><span style="font-family: Calibri">base </span><span style="font-family: 宋体">,值为目录名称,用于独立访问时使用。</span></li>
</ul>
<p><span style="font-family: 宋体">  部署之后注意三点:</span></p>
<ul>
<li>activeRule <span style="font-family: 宋体">不能和微应用的真实访问路径一样,否则在主应用页面刷新会直接变成微应用页面。</span></li>
<li><span style="font-family: 宋体">微应用的真实访问路径就是微应用的</span> <span style="font-family: Calibri">entry</span><span style="font-family: 宋体">,</span><span style="font-family: Calibri">entry </span><span style="font-family: 宋体">可以为相对路径。</span></li>
<li><span style="font-family: 宋体">微应用的</span> <span style="font-family: Calibri">entry </span><span style="font-family: 宋体">路径最后面的 </span><span style="font-family: Calibri">/ </span><span style="font-family: 宋体">不可省略,否则 </span><span style="font-family: Calibri">publicPath </span><span style="font-family: 宋体">会设置错误,例如子项的访问路径是 </span><span style="font-family: Calibri">http://localhost:8080/app1,</span><span style="font-family: 宋体">那么 </span><span style="font-family: Calibri">entry </span><span style="font-family: 宋体">就是 </span><span style="text-decoration: underline">http://localhost:8080/app1/<span style="font-family: 宋体">。</span></span></li>
</ul>
<p><span style="font-family: 宋体">  通过配置</span> <span style="font-family: Calibri">nginx </span><span style="font-family: 宋体">端口到目录的转发</span>。须要对外开放子利用对应的端口,将编译好的利用文件放到对应的配置目录。</p>
<p>  跳转到sass-base-web<span style="font-family: 宋体">目录,执行</span><span style="font-family: Calibri">npm run build-all</span><span style="font-family: 宋体">,会自动执行所有</span><span style="font-family: Calibri">build:</span><span style="font-family: 宋体">开头的命令:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">build:zibo</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cd ../zibo-custom-web &amp;&amp; npm run build</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">build:control</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cd ../control-center &amp;&amp; npm run build</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">build:main</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cd ../big-data-web &amp;&amp; npm run build</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">build-all</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">npm-run-all --parallel build:*</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p><span style="font-family: Calibri">  zibo-custom-web</span><span style="font-family: 宋体">目录结构如下图所示:</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133617307-2144423371.png" alt="" loading="lazy"></p>
<p><span style="font-family: 宋体">  这里是子应用和主应用部署在同一台服务器上,且</span><span style="font-family: Calibri">IP</span><span style="font-family: 宋体">和端口相同,</span><span style="font-family: Calibri">nginx</span><span style="font-family: 宋体">不需要额外设置。</span></p>
<p><span style="font-family: 宋体">  如果子应用和主应用部署在同一台服务器上,</span> 但是端口不同,需要修改<span style="font-family: Calibri">vue.config.js</span><span style="font-family: 宋体">中的</span><span style="font-family: Calibri">outputDir</span><span style="font-family: 宋体">,这个是打包编译后代码存放的路径,这个不做配置,默认会将代码编译打包到当前根目录下,并生成一个</span><span style="font-family: Calibri">dist</span><span style="font-family: 宋体">目录用于存放编译后的代码,如下图所示:</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/413851/202110/413851-20211029133628345-44997035.png" alt="" loading="lazy"></p>
<p><span style="font-family: 宋体">修改</span><span style="font-family: Calibri">nginx.conf</span><span style="font-family: 宋体">配置:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> #gzipon;
      upstream gateway { server </span>192.168.31.136:32067<span style="color: rgba(0, 0, 0, 1)">;}
   # 主应用
      server {
      listen   </span>32043<span style="color: rgba(0, 0, 0, 1)">;
      server_nameweb;
      root/dist;
      # 关闭端口重定向
      # port_in_redirect off;
      #charset koi8-r;
      access_log /var/log/nginx/nginx.log;

      location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }

      location ^~/api/ {
            proxy_read_timeout 600s;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_buffering off;
            rewrite ^/api/(.*)$ /$</span>1<span style="color: rgba(0, 0, 0, 1)"> break;
            proxy_pass http:</span>//gateway; <span style="color: rgba(0, 0, 0, 1)">
            }
      }

   # 子应用
    server {
      listen   </span>9010<span style="color: rgba(0, 0, 0, 1)">;
      server_namecus_web;
      # 子应用编译后的代码路径
      root/zibo-custom-web;
      # 允许跨域
      add_header Access-Control-Allow-Origin *;
      # 关闭端口重定向
      # port_in_redirect off;
      # charset koi8-r;
      access_log /var/log/nginx/nginx.log;
      location ^~/zibo-custom-web/ {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }

      location ^~/api/ {
            proxy_read_timeout 600s;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_buffering off;
            rewrite ^/api/(.*)$ /$</span>1<span style="color: rgba(0, 0, 0, 1)"> break;
            proxy_pass http:</span>//gateway; <span style="color: rgba(0, 0, 0, 1)">
            }
    }</span></pre>
</div>
<h4>2.&nbsp;<strong>&nbsp;</strong><strong><span style="font-family: Arial">docker nginx </span><span style="font-family: 黑体">配置</span></strong></h4>
<p><span style="font-family: 宋体">  此处</span><span style="font-family: Calibri">&nbsp;nginx&nbsp;</span><span style="font-family: 宋体">主要作用是用于端口目录转发,并配置主应用访问子应用的跨域问题。</span></p>
<p><span style="font-family: 宋体">  使用</span><span style="font-family: Calibri">&nbsp;</span>docker<span style="font-family: Calibri">&nbsp;</span><span style="font-family: 宋体">配置部署</span><span style="font-family: Calibri">&nbsp;</span>nginx:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># docker-compose.yml
version: </span>'3.1'<span style="color: rgba(0, 0, 0, 1)">
services:
nginx:
    restart: always
    image: nginx
    container_name: nginx
    ports:
      - </span>8888:80<span style="color: rgba(0, 0, 0, 1)">
      - </span>8889:8889<span style="color: rgba(0, 0, 0, 1)">
      - </span>7100:7100<span style="color: rgba(0, 0, 0, 1)">
      - </span>7101:7101<span style="color: rgba(0, 0, 0, 1)">
    volumes:
      - /app/volumes/nginx/nginx.conf:/etc/nginx/nginx.conf
      - /app/volumes/nginx/html:/usr/share/nginx/html
      - /app/micro/portal:/app/micro/portal
      - /app/micro/app1:/app/micro/app1
      - /app/micro/app2:/app/micro/app2</span></pre>
</div>
<p><span style="font-family: 宋体">  将编译后的主应用以及子应用放到对应的数据卷挂载目录即可,如主应用</span><span style="font-family: Calibri">&nbsp;</span>/app/micro/portal。<br><span style="font-family: 宋体">  同理,也需要将配置好的</span><span style="font-family: Calibri">&nbsp;</span>nginx.conf<span style="font-family: Calibri">&nbsp;</span><span style="font-family: 宋体">文件放到指定的数据卷挂载目录,使用</span><span style="font-family: Calibri">&nbsp;</span>docker-compose up -d<span style="font-family: Calibri">&nbsp;</span><span style="font-family: 宋体">启动即可。</span></p>
<p>  nginx<span style="font-family: Calibri">&nbsp;</span><span style="font-family: 宋体">端口目录转发配置:</span></p>
<div class="cnblogs_code">
<pre># nginx.<span style="color: rgba(0, 0, 0, 1)">conf
usernginx</span>;<span style="color: rgba(0, 0, 0, 1)">
worker_processes</span>1;<span style="color: rgba(0, 0, 0, 1)">
error_log</span>/var/log/nginx/error.log warn;<span style="color: rgba(0, 0, 0, 1)">
pid      </span>/var/<span style="color: rgba(0, 0, 255, 1)">run</span>/nginx.pid;<span style="color: rgba(0, 0, 0, 1)">

events {
    worker_connections</span>1024;<span style="color: rgba(0, 0, 0, 1)">}
http {
    include       </span>/etc/nginx/mime.types;<span style="color: rgba(0, 0, 0, 1)">
    default_typeapplication</span>/octet-stream;<span style="color: rgba(0, 0, 0, 1)">
    log_formatmain'</span>$remote_addr - $remote_user [$time_local] "$request"<span style="color: rgba(0, 0, 0, 1)"> '
                      '</span>$status $body_bytes_sent "$http_referer"<span style="color: rgba(0, 0, 0, 1)"> '
                      '</span>"$http_user_agent" "$http_x_forwarded_for"';<span style="color: rgba(0, 0, 0, 1)">

    access_log</span>/var/log/nginx/access.logmain;<span style="color: rgba(0, 0, 0, 1)">

    sendfile      </span><span style="color: rgba(0, 0, 255, 1)">on</span>;
    #tcp_nopush   <span style="color: rgba(0, 0, 255, 1)">on</span>;<span style="color: rgba(0, 0, 0, 1)">
    keepalive_timeout</span>65;

    #gzip<span style="color: rgba(0, 0, 255, 1)">on</span>;<span style="color: rgba(0, 0, 0, 1)">
    include </span>/etc/nginx/conf.d/*.conf;<span style="color: rgba(0, 0, 0, 1)">
    server {
      listen    </span>8889;<span style="color: rgba(0, 0, 0, 1)">
      server_name </span>192.168.2.192;<span style="color: rgba(0, 0, 0, 1)">
      location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
      root </span>/app/micro/portal;<span style="color: rgba(0, 0, 0, 1)">
      index index</span>.html;<span style="color: rgba(0, 0, 0, 1)">
      try_files </span>$uri $uri/ /index.html;<span style="color: rgba(0, 0, 0, 1)">
      }
    }
    server {
      listen    </span>7100;<span style="color: rgba(0, 0, 0, 1)">
      server_name </span>192.168.2.192;

      # 配置跨域访问,此处是通配符,严格生产环境的话可以指定为主应用 192.168.2.192:8889<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Origin *</span>;<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Methods 'GET</span>, POST, OPTIONS';<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Headers 'DNT</span>,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,<span style="color: rgba(0, 0, 255, 1)">If</span>-Modified-Since,<span style="color: rgba(0, 0, 255, 1)">Cache</span>-Control,Content-<span style="color: rgba(0, 0, 255, 1)">Type</span>,Authorization';<span style="color: rgba(0, 0, 0, 1)">
      location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
      root </span>/app/micro/app1;<span style="color: rgba(0, 0, 0, 1)">
      index index</span>.html;<span style="color: rgba(0, 0, 0, 1)">   
      try_files </span>$uri $uri/ /index.html;<span style="color: rgba(0, 0, 0, 1)">
      }
    }
    server {
      listen    </span>7101;<span style="color: rgba(0, 0, 0, 1)">
      server_name </span>192.168.2.192;
   
      # 配置跨域访问,此处是通配符,严格生产环境的话可以指定为主应用 192.168.2.192:8889<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Origin *</span>;<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Methods 'GET</span>, POST, OPTIONS';<span style="color: rgba(0, 0, 0, 1)">
      add_header Access-Control-Allow-Headers 'DNT</span>,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,<span style="color: rgba(0, 0, 255, 1)">If</span>-Modified-Since,<span style="color: rgba(0, 0, 255, 1)">Cache</span>-Control,Content-<span style="color: rgba(0, 0, 255, 1)">Type</span>,Authorization';<span style="color: rgba(0, 0, 0, 1)">
      
      location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
      root </span>/app/micro/app2;<span style="color: rgba(0, 0, 0, 1)">
      index index</span>.html;<span style="color: rgba(0, 0, 0, 1)">   
      try_files </span>$uri $uri/ /index.html;<span style="color: rgba(0, 0, 0, 1)">
      }
    }}</span></pre>
</div>
<p><span style="font-family: 宋体">  部署到生产,需要修改</span><span style="font-family: Calibri">big-data-web/public/util/config.js</span><span style="font-family: 宋体">中的</span><span style="font-family: Calibri">VUE_APP_ZIBO_CUSTOM_URL</span><span style="font-family: 宋体">配置项:</span></p>
<div class="cnblogs_code">
<pre>(<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
window.configs </span>=<span style="color: rgba(0, 0, 0, 1)"> {
    VUE_APP_CASLOGINURL: </span>"http://192.168.1.99:32080/cas", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">cas登录地址</span><span style="color: rgba(0, 0, 0, 1)">
    VUE_APP_REDIRECTURL: </span>"http://192.168.51.61:8888/big-data/", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">前端部署地址</span><span style="color: rgba(0, 0, 0, 1)">
    VUE_APP_SOCKET: </span>"ws://192.168.31.136:32061", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">websocket地址'</span><span style="color: rgba(0, 0, 0, 1)">
    VUE_APP_AMAPURLPREFIX: </span>"https://webapi.amap.com", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">高德地图地址</span><span style="color: rgba(0, 0, 0, 1)">
    VUE_APP_ZIBO_CUSTOM_URL:</span>"http://localhost:9010",<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自定义微应用地址</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">================================================</span>
<span style="color: rgba(0, 0, 0, 1)">
    VUE_APP_AMAPKEY: </span>"xxxxxx" <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">高德key</span><span style="color: rgba(0, 0, 0, 1)">
};
})();
</span></pre>
</div>
<p>  这里config.js是为了配置文件外置,不需要编译。</p>
<h3>8.&nbsp;<strong>&nbsp;<span style="font-family: 宋体">总结</span></strong></h3>
<p><span style="font-family: 宋体">  尽管</span><span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">框架支持各个子应用使用不同的技术框架,但是都需要子应用做相应的改造,而且在其它技术栈中总是会时不时的出现各种难以预料的错误,一旦出现问题,都需要我们去改造代码。所以如果都是</span><span style="font-family: Calibri">vue</span><span style="font-family: 宋体">技术栈,建议使用</span><span style="font-family: Calibri">qiankun</span><span style="font-family: 宋体">做微前端。</span></p>
<p><span style="font-family: 宋体">  如果是第三方公司的项目接入进来,由于他们的代码不受我们控制,需要酌情考虑是否用</span><span style="font-family: Calibri">iframe</span><span style="font-family: 宋体">。</span></p>
<p><span style="font-family: 宋体">  参考文献:</span>qiankun 微前端方案实践及总结</p>

</div>
<div id="MySignature" role="contentinfo">
    <div align="left"><div style="color: #111111"><table style="vertical-align: top">
<tbody><tr>
   <td id="tdSign">博客地址:</td><td>http://www.cnblogs.com/jiekzou/</td>
<td rowspan="2"></td>
</tr>
<tr>
   <td>博客版权:</td><td>本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。<br>如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!<br>再次感谢您耐心的读完本篇文章。 </td>
</tr>
<tr><td>其它:</td><td>
.net-QQ群4:<span style="color: green">612347965</span>
java-QQ群:<span style="color: green">805741535</span>
H5-QQ群:<span style="color: green">773766020</span><br>
<div>我的拙作
《Vue3.x+TypeScript实践指南》
《ASP.NET MVC企业级实战》
《H5+移动应用实战开发》
《Vue.js 2.x实践指南》
《JavaScript实用教程 》
《Node+MongoDB+React 项目实战开发》
已经出版,希望大家多多支持!</div></td></tr>
</tbody></table></div>
</div>
<p style=" margin-bottom:-13px;padding-top: 15px;"><img src="https://images.cnblogs.com/cnblogs_com/jiekzou/780174/t_240929014358_%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20240929094331.png" style=" border:2px solid #ddd;border-radius:20px;" height="326" width="235"></p><br><br>
来源:https://www.cnblogs.com/jiekzou/p/15480246.html
頁: [1]
查看完整版本: 微前端开发(Vue)