自適應服務端渲染(服務端根據客戶端環境自適應地響應首屏)

YJNldm發表於2019-04-22

前言

相信很多人看了標題都覺得一臉懵逼,我就不賣關子用人話解釋一下吧:
就是在某些情況(需求)下的服務端渲染應用需要獲取客戶端的某些引數(window.innerWidth之類的)使服務端首屏渲染能夠響應適合的內容(根據window.innerWidth做響應式網站),所以我把這種做法稱作自適應服務端渲染(RSSR)(自己編的)。

這種需求還是有的,比如rem佈局,需要客戶端載入js獲取當前視窗寬度來設定根元素的font-size,對於我來說,以前開發都是客戶端渲染的單頁應用,這種情況自然不會遇到。但現在用服務端渲染的專案,把首屏渲染的任務放到了伺服器上,又需要拿到客戶端的window.innerWidth來自適應響應首屏介面,這也反映出了服務端渲染的一個大大的缺點。

自適應服務端渲染方案

對於客戶端首屏請求傳參的方式我第一個想到的就是Service Worker,我們先大致瞭解一下Service Worker的作用和令人振奮的特性吧:

Service Worker 是瀏覽器在後臺獨立於網頁執行的指令碼,它開啟了通向不需要網頁或使用者互動的功能的大門。 現在,它們已包括如推送通知和後臺同步等功能。 將來,Service Worker 將會支援如定期同步或地理圍欄等其他功能。 本文提到的是關於攔截和處理網路請求的特性。

Service Worker 相關特性:

  • 它是一種 JavaScript Worker,無法直接訪問 DOM。 Service Worker 通過響應 postMessage 介面傳送的訊息來與其控制的頁面通訊,頁面可在必要時對 DOM 執行操作。
  • Service Worker 是一種可程式設計網路代理,讓您能夠控制頁面所傳送網路請求的處理方式。

瞭解更詳細內容可訪問 developers.google.com/web/fundame…

通過在瀏覽器開出的service worker便可攔截首屏請求帶上相關headers 再fetch,這樣伺服器就可以知道客戶端的引數做相應了。

註冊Service Worker

我們先在首屏文件中註冊service worker:

<script>
    if ('serviceWorker' in navigator) {
      // 帶上需要的客戶端引數
      navigator.serviceWorker.register('/service-worker.js?window-width=' + window.innerWidth);
    }
</script>
複製程式碼

由於service-worker.js裡的this上下文(ServiceWorkerGlobalScope)沒有相關window物件或詳細的客戶端內容,所以只能從註冊時帶上query引數,service-workder執行緒才能取到客戶端引數。

也可以在register後postMessage讓service-worker接收到。

然後編寫service-worker.js:

// 解析service-worker地址的引數
const query = (function () {
  var search = location.search
  if (search.indexOf('?') == 0) {
    search = search.substring(1);
  }
  return search
    ? search.replace('?', '').split('&').reduce(
      (o, v) => {
        const exec = v.split('=');
        o[exec[0]] = exec[1];
        return o;
      },
      {},
    )
    : {};
})()

// 取得window-width
const windowW = query[’window-width’];

this.addEventListener('fetch', function(event)  {
  const first_path = event.request.url.split('/')[3]
  // 判斷是根路徑即代表當前請求的是首屏文件
  if (first_path === '') {
    var new_req = new Request(url, {
      // 自定義header
      headers:   {
        'window-width': windowW
      }
    });
    // 攔截改造header後發起請求
    return fetch(new_req).then(function() {
      ...
    })
  }
})

複製程式碼

接下來只要在服務端拿到這個header就可以自適應服務端渲染啦!

缺陷

使用Service Worker這個方案有個致命的缺陷,它不是實時的,瀏覽器開出的Service Worker是一個獨立執行緒,不能完全阻塞主執行緒攔截或者不能擋在主執行緒之前就開始攔截控制頁面,所以每次都會等主線層解析首屏文件的navigator.serviceWorker.register, 才輪到service worker執行緒進行install、activate操作之後,才能用客戶端引數攔截請求,於是就會造成永遠晚一步的局面(當前重新整理後看到的是上一次的內容)。

這不是Service Worker的設計缺陷,只是Service Worker本意並不是對這種需求而已。

但這是個很好的思路,說不定以後瀏覽器會提供這種主執行緒攔截器之類的呢,興許還能發現更多Service Worker的新姿勢呢。

永遠保持一顆探索的心,才能迸發新鮮靈感

相關文章