Files
Colinx-Blog/content/posts/前端静态资源加载的一些优化.md
github-actions[bot] 9d467ca37b Change img CDN URL
2022-05-08 02:25:06 +00:00

154 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 前端静态资源加载的一些优化
date: 2022-05-06
description: 最近在折腾优化博客由于全站都是部署在Netlify、Vercel的CDN上都是海外节点国内访问延迟高在想办法优化下访问速度和体验。这篇文章来探讨下前端CSS和JS资源的加载。优化目标主要就两个1. 尽可能快,但是不希望我引入的辅助性第三方库影响到页面体验,不要阻塞主要内容渲染. 2. 为了快第三方静态资源肯定是上CDN但是要有容灾CDN挂了要能fallback到其他url
categories:
- 技术
tags:
- 技术
- 前端
- CDN
- JavaScript
---
<!-- # 前端静态资源加载的一些优化 -->
最近在折腾优化博客由于全站都是部署在Netlify、Vercel的CDN上都是海外节点国内访问延迟高在想办法优化下访问速度和体验。线路部分的优化可以看另一篇文章[国外静态网站托管服务商国内速度对比及线路优化](https://blog.colinx.one/posts/%E5%9B%BD%E5%A4%96%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99%E6%89%98%E7%AE%A1%E5%9C%A8%E5%9B%BD%E5%86%85%E9%80%9F%E5%BA%A6%E5%AF%B9%E6%AF%94%E5%8F%8A%E7%BA%BF%E8%B7%AF%E4%BC%98%E5%8C%96/)。这篇文章来探讨下前端CSS和JS资源的加载。
优化目标主要就两个:
* 尽可能快,但是不希望我引入的辅助性第三方库影响到页面体验,不要阻塞主要内容渲染
* 为了快第三方静态资源肯定是上CDN但是要有容灾CDN挂了要能fallback到其他url
## 静态资源异步加载
`prefetch``preload`这些预加载技术暂且不谈,博客站点没那么多资源。这里主要是用`async``defer`来控制脚本异步加载以避免阻塞DOM解析和页面渲染。
使用`async`异步加载,对于[pangu](https://github.com/vinta/pangu.js/)这种会影响到内容呈现的使用`async`,对于统计类第三方库使用`defer`延迟加载到DOM加载完再执行
有些第三方库需要手动init这样就不能使用`defer``async`关键字加载资源了,**`defer``async`关键字只适用于远程资源**,本以为把远程资源的`<script>`和内嵌在HTML里用来init的`<script>`都加上`defer`就可以利用它顺序加载的特性解决这个问题结果发现不是这样的。。。。异步打乱脚本执行顺序init脚本不等待资源下载完毕会先执行。利用好`onload`事件可以解决这个问题。
before
```html
<script src="https://cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js"></script>
<script>
pangu.spacingElementByClassName('post');
pangu.spacingElementByTagName('p');
document.addEventListener('DOMContentLoaded', () => {
pangu.autoSpacingPage();
});
</script>
```
after
```html
<script>
function panguSpaing() {
pangu.spacingElementByClassName('post');
pangu.spacingElementByTagName('p');
document.addEventListener('DOMContentLoaded', () => {
// listen to any DOM change and automatically perform spacing via MutationObserver()
pangu.autoSpacingPage();
});
}
</script>
<script async onload="panguSpaing()" src="https://cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js"></script>
```
除了`onload`还有`onerror`,还可以利用`onerror`事件在资源加载失败时fallback到其他CDN详见下文
这里跑了个测试看下优化前后的差距
**优化前**
![image-20220506193639029](https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117479.png)
![image-20220506193800266](https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117127.png)
First Contentful Pain Time在1.5s整个页面到1.5s才能可见DOM加载总时间达到了2.5s
本来我都已经把首页不需要加载的三方库都拿走了但是还剩下一个这个用于前端性能监控的js阻塞了渲染挂的是腾讯云的CDN国内快到飞起国外就卡到不行作为对比上图里从jsdelivr加载的js也是快到飞起墙内就慢得一批
**优化后**
![image-20220506193652188](https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117498.png)
优化后页面没有再阻塞0.8s页面就可用了总DOM加载时间只有1.5s,相较之前简直直接起飞。
![cn](https://blog-1301127393.file.myqcloud.com/BlogImgs/202205071426038.png)
国内某检测平台的结果也显示优化效果明显
## 静态资源Fallback加载
国内好用的静态资源CDN还真没有新浪那种只提供那么几个热门的库完全不符合需求BootCDN炸了不是一次两次字节CDN的资源URL有够uglyURL里面还带节点信息多半后面会出问题。360奇舞CDN之前也换过域名、备案出问题感觉每一个能省心用。七牛的目前看上去还行也不知道后面会不会出什么幺蛾子。。。为了稳妥起见+照顾全球的访客还是优先使用的cloudflare和jsdelivr的CDN
网站主要三方库都是通过jsdelivr和cloudflare引入的但是毕竟是海外的节点不知道哪一天就被墙了所以还要为墙内的访客准备一个fallback方案。这里利用到`script`标签的`onerror`事件
现代浏览器都支持`script``link`标签上的`onload``onerror`事件,详细支持情况可以查看这里
[https://pie.gd/test/script-link-events/](https://pie.gd/test/script-link-events/)
关于`onerror`,中文互联网上有好几篇文章说他会捕获`script`标签里的代码的执行错误。。。简直离谱。稍微查下MDN之类的文档就可以发现这玩意本来就是只捕获资源加载错误的
> The onerror event is triggered if an error occurs while loading an external file (e.g. a document or an image).
>
> https://www.w3schools.com/jsref/event_onerror.asp
利用好这个特性就可以实现在资源加载失败时fallback从另一个url加载资源。
```html
<script>
function fallbackJSloader(url, loadedEvent) {
console.log('Error loading asset, using fallback url');
console.log('load asset from', url);
let script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.async = true;
if (loadedEvent)
script.setAttribute('onload', `${loadedEvent}()`)
document.body.appendChild(script);
}
</script>
<script defer onload="init_gitalk()"
onerror="fallbackJSloader('https://cdn.staticfile.org/gitalk/1.7.2/gitalk.min.js','init_gitalk')"
src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js">
```
CSS的fallback加载同理此处不再赘述
优化后如图在开发者工具中手动block资源网站可以自动fallback从另一个url加载资源这下网站的可用性又提升了一点点🤏哈哈哈
![66230832-3581-42DF-9F2F-B817E88DAF4D](https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062133393.png)
## 扩展阅读
* [消除阻塞页面渲染的资源](https://www.w3cplus.com/performance/remove-block-rendering.html)
* [defer 和 async 的区别](https://segmentfault.com/q/1010000000640869)
* [动态加载 css](http://lengyun.github.io/js/3-2-2dynamicAddCSS.html#%E4%B8%8E%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BDjs%E7%9A%84%E5%8C%BA%E5%88%AB)
**网站profile**工具
* [https://www.webpagetest.org](https://www.webpagetest.org)