引
💡 Ajax 的出现,带来了 jQuery 时代;Node技术的发展,带来了前端工程化进阶;如果说前面二者是带来技术的革命,那么前端路由方案的多样化则带来了用户体验的升级以及项目管理的优化。
课程简介
《前端简史》系列技术分享课程通过讲述前端演变的发展历史,介绍前端重要技术的实现原理与使用方法,带你走进前端,感受前端的“前世今生”,结交前端历史上举足轻重的几位“大佬”,例如:Ajax、Node、Webpack、SPA、PWA、RN、Flutter等等。
该系列课程目前一共分为三节:《前端简史之裂变:Ajax变法》、《 前端简史之纵横:Node东出》、《前端简史之崛起:Router迁鼎》。以及不一定会有的《前端简史之天下:跨端跨平台》。
通过这门课程你将获得什么?
- 宏观上,你将了解前端发展演变的过程,清楚前端岗位的定义与定位,有助于自己做好在前端领域上的职业规划。
- 微观上,你将学到Ajax、Node、前端路由等一系列前端重要技术背后的原理与使用方法,有助于自己在实际项目开发过程中应用更加得心应手。
课程目录
一、前端路由与后端路由
1、什么是前端路由
https://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#anchor
URL组成部分:
协议、域名、端口、路径、参数、锚点。
协议(scheme)是浏览器请求服务器资源的方法,上例是https://的部分,表示使用 HTTPS 协议。
互联网支持多种协议,必须指明网址使用哪一种协议,默认是 HTTP 协议。
lh://LHLogin/presentToLogInViewController?statusBarIsVisible=1
lh://Webview/openNormal?url=...
域名(host)是资源所在的网站名或服务器的名字,上例的域名是www.example.com。
端口(port)是一个整数,可以简单理解成,访问者告诉服务器,想要访问哪一个网站。同一个域名下面可能同时包含多个网站,它们之间通过端口区分。
默认端口是80,如果省略了这个参数,服务器就会返回80端口的网站。
路径(path)是资源在网站的位置。比如,/path/index.html这个路径,指向网站的/path子目录下面的网页文件index.html。
路径可能只包含目录,不包含文件名,比如/foo/,甚至结尾的斜杠都可以省略,这时服务器通常会默认跳转到该目录里面的index.html文件(即等同于请求/foo/index.html),但也可能有其他的处理(比如列出目录里面的所有文件),这取决于服务器的设置。
参数(parameter)是提供给服务器的额外信息。参数的位置是在路径后面,两者之间使用?分隔,上例是?key1=value1&key2=value2。
查询参数可以有一组或多组。每组参数都是键值对(key-value pair)的形式,同时具有键名(key)和键值(value),它们之间使用等号(=)连接。比如,key1=value就是一个键值对,key1是键名,value1是键值。
多组参数之间使用&连接,比如key1=value1&key2=value2。
锚点(anchor)是网页内部的定位点,使用#加上锚点名称,放在网址的最后,比如#anchor。
浏览器加载页面以后,会自动滚动到锚点所在的位置。锚点名称通过网页元素的id属性命名。
注意:这玩意儿是重点角色!用的好可以玩出各种花样,例如:简单点可以实现一键置顶效果,复杂点可以实现一个SPA单页应用。算是早期前端路由的核心技术担当。
2、什么是后端路由
路由这个概念最早是出现在后端的,因为早期的网页都是服务端渲染的,比如:JSP,PHP,ASP等语言,都是直接返回渲染好的html给客户端显示。
在web开发早期的年代里,前端的功能远不如现在这么强大,一直是后端路由占据主导地位。无论是jsp,还是php、asp,用户能通过URL访问到的页面,大多是通过后端路由匹配之后再返回给浏览器的。浏览器在地址栏中切换不同的URL时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件返回给前端,并且每次切换页面时,浏览器都会刷新页面。
在后端,路由映射表中就是不同的URL地址与不同的 html + css + 后端数据 之间的映射。
以下是前后端未分离时期的一个请求示意图(后端路由):
二、前端路由模式有哪些
前端路由与后端路由区别的本质:前后端是否分离。
1、什么是前后端分离?
从开发体验层面上理解,可以是在不同项目上进行开发的分离。
从项目部署层面上理解,可以是在不同服务器上进行部署的分离。
前后端分离之后,前端必然需要自己承担路由映射的职责。
2、前端如何自己实现路由映射?
前端路由主要是有两种模式:hash模式、history模式。
hash原理:监听 hashchange
事件,并对 window.location.hash
进行解析处理。
history原理:监听 popstate
事件,并对事件状态值 event.state
进行处理。利用 history.pushState
和 history.replaceState
对浏览器历史记录进行更改。
需要注意的是,调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。
三、你应该知道的SPA、MPA、CSR、SSR
网络应用程序(WebApp) 从 传统的后端SSR 到 主流的前端CSR 再到 新型的前端SSR 最后又冒出了各种前端的XSR,这些渲染模式为我们开发一个web应用提供了各种便捷的同时也带来了选择困难症。为此,我们更应该清楚地去认识一下这些XSR的原理与利弊,为技术选型做好充分准备。
1、SPA:Single-Page Application 单页面应用
SPA应用所需的资源,如 HTML、CSS 和 JS 等,在一次请求中就加载完成,也就是不需刷新地动态加载。对于 SPA 来说,页面的切换就是组件或视图之间的切换。
简单来说,SPA应用程序只有一个html文件,在vue中可以通过vue-router来局部切换组件,而非刷新整个页面,来实现无刷新切换页面的技术。
2、MPA:Multi-Page Application 多页面应用
MPA应用是有多个独立页面的应用(多个html页面),每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。
3、SPA VS MPA
4、CSR:Client Side Rendering 客户端渲染
页面的渲染其实就是浏览器将HTML文本转化为页面帧的过程。而如今我们大部分WEB应用都是使用 JavaScript 框架(Vue、React、Angular)进行页面渲染的,也就是说,在执行 JavaScript 脚本的时候,HTML页面已经开始解析并且构建DOM树了,JavaScript 脚本只是动态的改变 DOM 树的结构,使得页面成为希望成为的样子,这种渲染方式叫动态渲染,也可以叫客户端渲染(client side rende)。
前端渲染把渲染的任务交给了浏览器,通过客户端的算力来解决页面的构建,这个很大程度上缓解了服务端的压力。而且配合前端路由,无缝的页面切换体验自然是对用户友好的。不过带来的坏处就是对SEO不友好。
一个传统的Vue.js应用程序是在浏览器(或客户端)中呈现的。然后,Vue.js在浏览器下载后生成HTML元素,并解析包含创建当前界面指令的所有JavaScript代码。图示如下:
5、SSR:Server Side Rendering 服务端渲染
顾名思义,服务端渲染就是在浏览器请求页面URL时,服务端直接将我们需要的HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过JavaScript脚本的执行,可直接构建出完整的DOM树并展示页面中。这个服务端组装的过程,叫做服务端渲染。
SSR优点
- 呈现速度和用户体验佳:SSR 对比 CSR,少了很多页面到达浏览器之后的解析、资源加载、逻辑代码执行的过程,用户拿到响应内容后,这份内容基本已经是可以呈现的页面,首屏时间大大缩短;
- SEO 友好:SSR 服务对于站点访问请求响应的是填充过的页面,其中已经有许多站点信息和数据可供爬虫直接识别,搜索引擎优化自不必说;
SSR缺点
- 引入成本高:SSR 方案重新将视图渲染的工作交给了服务器做,这就引入了新的概念和技术栈(如 Node),并且带来了更高的服务器硬件成本和运维成本;
- 响应时间长:对比 CSR 只需要响应早已准备好的空页面,SSR 在完成访问响应的时候需要做更多的计算和生成工作,因此其请求响应时间更长,同时还受限于前置数据接口的响应速度,一项关键指标 TTFB (Time To First Byte) 将变得更大;
- 首屏交互不佳:又是那句话,“SSR 的用户启动体验好,但不完全好”。虽然 SSR 可以让页面请求响应后更快在浏览器上渲染出来,但在首帧出现,需要客户端加载激活的逻辑代码(如事件绑定)还没有初始化完毕的时候,其实是不可交互的状态,同样影响用户体验;
- 传统开发思路受限:斟酌之下还是将其列出作为 SSR 的局限性,既然主要页面内容是在服务端完成渲染的,那么对于浏览器(或者 Hybrid、Webview 之下的宿主)环境的获知和相关操作就会受到局限,一些操作不得不延迟到客户端激活之后才得以进行,这也是导致上一个局限点的原因。
Nuxt2采用基于SSR技术的通用渲染(Universal Rendering),通用渲染允许Nuxt应用程序提供快速的页面加载时间,同时保留客户端呈现的好处。此外,由于内容已经存在于HTML文档中,爬行器可以在没有开销的情况下对其进行索引。图示如下:
6、其他XXR
前端研发中有许多常见场景,根据不同的构建、渲染过程有不同的优劣势和适用情况。如现代 UI 库加持下常用的 CSR、具有更好 SEO 效果的 SSR (SPR)、转换思路主打构建时生成的 SSG、大架构视野之上的 ISR、DPR,还有更少听到的 NSR、ESR 等等。
SPR:Serverless Pre-Rendering 无服务预渲染
这是 Serverless 话题之下的一项渲染技术。SPR 是指在 SSR 架构下通过预渲染与缓存能力,将部分页面转化为静态页面,以避免其在服务器接收到请求的时候频繁被渲染的能力,同时一些框架还支持设置静态资源过期时间,以确保这部分“静态页面”也能有一定的即时性。
SSG:Static Site Generation 静态站点生成。
在构建的时候直接把结果页面输出html到磁盘,每次访问直接把html返回给客户端,相当于一个静态资源。
- 减轻服务器压力,可以把生成的静态资源(html)放到CDN上,合理利用缓存。
- 有利于SEO,由于html已经提前生成好,不需要服务端和客户端去渲染。
- 只适用于静态数据,对于经常改动的数据,需要每次重新生成页面。
ESR:Edge-Side Rendering 边缘渲染
Edge 就是边缘,类比前面的各种 XSR,ESR 就是将渲染工作交给边缘服务器节点,常见的就是 CDN 的边缘节点。这个方案主打的是边缘节点相比核心服务器与用户的距离优势,利用了 CDN 分级缓存的概念,渲染和内容填充也可以是分级进行并缓存下来的。
ESR 之下静态内容与动态内容是分流的,边缘 CDN 节点可以将静态页面内容先响应给用户,然后再自己发起动态内容请求,得到核心服务器响应之后再返回给用户,是在大型网络架构下非常极致的一种优化,但这也就依赖更庞大的技术基建体系了。
在Nuxt3中就引入了ESR技术!Nux2中执行的通用渲染提供了良好的用户和开发人员体验。然而,Nuxt3通过引入混合渲染和边缘渲染,将通用渲染又向前推进了一步。
ISR:Incremental Site Rendering 增量式的网站渲染
- 关键性的页面(如网站首页、热点数据等)预渲染为静态页面,缓存至 CDN,保证最佳的访问性能。
- 非关键性的页面(如流量很少的老旧内容)先响应fallback 内容,然后浏览器渲染(CSR)为实际数据;同时对页面进行异步预渲染,之后缓存至 CDN,提升后续用户访问的性能。
DPR:Distributed Persistent Rendering 分布式的持续渲染
- 去除了 fallback 行为,而是直接用 On-demand Builder(按需构建器)来响应未经过预渲染的页面,然后将结果缓存至 CDN。
- 数据页面过期时,不再响应过期的缓存页面,而是 CDN 回源到 Builder 上,渲染出最新的数据。
- 每次发布新版本时,自动清除 CDN 的缓存数据。
7、如何选择?
特性关注点
- 是否关注 SEO?
-
- 是:需要 Pre-Render,纯 CSR 不可取。
- 否:无限制。
- 是否具有丰富可交互性、需要用户能力、差异化渲染?
-
- 是:CSR 较方便、SSR 加载快。
- 否:Pre-Render 系列保证加载体验。
- 页面结构 & 路由是否复杂、数据更新是否频繁、是否依赖实时数据接口?
-
- 是:首先排除 SSG、如果内容不能拆解,ISR、DPR 也不便接入。
- 否:无限制。
依赖关注点
- 是否接受引入服务器运维成本?
-
- 是:无限制,SSR 可冲。
- 否:失去 SSR 选项。
- 旧有实现中是否有浏览器依赖如 UI 框架内对 DOM、BOM,或 Hybrid 场景下的 JSBridge 的使用?
-
- 是:SSR、SSG 受限。
- 否:无限制。
四、微前端的本质是前端路由
尽管 Web 应用无论是路由还是渲染都有了足够丰富的技术框架支持,但却没有一种新的架构模式来解决现有的困境,并同时兼顾 DX(developer experience)和 UX(user experience)。
SPA:虽然带来丝滑的客户体验,但是面对逐渐庞大的项目时开发体验简直就是面对一座巨石。
MPA:虽然开发体验得到了改善,却降低了用户体验。
传统的分而治之的策略已经无法应对现代 Web 应用的复杂性,因此衍生出了微前端这样一种新的架构模式,与后端微服务相同,它同样是延续了分而治之的设计模式,不过却以全新的方法来实现。
1、什么是微前端
微前端提供了一种技术:可以将多个独立的Web应用聚合到一起,提供统一的访问入口。一个微前端应用给用户的感观就是一个完整的应用,但是在技术角度上是由一个个独立的应用组合通过某种方式组合而成的。
微前端的架构示意图:
微前端的应用场景demo:
上图是一个微前端的demo,主应用中有导航栏,footer组件以及左边的侧边栏组件,而右面是子应用部分,这里的子应用并没有集成在主应用中,只是通过微前端的框架内嵌到主应用中,可是给用户的感受就是一个完整的项目。
2、微前端的历史发展
微前端,早已是一个老生常谈的概念,它于 2016 年首次出现在 ThoughtWorks Technology Radar 上,将后端微服务的概念扩展到了前端世界。
同样的,面对越来越重的前端应用,可将微服务的思想照搬到前端,就有了微前端的概念。像微服务一样,一个前端应用,也可以按照一定的规则,拆分为不同的子应用,独立开发,独立部署,然后聚合成一个完整的应用面对客户。
2018年: 第一个微前端工具single-spa在github上开源。
2019年: 基于single-spa的qiankun问世。
2020年:Module Federation(webpack5)把项目中模块分为本地模块和远程模块,远程模块不属于当前构建,在运行时从所谓的容器加载。加载远程模块是异步操作。当使用远程模块时,这些异步操作将被放置在远程模块和入口之间的下一个chunk的加载操作中,从而实现微前端的构建。
3、微前端的特点
1)独立运行
微前端架构与框架无关,每个微应用都可以使用不同的框架。
2)独立开发
3)独立部署
4、微前端的技术方案
微前端常用技术方案:
- 路由分发式微前端
- iframe
- single-spa
- qiankun
- micro-app
- EMP
- Web Component
- 无界
4.1 路由分发式微前端
路由分发式微前端,即通过路由将不同的业务分发到不同的独立前端应用上。最常用的方案是通过 HTTP 服务的反向代理来实现。
下面是一个基于路由分发的 Nginx 配置:
http {server {listen 80;server_name xxx.xxx.com;location /api/ {proxy_pass http://localhost:3001/api;}location /web/admin {proxy_pass http://localhost:3002/api;}location / {proxy_pass /;}
}
}
优点:
- 实现简单;
- 不需要对现有应用进行改造;
- 完全技术栈无关;
缺点:
- 用户体验不好,每次切换应用时,浏览器都需要重新加载页面;
- 多个子应用无法并存;
- 局限性比较大;
- 子应用之间的通信比较困难;
- 子应用切换时需要重新登录;
4.2 iframe
iframe 作为一项非常古老的技术,也可以用于实现微前端。通过 iframe,我们可以很方便的将一个应用嵌入到另一个应用中,而且两个应用之间的 css 和 javascript 是相互隔离的,不会互相干扰。
优点:
- 实现简单;
- css 和 js 天然隔离,互不干扰;
- 完全技术栈无关;
- 多个子应用可以并存;
- 不需要对现有应用进行改造;
缺点:
- 用户体验不好,每次切换应用时,浏览器需要重新加载页面;
- UI 不同步,DOM 结构不共享;
- 全局上下文完全隔离,内存变量不共享,子应用之间通信、数据同步过程比较复杂;
- 对 SEO 不友好;
- 子应用切换时可能需要重新登录,体验不好;
4.3 single-spa
路由转发模式、iframe 模式尽管可以实现微前端,但是体验不好。我们每次切换回已经访问过的子应用时,都需要重新加载子应用,对性能有很大的影响。
在 single-spa 方案中,应用被分为两类:基座应用和子应用。其中,子应用就是文章上面描述的需要聚合的子应用;而基座应用,是另外的一个单独的应用,用于聚合子应用。
和单页应用的实现原理类似,single-spa 会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面。
优点:
- 切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
- 完全技术栈无关;
- 多个子应用可并存;
- 生态丰富;
缺点:
- 需要对原有应用进行改造,应用要兼容接入 sing-spa 和独立使用;
- 有额外的学习成本;
- 使用复杂,关于子应用加载、应用隔离、子应用通信等问题,需要框架使用者自己实现;
- 子应用间相同资源重复加载;
- 启动应用时,要先启动基座应用;
4.4 qiankun
qiankun 方案是基于 single-spa 的微前端方案。
优点:
- html entry 的方式引入子应用,相比 js entry 极大的降低了应用改造的成本;
- 完备的沙箱方案,js 沙箱做了 SnapshotSandbox、LegacySandbox、ProxySandbox 三套渐进增强方案,css 沙箱做了 strictStyleIsolation、experimentalStyleIsolation 两套适用不同场景的方案;
- 做了静态资源预加载能力;
缺点:
- 适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
- css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
- 无法同时激活多个子应用,也不支持子应用保活;
- 无法支持 vite 等 esmodule 脚本运行;
4.5 micro-app 方案
micro-app 是基于 webcomponent + qiankun sandbox 的微前端方案。
优点:
- 使用 webcomponet 加载子应用相比 single-spa 这种注册监听方案更加优雅;
- 复用经过大量项目验证过 qiankun 的沙箱机制也使得框架更加可靠;
- 组件式的 api 更加符合使用习惯,支持子应用保活;
- 降低子应用改造的成本,提供静态资源预加载能力;
缺点:
- 接入成本较 qiankun 有所降低,但是路由依然存在依赖(虚拟路由已解决);
- 多应用激活后无法保持各子应用的路由状态,刷新后全部丢失(虚拟路由已解决);
- css 沙箱依然无法绝对的隔离,js 沙箱做全局变量查找缓存,性能有所优化;
- 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
- 对于不支持 webcompnent 的浏览器没有做降级处理;
4.6 EMP 方案
EMP 方案是基于 webpack5 新增的 Module Federation (模块联邦) 的微前端方案。
优点:
- webpack 联邦编译可以保证所有子应用依赖解耦;
- 应用间去中心化的调用、共享模块;
- 模块远程 ts 支持;
缺点:
- 对 webpack 强依赖,老旧项目不友好;
- 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
- 子应用保活、多应用激活无法实现;
- 主、子应用的路由可能发生冲突;
4.7 Web Component
基于 Web Component 的 Shadow Dom 能力,我们也可以实现微前端,将多个子应用聚合起来。
Shadow Dom 的用法如下:
const shadow = document.querySelector('#hostElement').attachShadow({mode: 'open'});
// url 为应用的地址,基于 fetch,我们可以获取到应用的 html 模板,添加到指定节点下
fetch(url).then(res => {shadow.innerHTML = res
});
优点:
- 实现简单;
- css 和 js 天然隔离,互不干扰;
- 完全技术栈无关;
- 多个子应用可以并存;
- 不需要对现有应用进行改造;
缺点:
- 主要是浏览器兼容性问题;
- 开发成本较高;
4.8 无界方案
无界微前端方案基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。
5、微前端意义
- 技术架构上进一步的扩展性(模块边界清晰、依赖明确)
- 团队组织上的自治权
- 开发流程上能独立开发、独立交付
最大的意义在于解锁了多技术栈并存的能力,尤其适用于渐进式重构中架构升级过渡期
五、参考资料
路由 | Vue.js
不同的历史模式 | Vue Router
URL 简介 - 网址的组成部分 - 《阮一峰 HTML 语言教程》 - 书栈网 · BookStack
https://juejin.cn/post/7023932512363610120
https://juejin.cn/post/7018876571658223623
https://juejin.cn/post/7207973807775531063
https://www.1024nav.com/blog/front-render
Rendering Modes · Nuxt Concepts
History:pushState() 方法 - Web API | MDN
WindowEventHandlers.onpopstate - Web API | MDN
https://juejin.cn/post/6955341801381167112
https://juejin.cn/post/7208057259053006906
微前端究竟是什么?微前端核心技术揭秘! - 知乎
将微前端做到极致-无界微前端方案 - 知乎
Micro Frontends