转发连接:
https://juejin.im/post/5ed73b73e51d4578724e3fa4javascript
前面小编也发布过关于微前端 qiankun的文章:css
深刻浅出解析阿里成熟的微前端框架 qiankun乾坤源码【图文并茂】html
咱们先来看两个实际的场景:前端
一般,咱们的后台项目都长这样:vue
若是咱们的项目须要开发某个新的功能,而这个功能另外一个项目已经开发好,咱们想直接复用时。PS:咱们须要的只是别人项目的这个功能页面的「内容部分」,不须要别人项目的顶部导航和菜单。java
一个比较笨的办法就是直接把别人项目这个页面的代码拷贝过来,可是万一别人不是 vue 开发的,或者说 vue 版本、UI 库等不一样,以及别人的页面加载以前操做(路由拦截,鉴权等)咱们都须要拷贝过来,更重要的问题是,别人代码有更新,咱们如何作到同步更新。node
长远来看,代码拷贝不太可行,问题的根本就是,咱们须要作到让他们的代码运行在他们本身的环境之上,而咱们对他们的页面仅仅是“引用”。这个环境包括各类插件( vue、 vuex 、 vue-router 等),也包括加载前的逻辑(读 cookie,鉴权,路由拦截等)。私有 npm 能够共享组件,可是依然存在技术栈不一样/UI库不一样等问题。react
举个栗的,大家的产品有几百个页面,功能齐全且强大,客户只须要其中的部分页面,并且须要大家提供源码,这时候把全部代码都给出去确定是不可能的,只能挑出来客户须要,这部分代码须要另外制定版本维护,就很浪费。webpack
微前端的诞生也是为了解决以上两个问题:ios
使用微前端的好处:
目前微前端主要有两种解决方案:iframe 方案和 single-spa 方案
iframe 你们都很熟悉,使用简单方便,提供自然的 js/css 隔离,也带来了数据传输的不便,一些数据没法共享(主要是本地存储、全局变量和公共插件),两个项目不一样源(跨域)状况下数据传输须要依赖 postMessage 。
iframe 有不少坑,可是大多都有解决的办法:
iframe 和主页面共享链接池,而浏览器对相同域的链接有限制,因此会影响页面的并行加载,阻塞 onload 事件。每次点击都须要从新加载,虽然能够采用 display:none 来作缓存,可是页面缓存过多会致使电脑卡顿。「(没法解决)」
iframe 必须给一个指定的高度,不然会塌陷。
解决办法:子项目实时计算高度并经过 postMessage 发送给主页面,主页面动态设置 iframe 高度。有些状况会出现多个滚动条,用户体验不佳。
弹窗只能在 iframe 范围内垂直水平居中,无法在整个页面垂直水平居中。
弹窗的全屏,指的是在浏览器可视区全屏。这个全屏指的是占满用户屏幕。
全屏方案,原生方法使用的是 Element.requestFullscreen(),插件:vue-fullscreen。当页面在 iframe 里面时,全屏会报错,且 dom 结构错乱。
iframe 和主页面共用一个浏览历史,iframe 会影响页面的前进后退。大部分时候正常,iframe 屡次重定向则会致使浏览器的前进后退功能没法正常使用。而且 iframe 页面刷新会重置(好比说从列表页跳转到详情页,而后刷新,会返回到列表页),由于浏览器的地址栏没有变化,iframe 的 src 也没有变化。
非同源的 iframe 在火狐及 chorme 都不支持 onerror 事件。
解决办法参考:stackoverflow上的问题:Catch error if iframe src fails to load
spa 单页应用时代,咱们的页面只有 index.html 这一个 html 文件,而且这个文件里面只有一个内容标签 <div id="app"></div>,用来充当其余内容的容器,而其余的内容都是经过 js 生成的。也就是说,咱们只要拿到了子项目的容器 <div id="app"></div> 和生成内容的 js,插入到主项目,就能够呈现出子项目的内容。
<link href=/css/app.c8c4d97c.css rel=stylesheet>
<div id=app></div>
<script src=/js/chunk-vendors.164d8230.js> </script>
<script src=/js/app.6a6f1dda.js> </script>
复制代码
咱们只须要拿到子项目的上面四个标签,插入到主项目的 HTML 中,就能够在父项目中展示出子项目。
这里有个问题,因为子项目的内容标签是动态生成的,其中的 img/video/audio 等资源文件和按需加载的路由页面 js/css 都是相对路径,在子项目的 index.html 里面,能够正确请求,而在主项目的 index.html 里面,则不能。
举个例子,假设咱们主项目的网址是 www.baidu.com ,子项目的网址是 www.taobao.com ,在子项目的 index.html 里面有一张图片 <img src="./logo.jpg">,那么这张图片的完整地址是 www.taobao.com/logo.jpg,如今将这个图片的 img 标签生成到了父项目的 index.html,那么图片请求的地址是 www.baidu.com/logo.jpg,很显然,父项目服务器上并无这张图。
解决思路:
一般作法是动态修改 webpack 打包的 publicPath,而后就能够自动注入前缀给这些资源。
single-spa 是一个微前端框架,基本原理如上,在上述呈现子项目的基础上,还新增了 bootstrap 、 mount 、 unmount 等生命周期。
相对于 iframe,single-spa 让父子项目属于同一个 document,这样作既有好处,也有坏处。好处就是数据/文件均可以共享,公共插件共享,子项目加载就更快了,缺点是带来了 js/css 污染。
single-spa 上手并不简单,也不能开箱即用,开发部署更是须要修改大量的 webpack 配置,对子项目的改造也很是多。
qiankun 是蚂蚁金服开源的一款框架,它是基于 single-spa 的。他在 single-spa 的基础上,实现了开箱即用,除一些必要的修改外,子项目只须要作不多的改动,就能很容易的接入。若是说 single-spa 是自行车的话,qiankun 就是个汽车。
微前端中子项目的入口文件常见的有两种方式:JS entry 和 HTML entry
纯 single-spa 采用的是 JS entry,而 qiankun 既支持 JS entry,又支持 HTML entry。
JS entry 的要求比较苛刻:
(1)将 css 打包到 js 里面
(2)去掉 chunk-vendors.js,
(3)去掉文件名的 hash 值
(4)将 single-spa 模式的入口文件( app.js )放置到 index.html 目录,其余文件不变,缘由是要截取 app.js 的路径做为 publicPath
其实 qiankun 还支持 config entry :
{
entry: {
scripts: [
"app.3249afbe.js"
"chunk-vendors.75fba470.js",
],
styles: [
"app.3249afbe.css"
"chunk.75fba470.css",
],
html: 'http://localhost:5000'
}
}
复制代码
建议使用 HTML entry ,使用起来和 iframe 同样简单,可是用户体验比 iframe 强不少。qiankun 请求到子项目的 index.html 以后,会先用正则匹配到其中的 js/css 相关标签,而后替换掉,它须要本身加载 js 并运行,而后去掉 html/head/body 等标签,剩下的内容原样插入到子项目的容器中 :
使用 qiankun 的好处:
js/css 污染是没法避免的,而且是一个可大可小的问题。就像一颗定时炸弹,不知道何时会出问题,排查也麻烦。做为一个基础框架,解决这两个污染很是重要,不能仅凭“规范”开发。
js 沙箱的原理是子项目加载以前,对 window 对象作一个快照,子项目卸载时恢复这个快照,如图:
那么如何检测 window 对象的变化呢,直接将 window 对象进行一下深拷贝,而后深度对比各个属性显然可行性不高,qiankun框架采用的是ES6新特性,proxy代理方法。具体如何操做的,以前的文章有写(连接在文末),就再也不赘述。
可是 proxy 是不兼容 IE11 的,为了兼容,低版本 IE 采用了 diff 方法:浅拷贝 window 对象,而后对比每个属性。
qiankun 的 css 沙箱的原理是重写
HTMLHeadElement.prototype.appendChild 事件,记录子项目运行时新增的 style/link 标签,卸载子项目时移除这些标签。
single-spa 方案中我用了换肤的思路来解决 css 污染:首先 css-scoped 解决大部分的污染,对于一些全局样式,在子项目给 body/html 加一个惟一的 id/class(正常开发部署用),而后这个全局的样式前面加上这个 id/class,而 single-spa 模式则在 mount 周期为 body/html 加上这个惟一的 id/class,在 unmount 周期去掉,这样就能够保证这个全局 css 只对这个项目生效了。
这两个方案的致命点都在于没法解决多个子项目同时运行时的 css 污染,以及子项目对主项目的 css 污染。
虽说两个项目同时运行常见并不常见,可是若是想实现 keep-alive ,就须要使用 display: none 将子项目隐藏起来,子项目不须要卸载,这时候就会存在两个子项目同时运行,只不过其中一个对用户不可见。
css 沙箱还有个思路就是将子项目的样式局限到子项目的容器范围内生效,这样只须要给不一样的子项目不一样的容器就能够了。可是这样也会有新的问题,子项目中 append 到 body 的弹窗,样式就没法生效。因此说样式污染还须要制定规范才行,约定 class 命名前缀。
在个人前几篇文章(连接在文末)中,single-spa 和 qiankun 的 demo 已经实现了,开发部署流程也都有,接下来就是实践出真知,用在实际项目中,才知道有那些坑。
因为咱们是 vue 技术栈,因此我就以改造一个 vue 项目为例说明,其余的技术栈原理是同样的。
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
复制代码
import './public-path';
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import store from './store';
Vue.use(VueRouter)
Vue.config.productionTip = false
let router = null;
let instance = null;
function render(parent = {}) {
const router = new VueRouter({
// histroy模式的路由须要设置base,app-history-vue根据项目名称来定
base: window.__POWERED_BY_QIANKUN__ ? '/app-history-vue' : '/',
mode: 'history',
// hash模式不须要上面两行
routes: []
})
instance = new Vue({
router,
store,
render: h => h(App),
data(){
return {
parentRouter: parent.router,
parentVuex: parent.store,
}
},
}).$mount('#appVueHistory');
}
//全局变量来判断环境,独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main framework', props);
render(props.data);
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
复制代码
主要改动是引入修改 publicPath 的文件和 export 三个生命周期。
注意:
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',// 把子应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
复制代码
注: 这个 name 默认从 package.json 获取,能够自定义,只要和父项目注册时的 name 保持一致便可。
这个配置主要就两个,一个是容许跨域,另外一个是打包成 umd 格式。为何要打包成 umd 格式呢?是为了让 qiankun 拿到其 export 的生命周期函数。咱们能够看下其打包后的 app.js 就知道了:
root 在浏览器环境就是 window , qiankun 拿这三个生命周期,是根据注册应用时,你给的 name 值,name 不一致则会致使拿不到生命周期函数
资源放 src 目录,会通过 webpack 处理,能统一注入 publicPath。不然在主项目中会404。
参考:vue-cli3的官方文档介绍:什么时候使用-public-文件夹
暴露给运维人员的配置文件 config.js,能够放在 public 目录,由于在 index.html 中 url 为相对连接的 js/css 资源,qiankun 会给其注入前缀。
后续会考虑子项目共享公共插件,这时就须要避免公共插件的污染
// 正确作法:给 axios 实例添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
// 错误用法:直接给 axios 对象添加拦截器
axios.interceptors.request.use(function () {/*...*/});
复制代码
组件内样式的 css-scoped 是必须的。
对于一些插入到 body 的弹窗,没法使用 scoped,请不要直接使用原 class 修改样式,请添加本身的 class,来修改样式。
.el-dialog{
/* 不推荐使用组件原有的class */
}
.my-el-dialog{
/* 推荐使用自定义组件的class */
}
复制代码
在父项目中,这个定位未必准确,应尽可能避免使用,确有相对于浏览器窗口定位需求,能够用 position: sticky,可是会有兼容性问题(IE不支持)。若是定位使用的是 bottom 和 right,则问题不大。
还有个办法,位置能够写成动态绑定 style 的形式:
<div :style="{ top: isisQiankun ? '10px' : '0'}">
复制代码
js 沙箱只劫持了 window.addEventListener,使用
document.body.addEventListener 或者 document.body.onClick 添加的事件并不会被沙箱移除,会对其余的页面产生影响,请在 unmount 周期清除
先检查下子项目的入口文件有没有 export 生命周期函数,再检查下子项目的打包,最后看看请求到的子项目的文件对不对。
检查容器 div 是不是写在了某个路由里面,路由没匹配到全部未加载。若是只在某个路由页面加载子项目,能够在页面的 mounted 周期里面注册子项目并启动。
因为 qiankun 是经过 location.pathname 值来判断当前应该加载哪一个子项目的,因此须要给每一个子项目注入不一样的路由 path,而 hash 模式的项目路由跳转不改变 path,因此无影响,history 模式的项目路由设置 base 属性便可。
若是主项目使用 hash 模式,那么得用 location.hash 值来判断当前应该加载哪一个子项目,而且子项目都得是 hash 模式,还须要给子项目全部的路由都添加一个前缀,子项目的路由跳转若是以前使用的是 path 也须要修改,用 name 跳转则不用。
若是主项目是 hash 模式子项目为 history 模式,那么跳转到子项目以后,没法跳转到另外一个 history 模式的子项目,也没法回到主项目的页面。
vue 项目 hash 模式改 history 模式也很简单:
主项目要想不被子项目的样式污染,子项目是 vue 技术,样式能够写 css-scoped ,若是子项目是 jQuery 技术呢?因此主项目自己的 id/class 须要特殊一点,不能太简单,被子项目匹配到。
产生这个问题的缘由是:在子项目跳转到父项目时,子项目的卸载须要一点点的时间,在这段时间内,父项目加载了,插入了 css,可是被子项目的 css 沙箱记录了,而后被移除了。父项目的事件监听也是同样的,因此须要在子项目卸载完成以后再跳转。我本来想在路由钩子函数里面判断下,子项目是否卸载完成,卸载完成再跳转路由,然而路由不跳转,子项目根本不会卸载。
临时解决办法:先复制一下
HTMLHeadElement.prototype.appendChild 和 window.addEventListener ,路由钩子函数 beforeEach 中判断一下,若是当前路由是子项目,而且去的路由是父项目的,则还原这两个对象.
const childRoute = ['/app-vue-hash','/app-vue-history'];
const isChildRoute = path => childRoute.some(item => path.startsWith(item))
const rawAppendChild = HTMLHeadElement.prototype.appendChild;
const rawAddEventListener = window.addEventListener;
router.beforeEach((to, from, next) => {
// 从子项目跳转到主项目
if(isChildRoute(from.path) && !isChildRoute(to.path)){
HTMLHeadElement.prototype.appendChild = rawAppendChild;
window.addEventListener = rawAddEventListener;
}
next();
});
复制代码
在子项目里面如何跳转到另外一个子项目/主项目页面呢,直接写 或者用
router.push/router.replace 是不行的,缘由是这个 router 是子项目的路由,全部的跳转都会基于子项目的 base 。写 连接能够跳转过去,可是会刷新页面,用户体验很差。
解决办法也比较简单,在子项目注册时将主项目的路由实例对象传过去,子项目挂载到全局,用父项目的这个 router 跳转就能够了。
可是有一丢丢不完美,这样只能经过 js 来跳转,跳转的连接没法使用浏览器自带的右键菜单(如图:Chrome 自带的连接右键菜单)
项目之间的不要有太多的数据依赖,毕竟项目仍是要独立运行的。通讯操做须要判断是否 qiankun 模式,作兼容处理。
经过 props 传递父项目的 Vuex ,若是子项目是 vue 技术栈,则会很好用。假如子项目是 jQuery/react/angular ,就不能很好的监听到数据的变化。
qiakun 提供了一个全局的 GlobalState 来共享数据。主项目初始化以后,子项目能够监听到这个数据的变化,也能提交这个数据。
// 主项目初始化
import { initGlobalState } from 'qiankun';
const actions = initGlobalState(state);
// 主项目项目监听和修改
actions.onGlobalStateChange((state, prev) => {
// state: 变动后的状态; prev 变动前的状态
console.log(state, prev);
});
actions.setGlobalState(state);
// 子项目监听和修改
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变动后的状态; prev 变动前的状态
console.log(state, prev);
});
props.setGlobalState(state);
}
复制代码
vue 项目之间数据传递仍是使用共享父组件的 Vuex 比较方便,与其余技术栈的项目之间的通讯使用 qiankun 提供的 GlobalState 。
若是主项目和子项目都用到了同一个版本的 Vue/Vuex/Vue-Router 等,主项目加载一遍以后,子项目又加载一遍,就很浪费。
要想复用公共依赖,前提条件是子项目必须配置 externals ,这样依赖就不会打包进 chunk-vendors.js ,才能复制已有的公共依赖。
按需引入公共依赖,有两个层面:
webpack 的 externals 是支持大插件的按需引入的:
subtract : {
root: ['math', 'subtract']
}
复制代码
subtract 能够经过全局 math 对象下的属性 subtract 访问(例如 window['math']['subtract'])。
single-spa 是使用 systemJs 加载子项目和公共依赖的,将公共依赖和子项目一块儿配置到 systemJs 的配置文件 importmap.json ,就能够实现公共依赖的按需加载:
{
"imports": {
"appVueHash": "http://localhost:7778/app.js",
"appVueHistory": "http://localhost:7779/app.js",
"single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js",
"echarts": "https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js"
}
}
复制代码
巨无霸应用的公共依赖和公共函数被太多的页面使用,致使升级和改动困难,使用微前端可让各个子项目独立拥有本身的依赖,互不干扰。而咱们想要复用公共依赖,这与微前端的理念是相悖的。
因此个人想法是:父项目提供公共依赖,子项目能够自由选择用或者不用。
这个也很好实现,父项目先加载好依赖,而后在注册子项目时,将 Vue/Vuex/Vue-Router 等经过 props 传过去,子项目能够选择用或者不用。
主项目:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: '/app-vue-hash',
props: { data : { store, router, Vue, Vuex, VueRouter } }
},
]);
start();
复制代码
子项目:
import Vue from 'vue'
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main framework', props);
const { VueRouter, Vuex } = props.data;
Vue.use(VueRouter);
Vue.use(Vuex);
render(props.data);
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
复制代码
这样作不太可行,缘由有两个:
配置 webpack 的 externals 以后,子项目独立运行时,这些依赖的来源「有且仅有」index.html 中的外链 script 标签。
在这个前提下,子项目和主项目的 vue 版本一致的状况下,使用同一份服务器文件。即便没法共享,也是能够作 http 缓存的。
那么 qiankun 可否作到,某个依赖加载了以后,再也不加载,直接复用呢?好比说子项目 A 请求了服务器上的 2.6 版本 vue,切换到子项目 B,B 项目也用了这个 vue 文件,可否再也不次加载,直接复用呢?
实际上是能够的,能够看到 qiankun 将子项目的外链 script 标签,内容请求到以后,会记录到一个全局变量中,下次再次使用,他会先从这个全局变量中取。这样就会实现内容的复用,只要保证两个连接的url一致便可。
const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));
复制代码
因此只要子项目配置了 webpack 的 externals,并在 index.html 中使用外链 script 引入这些公共依赖,只要这些公共依赖在同一台服务器上,即可以实现子项目的公共依赖的按需引入,一个项目使用了以后,另外一个项目使用再也不重复加载,能够直接复用这个文件。
虽然 qiankun 不会重复请求相同 url 的公共依赖,可是这也仅比 http 缓存强了一丢丢。
有缺陷的地方在于:
这些问题可能须要去改动 qiankun 的源码。
子项目的内容标签插到父项目的 index.html 后,其中的资源( img/video/audio 等)路径都是相对的,致使资源没法正确显示。上面我列举了三种解决方案。
通常来讲,jQuery 项目是否通过 webpack 打包的,因此无法经过修改 publicPath 来注入路径前缀。后面两种方法操做起来比较麻烦,或者说咱们应该「优先从框架自己」解决这个问题,而不是其余方法。因此我想了以下三种方案:
html 有一个原生标签 <base>,这个标签只能放在 <head> 里面,它的 href 属性是一个 url 值。 mdn 地址: base 文档根 URL 元素
设置了 标签以后,页面上全部的连接和 url 都基于它的 href。例如页面访问地址是 https://www.taobao.com ,设置 以后,页面中本来的图的实际请求地址会变成
https://www.baidu.com/img/jQuery1.png ,页面上的 连接:,点击以后,页面会跳转到:
https://www.baidu.com/about
能够看到,<base> 标签和 webpack 的 publicPath 有同样的效果,那么可否在 jQuery 项目加载以前,把 jQuery 项目的地址赋给 <base> 标签,而后插入到 <head> ?这样就能够解决 jQuery 项目的资源加载问题。
作法也很简单,在 qiankun 提供的 beforeLoad 生命周期,判断当前是不是 jQuery 项目:
beforeLoad: app => {
if(app.name === 'purehtml'){
const baseTag = document.createElement('base');
baseTag.setAttribute('href',app.entry);
console.log(baseTag);
document.head.appendChild(baseTag);
}
},
beforeUnmount: app => {
if(app.name === 'purehtml'){
const baseTag = document.head.querySelector('base');
document.head.removeChild(baseTag);
}
}
复制代码
这样作子项目资源能够正确加载,可是 <base> 标签的威力太强大了,会致使全部的路由没法正常跳转,跳转到其余的子项目时,<a> 连接是基于 <base> 的,会跳转到 jQuery 子项目的不存在的路由。解决了一个 bug ,又出现了新的 bug ,这样是不行的。因此这个方案可行性特别小。
这个方案分两步:
前面咱们说到,对于子项目是 HTML entry 的,qiankun 拿到入口文件 index.html 以后,会用正则匹配到 <body> 标签及其内容,<head> 中的 link/style/script/meta 等标签,而后插入到父项目的容器中。
咱们能够传递一个 getTemplate 函数,将图片的相对路径转为绝对路径,它会在处理模板时使用:
start({
getTemplate(tpl,...rest) {
// 为了直接看到效果,因此写死了,实际中须要用正则匹配
return tpl.replace('<img src="./img/jQuery1.png">', '<img src="http://localhost:3333/img/jQuery1.png">');
}
});
复制代码
对于动态插入的标签,劫持其插入 DOM 的函数,注入前缀。
假如子项目动态插入一张图:
const render = $ => {
$('#purehtml-container').html('<p>Hello, render with jQuery</p><img src="./img/jQuery2.png">');
return Promise.resolve();
};
复制代码
主项目劫持 jQuery 的 html 方法:
beforeMount: app => {
if(app.name === 'purehtml'){
// jQuery 的 html 方法是一个挺复杂的函数,这里只是为了看效果,简写了
$.prototype.html = function(value){
const str = value.replace('<img src="/img/jQuery2.png">', '<img src="http://localhost:3333/img/jQuery2.png">')
this[0].innerHTML = str;
}
}
}
复制代码
固然了,还有个简单粗暴的写法,给 jQuery 项目的图片路径写成绝对路径,可是不建议这么作,换个服务器部署就不能用了。
这个方案的可行性不高,都是陈年老项目了,不必这样折腾。
qiankun 自己就对接入 jQuery 多页应用比较乏力,通常使用场景就是,一个大项目只接入某个/某几个页面,这样的话使用方案二比较合理。
虽然 qiankun 支持 jQuery 老项目,可是彷佛对「多页应用」没有很好的解决办法。每一个页面都去修改,成本很大也很麻烦,可是使用 iframe 嵌入这些老项目就比较方便。
qiankun 将每一个子项目的 js/css 文件内容都记录在一个全局变量中,若是子项目过多,或者文件体积很大,可能会致使内存占用过多,致使页面卡顿。
另外,qiankun 运行子项目的 js,并非经过 script 标签插入的,而是经过 eval 函数实现的,eval 函数的安全和性能是有一些争议的:MDN的eval介绍
{
"scripts": {
"install:hash": "cd app-vue-hash && npm install",
"install:history": "cd app-vue-history && npm install",
"install:main": "cd main && npm install",
"install:purehtml": "cd purehtml && npm install",
"install-all": "npm-run-all install:*",
"start:hash": "cd app-vue-hash && npm run serve ",
"start:history": "cd app-vue-history && npm run serve",
"start:main": "cd main && npm run serve",
"start:purehtml": "cd purehtml && npm run serve",
"start-all": "npm-run-all --parallel start:*",
"serve-all": "npm-run-all --parallel start:*",
"build:hash": "cd app-vue-hash && npm run build",
"build:history": "cd app-vue-history && npm run build",
"build:main": "cd main && npm run build",
"build-all": "npm-run-all --parallel build:*"
}
}
复制代码
其中 --parallel 参数表示并行,没有这个参数则是等上一个命令执行完才会执行下一个命令。
不要对 iframe 抱有偏见,它也是微前端的一种实现方式,若是页面上无弹窗、无全屏等操做,iframe 也是很好用的。配置缓存和 cdn 加速,若是是内网访问,也不会很慢。
iframe 和 qiankun 能够并存,jQuery 多页应用使用 iframe 接入就挺好,何时什么场景该用哪一种方案,具体状况具体分析。
最后,文章有什么问题或错误欢迎指出,谢谢!