《深入浅出 Webpack》章节试读 & 送书活动

Posted 前端外刊评论

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《深入浅出 Webpack》章节试读 & 送书活动相关的知识,希望对你有一定的参考价值。

今天给大家带来新书《深入浅出 Webpack》试读章节。作者 @吴浩麟, 一线前端工程师,曾就职于腾讯,现就职于美团。专注于 Web 开发,参与过众多大型 Web 项目的构建、设计和开发,喜欢探索Web前沿技术。也是 Golang 和音视频技术的爱好者,活跃于 GitHub,ID为 gwuhaolin。

深入浅出 Webpack

在文末有一个「成为外刊君作者!」的活动,送出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 的最简单方法是通过以下代码:

 
   
   
 
  1. // 如果 navigator 对象上存在 serviceWorker 对象,就表示支持

  2. if (navigator.serviceWorker) {

  3.  // 通过 navigator.serviceWorker 使用

  4. }

2. 注册 Service Workers

要为网页接入Service Workers,就需要在网页加载后注册一个描述Service Workers逻 辑的脚本。代码如下:

 
   
   
 
  1. if (navigator.serviceWorker) {

  2.  window.addEventListener('DOMContentLoaded', function () {

  3.    // 调用 serviceWorker.register 注册,参数/sw.js 为脚本文件所在的 URL 路径

  4.    navigator.serviceWorker.register('/sw.js');

  5.  });

  6. }

一旦这个脚本文件被加载,Service Workers 的安装就开始了。在这个脚本被安装到浏览 器中后,就算用户关闭了当前网页,它仍会存在。也就是说第一次打开该网页时,Service Workers 的逻辑不会生效,因为脚本还没有被加载和注册,但是以后再次打开该网页时脚本 里的逻辑将会生效。 在 Chrome 中可以通过打开网址 chrome://inspect/#service-workers 来查看当 前浏览器中所有已注册的 Service Workers。

3. 使用 Service Workers 实现离线缓存

Service Workers 在注册成功后会在其生命周期中派发出一些事件,通过监听对应的事 件在特点的时间节点上做一些事情。

在 Service Workers 脚本中引入了新的关键字 self,代表当前的 Service Workers 实例。 在 Service Workers 安装成功后会派发出 install 事件,需要在这个事件中执行缓存资 源的逻辑,实现代码如下:

 
   
   
 
  1. // 当前缓存版本的唯一标识符,用当前时间代替

  2. var cacheKey = new Date().toISOString();

  3. // 需要被缓存的文件的URL列表

  4. var cacheFileList = [

  5.  '/index.html',

  6.  '/app.js',

  7.  '/app.css'

  8. ];

  9. // 监听install事件

  10. self.addEventListener('install', function (event) {

  11.  // 等待所有资源缓存完成时,才可以进行下一步

  12.  event.waitUntil(

  13.    caches.open(cacheKey).then(function (cache) {

  14.      // 要缓存的文件URL列表

  15.      return cache.addAll(cacheFileList);

  16.    })

  17.  );

  18. });

接下来需要监听网络请求事件去拦截请求、复用缓存,代码如下:

 
   
   
 
  1. self.addEventListener('fetch', function (event) {

  2.  event.respondWith(

  3.    // 去缓存中查询对应的请求

  4.    caches.match(event.request).then(function (response) {

  5.      // 如果命中本地缓存,就直接返回本地的资源

  6.      if (response) {

  7.        return response;

  8.      }

  9.      // 否则就用 fetch 下载资源

  10.      return fetch(event.request);

  11.    }

  12.    ));

  13. });

这样就实现了离线缓存。

4. 更新缓存

线上的代码有时需要更新和重新发布,如果这个文件被离线缓存了,就需要在 Service Workers 脚本中有对应的逻辑去更新缓存。这可以通过更新 Service Workers 脚本文件做到。

浏览器针对 Service Workers 有如下机制。

  • 每次打开接入了Service Workers的网页时,浏览器都会重新下载Service Workers 脚本文件(所以要注意该脚本文件不能太大)。如果发现和当前已经注册过的文件 存在字节差异,就将其视为“新服务工作线程”。

  • 新的 Service Workers 线程将会启动,且将会触发其 install 事件。

  • 当网站上当前打开的页面关闭时,旧的 Service Workers 线程将会被终止,新的 Service Workers 线程将会取得控制权。

  • 新的 Service Workers 线程取得控制权后,将会触发其 activate 事件。

新的 Service Workers 线程中的 activate 事件就是清理旧缓存的最佳时间点,代码如下:

 
   
   
 
  1. // 当前缓存的白名单,在新脚本的 install 事件里将使用白名单里的 key

  2. var cacheWhitelist = [cacheKey];

  3. self.addEventListener('activate', function (event) {

  4.  event.waitUntil(

  5.    caches.keys().then(function (cacheNames) {

  6.      return Promise.all(

  7.        cacheNames.map(function (cacheName) {

  8.          // 将不在白名单中的缓存全部清理掉

  9.          if (cacheWhitelist.indexOf(cacheName) === -1) {

  10.            // 删除缓存

  11.            return caches.delete(cacheName);

  12.          }

  13.        }));

  14.    })

  15.  );

  16. });

最终,完整的 Service Workers 脚本代码如下:

 
   
   
 
  1. // 当前缓存版本的唯一标识符,用当前时间代替

  2. var cacheKey = new Date().toISOString();

  3. // 当前缓存的白名单,在新脚本的 install 事件里将使用白名单里的 key

  4. var cacheWhitelist = [cacheKey];

  5. // 需要被缓存的文件的 URL 列表

  6. var cacheFileList = [

  7.  '/index.html', 'app.js', 'app.css'

  8. ];

  9. // 监听 install 事件

  10. self.addEventListener('install', function (event) {

  11.  // 等所有资源缓存完成时,才可以进行下一步

  12.  event.waitUntil(

  13.    caches.open(cacheKey).then(function (cache) {

  14.      // 要缓存的文件 URL 列表

  15.      return cache.addAll(cacheFileList);

  16.    }));

  17. });

  18. // 拦截网络请求

  19. self.addEventListener('fetch', function (event) {

  20.  event.respondWith(

  21.    // 去缓存中查询对应的请求

  22.    caches.match(event.request).then(function (response) {

  23.      // 如果命中本地缓存,就直接返回本地的资源

  24.      if (response) {

  25.        return response;

  26.      }

  27.      // 否则就用 fetch 下载资源

  28.      return fetch(event.request);

  29.    })

  30.  );

  31. });

  32. // 新的 Service Workers 线程取得控制权后,将会触发 activate 事件

  33. self.addEventListener('activate', function (event) {

  34.  event.waitUntil(

  35.    caches.keys().then(function (cacheNames) {

  36.      return Promise.all(cacheNames.map(function (cacheName) {

  37.        // 将不在白名单中的缓存全部清理掉

  38.        if (cacheWhitelist.indexOf(cacheName) === -1) {

  39.          // 删除缓存

  40.          return caches.delete(cacheName);

  41.        }

  42.      }));

  43.    }));

  44. });

3.14.3 接入Webpack

用Webpack构建接入Service Workers的离线应用时,要解决的关键问题在于如何生成 上面提到的 sw.js 文件。并且 sw.js 文件中的 cacheFileList 变量,代表需要被缓存文件的 URL 列表,需要根据输出文件列表所对应的 URL 来决定,而不是像上面那样写成静态值。 假如构建输出的文件目录结构为:

 
   
   
 
  1. ├── app_4c3e186f.js

  2. ├── app_7cc98ad0.css

  3. └── index.html

那么 sw.js 文件中 cacheFileList 的值应该是:

 
   
   
 
  1. var cacheFileList = [

  2.  '/index.html',

  3.  'app_4c3e186f.js',

  4.  'app_7cc98ad0.css'

  5. ];

Webpack没有原生功能可以完成以上要求,幸好庞大的社区中已经有人为我们做好一个 插件 serviceworker-webpack-plugin,通过它可以方便地解决以上问题。使用该插件后的 Webpack 配置如下:

 
   
   
 
  1. const path = require('path');

  2. const ExtractTextPlugin = require('extract-text-webpack-plugin');

  3. const { WebPlugin } = require('web-webpack-plugin');

  4. const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');

  5. module.exports = {

  6.  entry: {

  7.    app: './main.js'// Chunk app 的 JS 执行入口文件

  8.  },

  9.  output: {

  10.    filename: '[name].js', publicPath: '',

  11.  }, module: {

  12.    rules: [{

  13.      test: /\.css/, // 增加对 CSS 文件的支持

  14.      // 提取出 Chunk 中的 CSS 代码到单独的文件中

  15.      use: ExtractTextPlugin.extract({

  16.        use: ['css-loader']

  17.      }),

  18.    },]

  19.  }, plugins: [

  20.    // 压缩 CSS 代码

  21.    // 一个 WebPlugin 对应一个 HTML 文件

  22.    new WebPlugin({

  23.      template: './template.html', // HTML 模板文件所在的文件路径

  24.      filename: 'index.html' // 输出的 HTML 的文件名称

  25.    }),

  26.    new ExtractTextPlugin({

  27.      filename: `[name].css`,// 为输出的 CSS 文件名称加上 Hash 值

  28.    }),

  29.    new ServiceWorkerWebpackPlugin({

  30.      // 自定义的 sw.js 文件所在的路径

  31.      // ServiceWorkerWebpackPlugin 会将文件列表注入生成的 sw.js 中

  32.      entry: path.join(__dirname, 'sw.js'),

  33.    }),],

  34.  devServer: {

  35.    // Service Workers 依赖 HTTPS,使用 DevServer 提供的 HTTPS 功能。

  36.    https: true,

  37.  }

  38. };

以上配置有两点需要注意:

  • 由于 Service Workers 必须在 HTTPS 环境下才能拦截网络请求来实现离线缓存,使所以这里通过在 2.6 节中提到的方式去实现 HTTPS 服务。

  • serviceworker-webpack-plugin 插件为了保证灵活性,允许使用者自定义 sw.js, 构建输出的 sw.js 文件中会在头部注入一个变量 serviceWorkerOption.assets 到全局,里面存放着所有需要被缓存的文件的 URL 列表。

需要将上面的 sw.js 文件中被写成了静态值的 cacheFileList 修改如下:

 
   
   
 
  1. // 需要被缓存的文件的 URL 列表

  2. var cacheFileList = global.serviceWorkerOption.assets;

以上已经完成所有文件的修改,在重新构建前先安装新引入的依赖:

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

安装成功后,在项目根目录下执行 webpack-dev-server 命令后,DevServer 将以 HTTPS 模式启动,并输出如下日志:

 
   
   
 
  1. > webpack-dev-server

  2. Project is running at https://localhost:8080/

  3. webpack output is served from /

  4. Hash: 402ee6ce5bffb16dffe2

  5. Version: webpack

  6. Time: 619ms

  7.     Asset       Size Chunks                    Chunk Names

  8. app.js 325 kB0 [emitted] [big] app

  9. app.css 21 bytes0 [emitted] app

  10. index.html 235bytes [emitted]

  11. sw.js 4.86 kB   [emitted]  

用 Chrome 浏览器打开网址 https://localhost:8080/index.html 后,就能访问 接入 Service Workers 离线缓存的页面了。

3.14.4 验证结果

为了验证 Service Workers 和缓存是否生效,需要通过 Chrome 的开发者工具来查看。 通过打开开发者工具的 Application-Service Workers 一栏,就能看到当前页面注册的 Service Workers,正常的效果如图 3-6 所示。

《深入浅出 Webpack》章节试读 & 送书活动
图 3-6 查看当前页面注册的 Service Workers

打开开发者工具的 Application-Cache-Cache Storage 一栏,就能看到当前页面缓存的资 源列表,正常的效果如图 3-7 所示。

图 3-7 查看当前页面的 Cache Storage

为了验证网页在离线时的访问能力,需要在开发者工具中的 Network 一栏通过 Offline 选项禁用网络,再刷新页面使其能正常访问,并且网络请求的响应都来自 Service Workers, 正常的效果如图 3-8 所示。

图 3-8 在离线情况下访问页面

本实例提供项目的完整代码,参见 3-14构建离线应用.zip。


活动:成为外刊君作者!

欢迎注册/登录前端外刊评论官网:https://qianduan.group ,提交你的文章,一旦通过审核,成为外刊君的作者,前五名即可获得《深入浅出 Webpack》一本!

要求

  • 文章内容紧贴前端、Node.js 等;

  • 原创、译文不限;

  • 字数适中,文章结构排版清晰;

  • 在前端外刊评论首发。

活动时间

2018年2月14日止。


阅读原文 即可注册/登录参与活动。


以上是关于《深入浅出 Webpack》章节试读 & 送书活动的主要内容,如果未能解决你的问题,请参考以下文章

初识跨终端Web

《恐怖童谣》小说全文精彩章节在线阅读(小阳)

深入浅出的webpack构建工具---webpack基本配置

深入浅出的webpack构建工具--webpack4+vue搭建环境 (十三)

[js高手之路]深入浅出webpack教程系列9-打包图片(file-loader)用法

webpck深入浅出教程webpack源码分析