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

前言

相信不少人看了標題都以爲一臉懵逼,我就不賣關子用人話解釋一下吧:
就是在某些狀況(需求)下的服務端渲染應用須要獲取客戶端的某些參數(window.innerWidth之類的)使服務端首屏渲染可以響應適合的內容(根據window.innerWidth作響應式網站),因此我把這種作法稱做自適應服務端渲染(RSSR)(本身編的)。javascript

這種需求仍是有的,好比rem佈局,須要客戶端加載js獲取當前窗口寬度來設置根元素的font-size,對於我來講,之前開發都是客戶端渲染的單頁應用,這種狀況天然不會遇到。但如今用服務端渲染的項目,把首屏渲染的任務放到了服務器上,又須要拿到客戶端的window.innerWidth來自適應響應首屏界面,這也反映出了服務端渲染的一個大大的缺點。html

自適應服務端渲染方案

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

Service Worker 是瀏覽器在後臺獨立於網頁運行的腳本,它打開了通向不須要網頁或用戶交互的功能的大門。 如今,它們已包括如推送通知和後臺同步等功能。 未來,Service Worker 將會支持如按期同步或地理圍欄等其餘功能。 本文提到的是關於攔截和處理網絡請求的特性。web

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接收到。post

而後編寫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的新姿式呢。

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

相關文章
相關標籤/搜索