這兒的 PageSpeed

這兒的 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上花這些時間!