《深入淺出Webpack》章節試讀& 送書活動

blank

《深入淺出Webpack》章節試讀& 送書活動

今天給大家帶來新書《深入淺出Webpack》試讀章節。作者@吳浩麟,一線前端工程師,曾就職於騰訊,現就職於美團。專注於Web開發,參與過眾多大型Web項目的構建、設計和開發,喜歡探索Web前沿技術。也是Golang和音視頻技術的愛好者,活躍於GitHub,ID為gwuhaolin。

blank


在文末有一個「成為外刊君作者!」的活動,送出5本《深入淺出Webpack》!


3.14 構建離線應用

3.14.1 認識離線應用

即使將網頁的性能優化得非常好,如果網絡不好,則也會導致網頁的體驗很差。離線應用是指通過離線緩存技術,讓資源在第一次被加載後緩存在本地,在下次訪問它時就直接返回本地的文件,即使沒有網絡連接。

離線應用有以下優點:

  • 在沒有網絡的情況下也能打開網頁;
  • 由於部分被緩存的資源直接從本地加載,所以對用戶來說可以加快網頁的加載速度,對網站運營者來說可以減少服務器的壓力及傳輸流量費用。
  • 離線應用的核心是離線緩存技術,歷史上曾先後出現兩種離線離線緩存技術,如下所述。
  • AppCache :又叫作Application Cache,目前已經從Web標準中刪除,請盡量不要使用它。
  • Service Workers :是目前最新的離線緩存技術,是Web Worker的一部分。它通過攔截網絡請求實現離線緩存,比AppCache 更靈活。它也是構建PWA應用的關鍵技術之一。

與AppCache相比,Service Workers更加靈活,因為它可以通過JavaScript代碼去控制緩存的邏輯。由於第1 種技術已被廢棄,所以本節只專注於講解如何用Webpack 構建使用了Service Workers 的網頁。

3.14.2 認識ServiceWorkers

Service Workers 是一個在瀏覽器後台運行的腳本,它的生命週期完全獨立於網頁。它無法直接訪問DOM,但可以通過postMessage 接口發送消息來和UI 進程通信。攔截網絡請求是Service Workers 的重要功能,通過Service Workers 能完成離線緩存、編輯響應、過濾響應等功能。

若想更深入地了解Service Workers,則推薦閱讀服務工作線程簡介

1. Service Workers 兼容性

目前Chrome、Firefox 和Opera 都已經全面支持Service Workers,但只有高版本的Android支持移動端的瀏覽器。由於Service Workers無法通過注入polyfill實現兼容,所以在打算使用它前,請先弄明白自己的網頁的運行場景。

判斷瀏覽器是否支持Service Workers 的最簡單方法是通過以下代碼:

// 如果navigator 对象上存在serviceWorker 对象,就表示支持if (navigator.serviceWorker) { // 通过navigator.serviceWorker 使用}

2. 註冊Service Workers

要為網頁接入Service Workers,就需要在網頁加載後註冊一個描述Service Workers邏輯的腳本。代碼如下:

if (navigator.serviceWorker) { window.addEventListener('DOMContentLoaded', function () { // 调用serviceWorker.register 注册,参数/sw.js 为脚本文件所在的URL 路径navigator.serviceWorker.register('/sw.js'); }); }

一旦這個腳本文件被加載,Service Workers 的安裝就開始了。在這個腳本被安裝到瀏覽器中後,就算用戶關閉了當前網頁,它仍會存在。也就是說第一次打開該網頁時,Service Workers 的邏輯不會生效,因為腳本還沒有被加載和註冊,但是以後再次打開該網頁時腳本里的邏輯將會生效。在Chrome 中可以通過打開網址chrome://inspect/#service-workers 來查看當前瀏覽器中所有已註冊的Service Workers。

3. 使用Service Workers 實現離線緩存

Service Workers 在註冊成功後會在其生命週期中派發出一些事件,通過監聽對應的事件在特點的時間節點上做一些事情。

在Service Workers 腳本中引入了新的關鍵字self,代表當前的Service Workers 實例。在Service Workers 安裝成功後會派發出install 事件,需要在這個事件中執行緩存資源的邏輯,實現代碼如下:

// 当前缓存版本的唯一标识符,用当前时间代替var cacheKey = new Date().toISOString(); // 需要被缓存的文件的URL列表var cacheFileList = [ '/index.html', '/app.js', '/app.css' ]; // 监听install事件self.addEventListener('install', function (event) { // 等待所有资源缓存完成时,才可以进行下一步event.waitUntil( caches.open(cacheKey).then(function (cache) { // 要缓存的文件URL列表return cache.addAll(cacheFileList); }) ); });

接下來需要監聽網絡請求事件去攔截請求、復用緩存,代碼如下:

self.addEventListener('fetch', function (event) { event.respondWith( // 去缓存中查询对应的请求caches.match(event.request).then(function (response) { // 如果命中本地缓存,就直接返回本地的资源if (response) { return response; } // 否则就用fetch 下载资源return fetch(event.request); } )); });

這樣就實現了離線緩存。

4. 更新緩存

線上的代碼有時需要更新和重新發布,如果這個文件被離線緩存了,就需要在Service Workers 腳本中有對應的邏輯去更新緩存。這可以通過更新Service Workers 腳本文件做到。

瀏覽器針對Service Workers 有如下機制。

  • 每次打開接入了Service Workers的網頁時,瀏覽器都會重新下載Service Workers 腳本文件(所以要注意該腳本文件不能太大)。如果發現和當前已經註冊過的文件存在字節差異,就將其視為“新服務工作線程”。
  • 新的Service Workers 線程將會啟動,且將會觸發其install 事件。
  • 當網站上當前打開的頁面關閉時,舊的Service Workers 線程將會被終止,新的Service Workers 線程將會取得控制權。
  • 新的Service Workers 線程取得控制權後,將會觸發其activate 事件。

新的Service Workers 線程中的activate 事件就是清理舊緩存的最佳時間點,代碼如下:

// 当前缓存的白名单,在新脚本的install 事件里将使用白名单里的key var cacheWhitelist = [cacheKey]; self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all( cacheNames.map(function (cacheName) { // 将不在白名单中的缓存全部清理掉if (cacheWhitelist.indexOf(cacheName) === -1) { // 删除缓存return caches.delete(cacheName); } })); }) ); });

最終,完整的Service Workers 腳本代碼如下:

// 当前缓存版本的唯一标识符,用当前时间代替var cacheKey = new Date().toISOString(); // 当前缓存的白名单,在新脚本的install 事件里将使用白名单里的key var cacheWhitelist = [cacheKey]; // 需要被缓存的文件的URL 列表var cacheFileList = [ '/index.html', 'app.js', 'app.css' ]; // 监听install 事件self.addEventListener('install', function (event) { // 等所有资源缓存完成时,才可以进行下一步event.waitUntil( caches.open(cacheKey).then(function (cache) { // 要缓存的文件URL 列表return cache.addAll(cacheFileList); })); }); // 拦截网络请求self.addEventListener('fetch', function (event) { event.respondWith( // 去缓存中查询对应的请求caches.match(event.request).then(function (response) { // 如果命中本地缓存,就直接返回本地的资源if (response) { return response; } // 否则就用fetch 下载资源return fetch(event.request); }) ); }); // 新的Service Workers 线程取得控制权后,将会触发activate 事件self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all(cacheNames.map(function (cacheName) { // 将不在白名单中的缓存全部清理掉if (cacheWhitelist.indexOf(cacheName) === -1) { // 删除缓存return caches.delete(cacheName); } })); })); });

3.14.3 接入Webpack

用Webpack構建接入Service Workers的離線應用時,要解決的關鍵問題在於如何生成上面提到的sw.js 文件。並且sw.js 文件中的cacheFileList 變量,代表需要被緩存文件的URL 列表,需要根據輸出文件列表所對應的URL 來決定,而不是像上面那樣寫成靜態值。假如構建輸出的文件目錄結構為:

├── app_4c3e186f.js ├── app_7cc98ad0.css └── index.html

那麼sw.js 文件中cacheFileList 的值應該是:

var cacheFileList = [ '/index.html', 'app_4c3e186f.js', 'app_7cc98ad0.css' ];

Webpack沒有原生功能可以完成以上要求,幸好龐大的社區中已經有人為我們做好一個插件serviceworker-webpack-plugin ,通過它可以方便地解決以上問題。使用該插件後的Webpack 配置如下:

const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const { WebPlugin } = require('web-webpack-plugin'); const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); module.exports = { entry: { app: './main.js'// Chunk app 的JS 执行入口文件}, output: { filename: '[name].js', publicPath: '', }, module: { rules: [{ test: /.css/, // 增加对CSS 文件的支持// 提取出Chunk 中的CSS 代码到单独的文件中use: ExtractTextPlugin.extract({ use: ['css-loader'] }), },] }, plugins: [ // 压缩CSS 代码// 一个WebPlugin 对应一个HTML 文件new WebPlugin({ template: './template.html', // HTML 模板文件所在的文件路径filename: 'index.html' // 输出的HTML 的文件名称}), new ExtractTextPlugin({ filename: `[name].css`,// 为输出的CSS 文件名称加上Hash 值}), new ServiceWorkerWebpackPlugin({ // 自定义的sw.js 文件所在的路径// ServiceWorkerWebpackPlugin 会将文件列表注入生成的sw.js 中entry: path.join(__dirname, 'sw.js'), }),], devServer: { // Service Workers 依赖HTTPS,使用DevServer 提供的HTTPS 功能。 https: true, } };

以上配置有兩點需要注意:

  • 由於Service Workers 必須在HTTPS 環境下才能攔截網絡請求來實現離線緩存,使所以這里通過在2.6 節中提到的方式去實現HTTPS 服務。
  • serviceworker-webpack-plugin 插件為了保證靈活性,允許使用者自定義sw.js, 構建輸出的sw.js 文件中會在頭部注入一個變量serviceWorkerOption.assets 到全局,裡面存放著所有需要被緩存的文件的URL 列表。

需要將上面的sw.js 文件中被寫成了靜態值的cacheFileList 修改如下:

// 需要被缓存的文件的 URL 列表
var cacheFileList = global.serviceWorkerOption.assets;

以上已經完成所有文件的修改,在重新構建前先安裝新引入的依賴:

npm i -D serviceworker-webpack-plugin webpack-dev-server

安裝成功後,在項目根目錄下執行webpack-dev-server 命令後,DevServer 將以HTTPS 模式啟動,並輸出如下日誌:

> webpack-dev-server Project is running at https://localhost:8080/ webpack output is served from / Hash: 402ee6ce5bffb16dffe2 Version: webpack Time: 619ms Asset Size Chunks Chunk Names app.js 325 kB0 [emitted] [big] app app.css 21 bytes0 [emitted] app index.html 235bytes [emitted] sw.js 4.86 kB [emitted]

用Chrome瀏覽器打開網址 localhost:8080/index.ht 後,就能訪問接入Service Workers離線緩存的頁面了。

3.14.4 驗證結果

為了驗證Service Workers 和緩存是否生效,需要通過Chrome 的開發者工具來查看。通過打開開發者工具的Application-Service Workers 一欄,就能看到當前頁面註冊的Service Workers,正常的效果如圖3-6 所示。

blank

圖3-6查看當前頁面註冊的Service Workers

打開開發者工具的Application-Cache-Cache Storage 一欄,就能看到當前頁面緩存的資源列表,正常的效果如圖3-7 所示。

blank

圖3-7查看當前頁面的Cache Storage

為了驗證網頁在離線時的訪問能力,需要在開發者工具中的Network 一欄通過Offline 選項禁用網絡,再刷新頁面使其能正常訪問,並且網絡請求的響應都來自Service Workers, 正常的效果如圖3-8 所示。

blank

圖3-8在離線情況下訪問頁面

本實例提供項目的完整代碼,參見3-14構建離線應用.zip


活動:成為外刊君作者!

歡迎註冊/登錄前端外刊評論官網: https://qianduan.group ,提交你的文章,一旦通過審核,成為外刊君的作者,前五名即可獲得《深入淺出Webpack》一本!

要求

  • 文章內容緊貼前端、Node.js 等;
  • 原創、譯文不限;
  • 字數適中,文章結構排版清晰;
  • 在前端外刊評論首發

活動時間

2018年2月14日止。

What do you think?

Written by marketer

blank

2018 我所了解的Vue 知識大全(二)

blank

掃碼關注外刊君公眾號,定期推送!