模块预加载:提升前端性能的关键技术
更新:该Web功能现已在三大主流浏览器引擎中全面可用。自2023年9月18日起,已成为基线提供的新功能。
模块化开发的优势与挑战
基于模块的开发方式在缓存优化方面具有显著优势,能够有效减少需要传输给用户的字节数。通过提高代码的细粒度,开发人员可以为应用程序中的关键代码分配优先级,从而优化加载流程。
然而,模块依赖关系也带来了加载时序的问题。浏览器必须等待模块加载完成后,才能发现其依赖关系。解决这一问题的方法之一是对依赖项进行预加载,让浏览器提前了解所有文件,并保持连接处于活跃状态。
预加载的工作原理
<link rel="preload"> 为浏览器提供了一种在需要资源之前就声明性请求资源的方式。
这种方法特别适用于隐藏在CSS文件中的资源,如字体文件。在这种情况下,浏览器需要等待多个往返周期后才会意识到需要获取较大的字体文件。如果能够提前开始下载,就能最大化利用连接带宽。
<link rel="preload"> 及其等效的HTTP头部提供了一种简单的声明性方法,能够立即通知浏览器当前导航所需的关键文件。浏览器检测到预加载指令后,会启动资源的高优先级下载,确保在真正需要时资源已经全部或部分获取完成。
为什么预加载不适用于模块?
认证模式匹配问题
资源存在多种认证模式,要获得缓存命中,这些模式必须匹配。如果不匹配,资源将被重复获取。重复获取显然是不可取的,既浪费用户带宽,又无故增加了延迟时间。
对于 <script> 和 <link> 标签,可以通过 crossorigin 属性设置认证模式。但是,没有 crossorigin 属性的 <script type="module"> 表示认证模式为 omit,而这种模式在 <link rel="preload"> 中并不存在。这意味着需要将 <script type="module"> 和 <link rel="preload"> 的 crossorigin 属性都改为其他值。如果要预加载的内容是其他模块的依赖项,这可能难以轻易修改。
解析与编译差异
文件获取只是实际执行代码的第一步。首先,浏览器需要对其进行解析和编译。理想情况下,应该在模块需要之前就完成这些处理。
然而,V8(Chrome的JavaScript引擎)对模块的解析和编译方式与其他JavaScript不同。由于 <link rel="preload"> 无法指示加载的文件是模块,浏览器只能加载文件并将其存入缓存。当脚本通过 <script type="module"> 标签加载时(或被其他模块加载时),浏览器才会将其解析并编译为JavaScript模块。
<link rel="modulepreload> 是模块专用的预加载吗?
简单来说,是的。为模块预加载使用特定的 link 类型,可以让开发人员编写简单的HTML而无需担心使用的认证模式,默认设置就能正常工作。
浏览器能够识别预加载目标是模块,因此在获取完成后立即开始解析和编译,而不必等待执行尝试。
浏览器兼容性
| 浏览器 | 版本 |
|---|---|
| Chrome | 66 |
| Edge | ≤79 |
| Firefox | 115 |
| Safari | 17 |
数据来源:Can I use
模块依赖关系如何处理?
感谢您的提问。实际上,本文并未讨论递归预加载。
在实际情况中,<link rel="modulepreload"> 规范不仅允许预加载请求的模块,还可以加载其完整的依赖关系树。浏览器不必执行此操作,但可以选择执行。
最佳实践:模块预加载策略
由于运行应用程序需要完整的依赖关系树,那么预加载模块及其依赖树的最佳跨浏览器解决方案是什么?
递归预加载依赖关系的浏览器需要进行健壮的模块去重处理。因此,一般的最佳实践是声明模块及其依赖项的扁平列表,并信任浏览器不会重复获取相同的模块。
预加载能提升性能吗?
预加载通过告知浏览器需要获取的内容,避免了在长时间往返过程中处于空闲状态,从而最大化带宽利用率。如果您正在测试模块,并且由于依赖树较深而遇到性能问题,创建预加载的扁平列表可能会很有效。
然而,模块性能优化仍在持续开发中。建议使用开发者工具详细检查应用程序的行为,同时考虑将应用程序拆分为多个代码块进行打包。值得注意的是,Chrome中关于模块的工作仍在积极推进中,预计打包工具在未来将能够获得更好的优化效果。
正在加载评论...