<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>JavaScript on Colinx Blog</title><link>/tags/javascript/</link><description>Recent content in JavaScript on Colinx Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 06 May 2022 00:00:00 +0000</lastBuildDate><atom:link href="/tags/javascript/index.xml" rel="self" type="application/rss+xml"/><item><title>前端静态资源加载的一些优化</title><link>/posts/%E5%89%8D%E7%AB%AF%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BC%98%E5%8C%96/</link><pubDate>Fri, 06 May 2022 00:00:00 +0000</pubDate><guid>/posts/%E5%89%8D%E7%AB%AF%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BC%98%E5%8C%96/</guid><description>
&lt;!-- # 前端静态资源加载的一些优化 -->
&lt;p>最近在折腾优化博客，由于全站都是部署在 Netlify、Vercel 的 CDN 上，都是海外节点，国内访问延迟高，在想办法优化下访问速度和体验。线路部分的优化可以看另一篇文章&lt;a href="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/">国外静态网站托管服务商国内速度对比及线路优化&lt;/a>。这篇文章来探讨下前端 CSS 和 JS 资源的加载。&lt;/p>
&lt;p>优化目标主要就两个：&lt;/p>
&lt;ul>
&lt;li>尽可能快，但是不希望我引入的辅助性第三方库影响到页面体验，不要阻塞主要内容渲染&lt;/li>
&lt;li>为了快第三方静态资源肯定是上 CDN，但是要有容灾，CDN 挂了要能 fallback 到其他 url&lt;/li>
&lt;/ul>
&lt;h2 id="静态资源异步加载">
静态资源异步加载
&lt;a href="#%e9%9d%99%e6%80%81%e8%b5%84%e6%ba%90%e5%bc%82%e6%ad%a5%e5%8a%a0%e8%bd%bd" class="h-anchor" aria-hidden="true">#&lt;/a>
&lt;/h2>
&lt;p>&lt;code>prefetch&lt;/code>、&lt;code>preload&lt;/code>这些预加载技术暂且不谈，博客站点没那么多资源。这里主要是用&lt;code>async&lt;/code>和&lt;code>defer&lt;/code>来控制脚本异步加载，以避免阻塞 DOM 解析和页面渲染。&lt;/p>
&lt;p>使用&lt;code>async&lt;/code>异步加载，对于&lt;a href="https://github.com/vinta/pangu.js/">pangu&lt;/a>这种会影响到内容呈现的使用&lt;code>async&lt;/code>，对于统计类第三方库使用&lt;code>defer&lt;/code>延迟加载，到 DOM 加载完再执行&lt;/p>
&lt;p>有些第三方库需要手动 init，这样就不能使用&lt;code>defer&lt;/code>、&lt;code>async&lt;/code>关键字加载资源了，&lt;strong>&lt;code>defer&lt;/code>和&lt;code>async&lt;/code>关键字只适用于远程资源&lt;/strong>，本以为把远程资源的&lt;code>&amp;lt;script&amp;gt;&lt;/code>和内嵌在 HTML 里用来 init 的&lt;code>&amp;lt;script&amp;gt;&lt;/code>都加上&lt;code>defer&lt;/code>就可以利用它顺序加载的特性解决这个问题，结果发现不是这样的。。。。异步打乱脚本执行顺序，init 脚本不等待资源下载完毕会先执行。利用好&lt;code>onload&lt;/code>事件可以解决这个问题。&lt;/p>
&lt;p>before&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-html" data-lang="html">&amp;lt;&lt;span style="color:#f92672">script&lt;/span> &lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js&amp;#34;&lt;/span>&amp;gt;&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&amp;lt;&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">spacingElementByClassName&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;post&amp;#39;&lt;/span>);
&lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">spacingElementByTagName&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;p&amp;#39;&lt;/span>);
document.&lt;span style="color:#a6e22e">addEventListener&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;DOMContentLoaded&amp;#39;&lt;/span>, () =&amp;gt; {
&lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">autoSpacingPage&lt;/span>();
});
&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/code>&lt;/pre>&lt;/div>&lt;p>after&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-html" data-lang="html">&amp;lt;&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;span style="color:#66d9ef">function&lt;/span> &lt;span style="color:#a6e22e">panguSpaing&lt;/span>() {
&lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">spacingElementByClassName&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;post&amp;#39;&lt;/span>);
&lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">spacingElementByTagName&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;p&amp;#39;&lt;/span>);
document.&lt;span style="color:#a6e22e">addEventListener&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;DOMContentLoaded&amp;#39;&lt;/span>, () =&amp;gt; {
&lt;span style="color:#75715e">// listen to any DOM change and automatically perform spacing via MutationObserver()
&lt;/span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">pangu&lt;/span>.&lt;span style="color:#a6e22e">autoSpacingPage&lt;/span>();
});
}
&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&amp;lt;&lt;span style="color:#f92672">script&lt;/span> &lt;span style="color:#a6e22e">async&lt;/span> &lt;span style="color:#a6e22e">onload&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;panguSpaing()&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js&amp;#34;&lt;/span>&amp;gt;&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/code>&lt;/pre>&lt;/div>&lt;p>除了&lt;code>onload&lt;/code>还有&lt;code>onerror&lt;/code>，还可以利用&lt;code>onerror&lt;/code>事件在资源加载失败时 fallback 到其他 CDN，详见下文&lt;/p>
&lt;p>这里跑了个测试看下优化前后的差距&lt;/p>
&lt;p>&lt;strong>优化前&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117479.png" alt="image-20220506193639029">&lt;/p>
&lt;p>&lt;img src="https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117127.png" alt="image-20220506193800266">&lt;/p>
&lt;p>First Contentful Pain Time 在 1.5s，整个页面到 1.5s 才能可见，DOM 加载总时间达到了 2.5s&lt;/p>
&lt;p>本来我都已经把首页不需要加载的三方库都拿走了，但是还剩下一个，这个用于前端性能监控的 js 阻塞了渲染，挂的是腾讯云的 CDN，国内快到飞起，国外就卡到不行（作为对比上图里从 jsdelivr 加载的 js 也是快到飞起，墙内就慢得一批）&lt;/p>
&lt;p>&lt;strong>优化后&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062117498.png" alt="image-20220506193652188">&lt;/p>
&lt;p>优化后页面没有再阻塞，0.8s 页面就可用了，总 DOM 加载时间只有 1.5s，相较之前简直直接起飞。&lt;/p>
&lt;p>&lt;img src="https://blog-1301127393.file.myqcloud.com/BlogImgs/202205071426038.png" alt="cn">&lt;/p>
&lt;p>国内某检测平台的结果也显示优化效果明显&lt;/p>
&lt;h2 id="静态资源-fallback-加载">
静态资源 Fallback 加载
&lt;a href="#%e9%9d%99%e6%80%81%e8%b5%84%e6%ba%90-fallback-%e5%8a%a0%e8%bd%bd" class="h-anchor" aria-hidden="true">#&lt;/a>
&lt;/h2>
&lt;p>国内好用的静态资源 CDN 还真没有，新浪那种只提供那么几个热门的库完全不符合需求，BootCDN 炸了不是一次两次，字节 CDN 的资源 URL 有够 ugly，URL 里面还带节点信息多半后面会出问题。360 奇舞 CDN 之前也换过域名、备案出问题，感觉每一个能省心用。七牛的目前看上去还行，也不知道后面会不会出什么幺蛾子。。。为了稳妥起见 + 照顾全球的访客，还是优先使用的 cloudflare 和 jsdelivr 的 CDN&lt;/p>
&lt;p>网站主要三方库都是通过 jsdelivr 和 cloudflare 引入的，但是毕竟是海外的节点不知道哪一天就被墙了，所以还要为墙内的访客准备一个 fallback 方案。这里利用到&lt;code>script&lt;/code>标签的&lt;code>onerror&lt;/code>事件&lt;/p>
&lt;p>现代浏览器都支持&lt;code>script&lt;/code>和&lt;code>link&lt;/code>标签上的&lt;code>onload&lt;/code>和&lt;code>onerror&lt;/code>事件，详细支持情况可以查看这里&lt;/p>
&lt;p>&lt;a href="https://pie.gd/test/script-link-events/">https://pie.gd/test/script-link-events/&lt;/a>&lt;/p>
&lt;p>关于&lt;code>onerror&lt;/code>，中文互联网上有好几篇文章说他会捕获&lt;code>script&lt;/code>标签里的代码的执行错误。。。简直离谱。稍微查下 MDN 之类的文档就可以发现这玩意本来就是只捕获资源加载错误的&lt;/p>
&lt;blockquote>
&lt;p>The onerror event is triggered if an error occurs while loading an external file (e.g. a document or an image).&lt;/p>
&lt;p>&lt;a href="https://www.w3schools.com/jsref/event_onerror.asp">https://www.w3schools.com/jsref/event_onerror.asp&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>利用好这个特性，就可以实现在资源加载失败时 fallback 从另一个 url 加载资源。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-html" data-lang="html">&amp;lt;&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;span style="color:#66d9ef">function&lt;/span> &lt;span style="color:#a6e22e">fallbackJSloader&lt;/span>(&lt;span style="color:#a6e22e">url&lt;/span>, &lt;span style="color:#a6e22e">loadedEvent&lt;/span>) {
&lt;span style="color:#a6e22e">console&lt;/span>.&lt;span style="color:#a6e22e">log&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;Error loading asset, using fallback url&amp;#39;&lt;/span>);
&lt;span style="color:#a6e22e">console&lt;/span>.&lt;span style="color:#a6e22e">log&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;load asset from&amp;#39;&lt;/span>, &lt;span style="color:#a6e22e">url&lt;/span>);
&lt;span style="color:#66d9ef">let&lt;/span> &lt;span style="color:#a6e22e">script&lt;/span> &lt;span style="color:#f92672">=&lt;/span> document.&lt;span style="color:#a6e22e">createElement&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;script&amp;#34;&lt;/span>);
&lt;span style="color:#a6e22e">script&lt;/span>.&lt;span style="color:#a6e22e">type&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;text/javascript&amp;#34;&lt;/span>;
&lt;span style="color:#a6e22e">script&lt;/span>.&lt;span style="color:#a6e22e">src&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">url&lt;/span>;
&lt;span style="color:#a6e22e">script&lt;/span>.&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>;
&lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">loadedEvent&lt;/span>)
&lt;span style="color:#a6e22e">script&lt;/span>.&lt;span style="color:#a6e22e">setAttribute&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;onload&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">`&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#a6e22e">loadedEvent&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">()`&lt;/span>)
document.&lt;span style="color:#a6e22e">body&lt;/span>.&lt;span style="color:#a6e22e">appendChild&lt;/span>(&lt;span style="color:#a6e22e">script&lt;/span>);
}
&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&amp;lt;&lt;span style="color:#f92672">script&lt;/span> &lt;span style="color:#a6e22e">defer&lt;/span> &lt;span style="color:#a6e22e">onload&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;init_gitalk()&amp;#34;&lt;/span>
&lt;span style="color:#a6e22e">onerror&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;fallbackJSloader(&amp;#39;https://cdn.staticfile.org/gitalk/1.7.2/gitalk.min.js&amp;#39;,&amp;#39;init_gitalk&amp;#39;)&amp;#34;&lt;/span>
&lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js&amp;#34;&lt;/span>&amp;gt;
&lt;/code>&lt;/pre>&lt;/div>&lt;p>CSS 的 fallback 加载同理，此处不再赘述&lt;/p>
&lt;p>优化后如图，在开发者工具中手动 block 资源网站，可以自动 fallback 从另一个 url 加载资源，这下网站的可用性又提升了一点点🤏哈哈哈&lt;/p>
&lt;p>&lt;img src="https://blog-1301127393.file.myqcloud.com/BlogImgs/202205062133393.png" alt="66230832-3581-42DF-9F2F-B817E88DAF4D">&lt;/p>
&lt;h2 id="扩展阅读">
扩展阅读
&lt;a href="#%e6%89%a9%e5%b1%95%e9%98%85%e8%af%bb" class="h-anchor" aria-hidden="true">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.w3cplus.com/performance/remove-block-rendering.html">消除阻塞页面渲染的资源&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://segmentfault.com/q/1010000000640869">defer 和 async 的区别&lt;/a>&lt;/li>
&lt;li>&lt;a href="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">动态加载 css&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>网站 profile&lt;/strong>工具&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.webpagetest.org">https://www.webpagetest.org&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>