我的烏拉拉練功坊

請來參觀移植到 Google Firebase 的成績 https://oolala.xyz/ken73chen/

2017年9月21日

這兒的 PageSpeed

各位看官,96 分!這種事情當然要拿出來炫耀的呀!

因為這兒持續被我改來改去的,所以不會一直維持在 96 分。
其實會被 PageSpeed 挑出來的問題,都不算太難解決,最麻煩的就是這兩個了:
  1. 優先處理要顯示的內容
  2. 清除前幾行內容中的禁止轉譯 JavaScript 和 CSS
PageSpeed 的標準不難理解,要以最快的速度,將網頁內容顯示在使用者的可視範圍內,可視範圍就是瀏覽器不用捲動的那個區域,所以:
  1. CSS 只要足夠讓網頁正確顯示在可視範圍就夠了
  2. 為了讓內容盡快出現,所有的外部 CSS 和 JavaScript 檔案,都要用非同步的方式載入,或者等待網頁內容出現後再載入
提醒你,如果 web server 是自己的,一定要先從 web server 開始先改善 (例如 expires、reverse proxy……),再來才是改善網頁。

首先 

先把 Google 給的說明、建議、資料等,都先看一看。

優先處理要顯示的內容 

說起來很簡單,只要把現有的 CSS 中,和可視範圍中有關的挑出來就可以了,但是如果有用 BootstrapW3.CSS 這類東西的,不就哭哭了!

其實你只要到 https://www.sitelocity.com/critical-path-css-generator,把你的網址給他,大約數十秒,他就幫你挑好,電腦不只會挑花生,也會挑 CSS 喔!

記得喔!你的 CSS 必須是在 HTML 裡面載入的,這樣網站才看得懂。

網站有詳細的說明,從「為什麼」開始詳細說明,就照他的說明,我是建議直接塞到 head 裡面,免得你的問題變成俄羅斯娃娃。

當然,sitelocity 挑出來的,是基於目前的網站內容,以後網站內容有變動,這裡當然也要修改;如果只是改改顏色之類,不會變動到排版 (包括大小),就沒有關係。

清除前幾行內容中的禁止轉譯 JavaScript 和 CSS

請先閱讀關鍵轉譯路徑這篇文章,主要是在講 CSS 和 JavaScript 對於瀏覽器顯示網頁內容效率的影響。

轉譯就是 rendering,比較常見到的翻譯是「渲染」,常常看到跟 GPU 相關的文章一起出現;這兒講的是,瀏覽器把 HTML、CSS、font…等一堆東西搓成一團,終於顯示出網頁的這段過程。

非同步載入 JavaScript 外部檔案

這個問題應該不大,如果你還有這個問題的話,建議你直接導入 RequireJS,雖然 RequireJS 功能非常多,不過你只要使用非同步載入 JavaScript 的功能就好。

非同步載入 JavaScript 當然一點也不稀奇,不過非同步載入的檔案一多,載入完成的先後順序,和彼此的相依性就很麻煩,這個時候,就用 RequireJS 處裡就好,雖然原先的 code 得作些修改。

但是即使你不用 RequireJS,還是得修改 code 去解決先後順序、相依性的問題,不如一鼓作氣,就用 RequireJS 吧!

非同步載入 CSS 外部檔案

一般的載入方式就是同步的:
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css" media="all">,這個會阻擋瀏覽器其他工作,因為要等檔案載入、解析……。

反正就是要用非同步的方式載入CSS 檔案,以下是我的方式:
  1. 先 <head> 內,所有載入外部 CSS 檔案的 <link>,搬到 <body> 的底下
  2. 將 <link ...> 改成 <async-css data-href='xxx.css'></async-css>,除了改 tag 以外,原先的 href 改成 data-href,其他都拿掉
  3. 延伸閱讀:MDN 的 Custom Element
  4. 將原先用 <link ...> 載入的 CSS 檔案,改用 JavaScript 先推到一個 [] 去
  5. 接下來, 那些 CSS 外部檔案,用 JavaScript,在 window 的 load 事件後再載入
Google 一下「async css」,就可以找到很多說明、作法,以下是我的作法;首先,在 HTML 上頭:

_blogger.loadCss = function(){_blogger.loadCss.queue.push(arguments);}; _blogger.loadCss.queue = [];
_blogger 是我用來當全域變數的,接下來,將要在 load 事件載入的 css,改成以下這個樣子:
<script>_blogger.loadCss('https://www.w3schools.com/w3css/4/w3.css','https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');</script>
最後,在 load 事件載入 CSS 就好了。
(function() { var loadCssFunc = function() { var ss, href, i; for (i = 0; i < arguments.length; i++) { href = arguments[i]; if (_blogger.hostname && !href.match(/^(data:|http|\/\/)/i)) { href = _blogger.hostname + href; } console.time(href+"_load"); ss = window.document.createElement('link'); ss.rel = 'stylesheet'; ss.media = 'none'; ss.href = href; ss.id = 'async_'+(Date.now()+Math.floor(Math.random()*1e13)).toString(32); ss.onload = function() { this.media = 'all'; console.timeEnd(this.href+"_load"); this.onload = null; }; document.getElementsByTagName('body')[0].appendChild(ss); } }; var loadCssOnLoad = function(){ document.getElementsByTagName('body')[0].dataset.dataLoaded = '1'; var i, j; for (i = 0; i < _blogger.loadCss.queue.length; i++) { for(j = 0; j < _blogger.loadCss.queue[i].length; j++) { loadCssFunc(_blogger.loadCss.queue[i][j]); } _blogger.loadCss.queue[i] = null; } _blogger.loadCss = loadCssFunc; delete _blogger.loadCss.queue; }; if (document.getElementsByTagName('body')[0].dataset.dataLoaded === '1') { loadCssOnLoad.apply(); } else { window.addEventListener('load',loadCssOnLoad,{once:true}); } }());
可以的話就直接放在 HTML 裡面,別再讓瀏覽器花功夫載入;程式很簡單,就是產生一個新的 <link ... />,放在 <body> 的最後面,重點是 media='none';因為這是個沒用的 CSS 外部檔案,所以瀏覽器就不會停下來等,也就不會造成「阻擋」,接著用一個 onload 的事件,當檔案載入後,圖窮匕見,再把 media 改成 all (或 screen 也可以),這時瀏覽器才會發現被騙了。

目前是沒有收到過瀏覽器的抱怨。

window.onload 事件之後,一樣可以用 _blogger.loadCss() 載入 CSS 檔案;之後用 AJAX 更新的內容,要注意裡面有沒有要載入新的 CSS。

原本打算用 Custom Elements,除了看起來比較炫以外,CSS 本來就用 HTML tag 載入的,那麼一樣用 HTML tag,感覺上就很合理;不過 IE 的問題太難解決,有興趣的話可以看 asyncLoad_customElements.js 這個檔案。

大 CSS 檔案  vs. 小 CSS 檔案

假設載入了 10 個 CSS 檔案,就可能造成瀏覽器重新排版網頁 10 次 (延伸閱讀),這時候, PageSpeed 會說個往返路徑之類的原因,然後就扣分。

PageSpeed 的要求是「畢其功於一役」,在使用者看到網頁前,往返抓 CSS 檔案的次數要越少越好。

這年頭應該都用 less 或 scss 來對付 css 了,所以即使你愛用小檔案,還是可以保留一大堆 .less,然後用 less 的 @import (注意,是 less 的,不是 css 的!),產生一個 (或兩個…反正越少越好) 大 CSS 檔案。

至於別人家的 CSS,我的想法是,就用已經放在 CDN 上的,通常也早在瀏覽器的 cache 中,除非是要 build 自己的版本。

Hosting JavaScript 和 CSS 檔案

因為 Blogger 並不讓人放 JavaScript 或者 CSS 檔案,所以我的是放在 UpDogForge;這兒有一篇文章 Static Web Hosting: Who’s Best? 值得一讀。

UpDog

UpDog 非常簡單,直接用 Dropbox 或 Google 帳號登入、連上自己的 Dropbox 帳號,就好了,UpDog 的網站也是非常簡潔,功能大概就是登入和登出。

這個最大的好處,就是你的 site root 直接連到你的 Dropbox 指定目錄,所以存檔後,等檔案自己同步到 Dropbox,就 ok 了;此外,沒有頻寬限制 (因為苦主是 Dropbox)、有 https、一個 site 是免費的、有個免費網址 https://ken73chen.updog.co、免費 site 會被塞一個小小的 updog.co 的 logo、只能放靜態檔案。

速度不快比較麻煩,traceroute 看是還要跑回美國。

Forge

為了達到虛榮的 96 分,所以搭著 Forge 一起用。

使用 Forge,當然是為了亞馬遜的 CDN,而且當然在台灣有節點,否則大家連都很快,只有自己連的慢,這樣不是很淒涼嗎!

Forge 的免費帳號,能有一個 site、一個月 1GB 頻寬、有版本控制、可以和 Dropbox (或  GitHub) 同步,又是 Dropbox,而且可以指定和 UpDog 同一個目錄,真是太感人了。

不像 UpDog,只要將檔案更新到 Dropbox 就完工,Forge  還要 deployment 的動作,如果 enable Auto-deployment,根據說明,檔案更新到 Dropbox 後,三十秒就自動 deployment 到 Forge。

我建議不要使用 Auto-deployment,先在 UpDog 好好測試,一個階段再 deploy 到 Forge,這樣就可以好好使用 Forge 的版本控制功能,而且還能知道每次更新或新增的檔案。

此外,每次 deployment 後,不同的版本會有不同的 CDN 路徑,Forge 會幫你修改 HTML 的內容;不過會改變的其實也只有一個數字,Forge 會放到 HTML 內 <meta name='forge-tag' value='forge-token:XXXXX'/>

Forge 的管理界面,比 UpDog 複雜一千倍,不過還是不困難,摸一摸就熟悉了,有提供個 Web Hook 網址, 如果你要 deploy,又懶的登入 Forge,只要連網址,就會自動
deployment;如果你又懶的登入檢查是否 deployment 完工,就可以設定個 Outgoing Hook,這樣 Forge 就可以主動通知你 deployment  的結果。

最後

我是有特別的原因,才在 Blogger 上面作這些事情,誠心建議您,不需要在 Blogger上花這些時間!