什么是前端微服务,网上大把的介绍,我就不啰嗦了,简单来说,就是把各个子项目整合到一起。
《前端架构:从入门到微前端》 这本书中介绍,微前端架构一般可以由以下几种方式进行:
其中比较常见的就是 iframe
和 single-spa
,这两者各有千秋。
页面加载问题: 影响主页面加载,阻塞 onload
事件,本身加载也很慢,页面缓存过多会导致电脑卡顿。 (无法解决)
布局问题: iframe
必须给一个指定的高度,否则会塌陷。解决办法:子系统实时计算高度并通过 postMessage
发送给主页面,主页面动态设置高度,修改子系统或者代理插入脚本。有些情况会出现多个滚动条,用户体验不佳。
弹窗及遮罩层问题:只能在 iframe
范围内垂直水平居中,没法在整个页面垂直水平居中。
iframe
浏览器前进/后退问题: iframe
和主页面共用一个浏览历史, iframe
会影响页面的前进后退,大部分时候正常, iframe
多次重定向则会导致浏览器的前进后退功能无法正常使用,不是全部页面都会出现,基本可以忽略。但是 iframe
页面刷新会重置(比如说从列表页跳转到详情页,然后刷新,会返回到列表页),因为浏览器的地址栏没有变化。
iframe
的页面跳转到其他页面出问题,比如两个 iframe
之间相互跳转,直接跳转会只在 iframe
范围内跳转,所以必须通过主页面来进行跳转。不过 iframe
跳转的情况很少
系统之间的通讯需要通过 postMessage
,存在一定的安全性
css
和 js
,避免了各个系统之间的样式和 js
污染 css
和 js
需要制定规范,进行隔离。否则容易造成全局污染,尤其是 vue
的全局组件,全局钩子。 document http
对比项 | single-spa | iframe | 补充 |
---|---|---|---|
加载速度 | single-spa可以将所有系统共用的vue/vuex/vue-router等文件提取出来,只加载一次,各系统复用,加载速度很快,但是必须保证文件版本统一 | iframe会占用主系统的http通道,影响主系统的加载,加载速度很慢 | 两者都可以通过http缓存提高一定的加载速度,但是对于vue这些通用文件没法做cdn,因为内部系统很可能无法访问外网 |
兼容性 | single-spa只适用于vue、react、angular编写的系统,对一些jq写的老系统无能为力 | iframe则可以嵌入任何页面 | |
技术难度 | single-spa需要一定的技术储备,有一些学习成本 | iframe门槛则很低,无需额外学习 | |
局限性 | single-spa可以嵌入任何部件 | iframe只能嵌入页面,当然了也可以把一个部件单独写成一个页面 | |
改造成本 | single-spa一定要对子系统进行改造,但是改造的内容并不多很多,半小时即可完成 | iframe可以不对原系统进行改造,但是必须借助代理服务器进行插入脚本和css,增加了代理服务器也增加了系统的不稳定性(两台服务器中的任何一台挂掉都会导致系统不可用),服务器也需要成本。如对原系统进行改造,则工作量和single-spa相当 | 项目的源文件丢失或者其他一些无法改动源文件的情况,只能使用iframe |
补充:
SEO
, iframe
无法解决,但是 single-spa
有办法解决(谷歌能支持单页应用的 SEO
,百度则需要 SSR
),但是内部系统,SEO的需求比较少。 iframe
存在安全隐患,两个 iframe
页面互相引用则会导致无限嵌套 bug
,会导致页面卡死,目前只能通过代理服务器检查 iframe
页面内容来处理 iframe很简单,一个标签就实现了。single-spa比较陌生,我会详细介绍。
以 vue
为例, vue-cli4
生成的项目打包生成的 index.html
文件内容如下(精简了一些无关的内容):
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>my-app</title> <link href=/js/about.6b1cbb89.js rel=prefetch> <link href=/css/app.c8c4d97c.css rel=preload as=style> <link href=/js/app.6a6f1dda.js rel=preload as=script> <link href=/js/chunk-vendors.164d8230.js rel=preload as=script> <link href=/css/app.c8c4d97c.css rel=stylesheet> </head> <body> <noscript> <strong>We're sorry but my-app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id=app></div> <script src=/js/chunk-vendors.164d8230.js> </script> <script src=/js/app.6a6f1dda.js> </script> </body> </html> 复制代码
其中最核心的部分是:
<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> 复制代码
猜想:能否借助 node
服务器,将子系统的 index.html
获取到,然后读取 HTML
,获取到这几个标签,返回给主系统,主系统直接插入到 body
中,能否呈现出子系统?
node
代理代码实现,操作 DOM
使用的是 cheerio
插件:
const http = require('http') // 引入cheerio模块 const cheerio = require('cheerio') const axios = require('axios') const server = http.createServer(function (request, response) { //请求子系统服务器,获取到index.html文件 axios.get('http://localhost/').then(res => { response.writeHead(200, { 'Content-Type': 'application/xml' , 'Access-Control-Allow-Origin': '*' }) // 加载HTML字符串 const $ = cheerio.load(res.data) $('link').each(function () { $(this).attr('href', 'http://localhost' + $(this).attr('href')) }) $('script').each(function () { $(this).attr('src', 'http://localhost' + $(this).attr('src')) }) const resp = $('body').prepend($('link[rel=stylesheet]')).html(); response.end(resp) }).catch(e => { console.log(e) }) }) server.listen(8080) 复制代码
需要注意的是:
js/css
文件路径都是相对路径,需要拼上子项目的前缀。 v-html
插入的 DOM
片段,外链 script
不会生效,需要手动插入
结果是主系统中 #app
里面能渲染出子系统,但是 #app
里面动态生成的 HTML
, img/video/audio
等文件的路径是相对的,所以会请求到主系统上,但是这些文件并不在主系统,所以会404,同样,按需加载的路由页面对应的 js/css
文件也是相对路径,会请求出错。 如果路由没按需加载,则不存在这个问题
结论:可以实现微服务效果,但是需要解决文件相对路径的问题, index.html
里面的 link/script
还可以手动加上,但是动态生成的 html
里面的 img/video/audio
等,以及按需加载路由页面对应的 js/css
无法通过代理服务器解决。
解决思路:
这里面的 js/css/img/video
等都是相对路径,能否通过 webpack
打包,将这些路径全部打包成绝对路径?这样就可以解决文件请求失败的问题。
能否像CDN一样,一个服务器挂了,会去其他服务器上请求对应文件。或者说服务器之间的文件共享,主系统上的文件请求失败会自动去子服务器上找到并返回。
能否手动(或借助 node
)将子系统的文件全部拷贝到主项目服务器上, node
监听子系统文件有更新,就自动拷贝过来,并且按 js/css/img
文件夹合并
查阅 webpack
和 vue-cli3
官网后发现:
默认情况下, Vue CLI
会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/
。如果应用被部署在一个子路径上 https://www.my-app.com/my-app/
,而你使用的是history模式的路由,对于url: https://www.my-app.com/my-app/page1
,vue无法区分 my-app
是真实路径,而 page1
是路由参数,这个时候需要设置 publicPath
为 /my-app/
,vue才能正确的请求文件资源和匹配路由。
这里可以将 vue-cli3
的 publicPath
设置为 https://www.my-app.com/my-app/
,然后代码里面的 js/css/img/video
路径都会变成绝对路径,前缀是 https://www.my-app.com/my-app/
,这样就解决了 url
路径的问题。
这样就可以实现一个简单的 single-spa
应用,但是加载好的 Vue
子系统不会在切换到下一个系统的时候卸载掉,子系统过多则会导致卡顿,并且 css/js
污染的可能性增加,实用性不大。
文章有什么疑问or错误,欢迎评论。下一篇预告:从0实现一个 single-spa
项目,包含完整的打包/开发调试流程,老项目如何改造等。