Webpack: 深入理解图像加载原理与最佳实践

概述

图形图像资源是当代 Web 应用的最常用、实惠的内容、装饰元素之一,但在 Webpack 出现之前对图像资源的处理复杂度特别高,需要借助一系列工具(甚至 Photoshop)完成压缩、雪碧图、hash、部署等操作。

而在 Webpack 中,图像以及其它多媒体资源都被提升为一等公民 ------ 能够像引用普通 JavaScript 模块一样通过 import/require 语句导入资源模块,这种开发模式允许我们将图像相关的处理合入统一的心智模型中,提升开发效率。

本文将集中介绍 Webpack 体系下处理图像资源的常见方法,包括:

  • 如何使用适当的 Loader 处理图像资源;
  • 如何借助 Loader 或插件实现图像优化,包括压缩、雪碧图、响应式图片。

在 Webpack 4 中导入图像

原生 Webpack 4 只能处理标准 JavaScript 模块,因此需要借助 Loader ------ 例如 file-loaderurl-loaderraw-loader 等完成图像加载操作,实践中我们通常需要按资源类型选择适当加载器,简单介绍:

  • file-loader:将图像引用转换为 url 语句并生成相应图片文件,例如使用如下配置:

    js 复制代码
    // webpack.config.js
    module.exports = {
      // ...
      module: {
        rules: [{
          test: /\.(png|jpg)$/,
          use: ['file-loader']
        }],
      },
    };

经过 file-loader 处理后,原始图片会被重命名并复制到产物文件夹,同时在代码中插入图片 URL 地址,形如:

  • url-loader:有两种表现,对于小于阈值 limit 的图像直接转化为 base64 编码;大于阈值的图像则调用 file-loader 进行加载,例如如下配置:

    js 复制代码
    module.exports = {
      // ...
      module: {
        rules: [{
          test: /\.(png|jpg)$/,
          use: [{
            loader: 'url-loader',
            options: {
              limit: 1024
            }
          }]
        }],
      },
    };

经过 url-loader 处理后,小于 limit 参数即 1024B 的图片会被转译为 Base64 编码,如:

对于超过 limit 值的图片则直接调用 file-loader 完成加载。

url-loader 同样适用于大多数图片格式,且能将许多细小的图片直接内嵌进产物中,减少页面运行时需要发出的网络请求数,在 HTTP 1.1 及之前版本中能带来正向的性能收益。

  • raw-loader:不做任何转译,只是简单将文件内容复制到产物中,适用于 SVG 场景,例如如下配置:

    js 复制代码
    // webpack.config.js
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.svg$/i,
            use: ['raw-loader'],
          },
        ],
      },
    };

经过 raw-loader 处理后,SVG 资源会被直接复制成字符串形式:

提示:除 raw-loader 外,我们还可以使用如下 Loader 加载 SVG 资源:

在 Webpack 5 中导入图像

上述 file-loaderurl-loaderraw-loader 都并不局限于处理图片,它们还可以被用于加载任意类型的多媒体或文本文件,使用频率极高,几乎已经成为标配组件!所以 Webpack5 直接内置了这些能力,开箱即可使用。

用法上,原本需要安装、导入 Loader,Webpack5 之后只需要通过 module.rules.type 属性指定资源类型即可,对比来看:

  • file-loader 对标到 type = "asset/resource"'
js 复制代码
// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.(png|jpg)$/,
-     use: ['file-loader']
+     type: 'asset/resource'
    }],
  },
};

提示:默认情况下,asset/resource 生成的文件会以 [hash][ext][query] 方式重命名,可以通过 output.assetModuleFilename 属性控制。

  • url-loader 对标到 type = "asset"type = "asset/inline"

    js 复制代码
    module.exports = {
      // ...
      module: {
        rules: [{
          test: /\.(png|jpg)$/,
    -     use: [{
    -       loader: 'url-loader',
    -       options: {
    -         limit: 1024
    -       }
    -     }]
    +     type: "asset",
    +     parser: {
    +        dataUrlCondition: {
    +          maxSize: 1024 // 1kb
    +        }
    +     }
        }],
      },
    };

其中,module.rules.parser.dataUrlCondition 用于限定文件大小阈值,对标 url-loaderlimit 属性。

  • raw-loader 对标到 type = "asset/source"

    js 复制代码
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.svg$/i,
    -       use: ['raw-loader']
    +       type: "asset/source"
          },
        ],
      },
    };

补充一下,引入 module.rules.type 并不只是为了取代 Loader 那么简单,更重要的目的是在 JavaScript Module 之外增加对其它资源 ------ Asset Module 的原生支持,让 Webpack 有机会介入这些多媒体资源的解析、生成过程,从而有机会实现更标准、高效的资源处理模型。

目前 module.rules.type 已经支持 JSON、WebAssemsbly、二进制、文本等资源类型,相信在下一个 Webpack 版本中,必然会基于 Asset Module 实现更丰富的资源处理能力。

图像优化:压缩

前面介绍的 Loader 与 Asset Modules 都只是解决了图像资源加载 ------ 也就是让 Webpack 能够理解、处理图像资源,现实中我们还需要为 Web 页面中的图片做各种优化,提升页面性能,常见的优化方法包括:

  • 图像压缩:减少网络上需要传输的流量;
  • 雪碧图:减少 HTTP 请求次数;
  • 响应式图片:根据客户端设备情况下发适当分辨率的图片,有助于减少网络流量;
  • CDN:减少客户端到服务器之间的物理链路长度,提升传输效率;
  • 等等。

这其中有不少可以在开发、构建阶段借助 Webpack 搭建自动优化工作流,例如:图像压缩。

在 Webpack 生态中有不少优秀的图像压缩组件,包括:image-webpack-loaderimagemin-webpack-pluginimage-minimizer-webpack-plugin 等,以我的使用经验来看,image-webpack-loader 组件功能齐全且用法简单,更推荐使用。基本用法首先安装依赖:

arduino 复制代码
yarn add -D image-webpack-loader

之后配置 Loader:

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.(gif|png|jpe?g|svg)$/i,
      // type 属性适用于 Webpack5,旧版本可使用 file-loader
      type: "asset/resource",
      use: [{
        loader: 'image-webpack-loader',
        options: {
          // jpeg 压缩配置
          mozjpeg: {
            quality: 80
          },
        }
      }]
    }],
  },
};

image-webpack-loader 底层依赖于 imagemin 及一系列的图像优化工具:

  • mozjpeg:用于压缩 JPG(JPEG) 图片;
  • optipng:用于压缩 PNG 图片;
  • pngquant:同样用于压缩 PNG 图片;
  • svgo:用于压缩 SVG 图片;
  • gifsicle:用于压缩 Gif 图;
  • webp:用于将 JPG/PNG 图压缩并转化为 WebP 图片格式。

基本上已经覆盖 Web 页面常用的图片格式,具体用法可点击上述链接查阅,此处不再赘述。最后补充一点,图像压缩是一种非常耗时的操作,建议只在生产环境下开启:

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [{
      // ...
      use: [{
        loader: 'image-webpack-loader',
        options: {
+         disable: process.env.NODE_ENV === 'development'
          // ...
        }
      }]
    }],
  },
};

图像优化:雪碧图

在 HTTP 2 之前,HTTP 请求-响应是一种性能低下的通讯模型,即使是为了请求一个非常少的数据,也可能需要完整经历:建立 TCP 连接 => 发送 HTTP 请求 => 服务端处理 => 返回响应数据整个过程,加之 HTTP 协议的队首阻塞、浏览器并发请求数限制等原因,迫使我们必须尽量减少 HTTP 请求数以提升网络通讯效率。

例如,我们可以将许多细小的图片合并成一张大图 ------ 从而将复数次请求合并为一次请求,之后配合 CSS 的 background-position 控制图片的可视区域,这种技术被称作"雪碧图 "。在 Webpack 中,我们可以使用 webpack-spritesmith 插件自动实现雪碧图效果,首先安装依赖:

csharp 复制代码
yarn add -D webpack-spritesmith

之后添加配置:

js 复制代码
module.exports = {
  // ...
  resolve: {
    modules: ["node_modules", "assets"]
  },
  plugins: [
    new SpritesmithPlugin({
      // 需要
      src: {
        cwd: path.resolve(__dirname, 'src/icons'),
        glob: '*.png'
      },
      target: {
        image: path.resolve(__dirname, 'src/assets/sprite.png'),
        css: path.resolve(__dirname, 'src/assets/sprite.less')
      }
    })
  ]
};

关键在于,webpack-spritesmith 插件会将 src.cwd 目录内所有匹配 src.glob 规则的图片合并成一张大图并保存到 target.image 指定的文件路径,同时生成兼容 SASS/LESS/Stylus 预处理器的 mixins 代码,例如对于下面文件结构:

lua 复制代码
load-img
├─ src
│  ├─ icons
│  │  ├─ grunt.png
│  │  ├─ gulp-js.png
│  │  └─ webpack.png
│  └─ index.js
├─ webpack.config.js
└─ package.json

按照上述配置运行后会生成如下产物:

src/assets/sprite.png src/assets/sprite.less
/* ... */ .sprite(@sprite) { .sprite-image(@sprite); .sprite-position(@sprite); .sprite-width(@sprite); .sprite-height(@sprite); } /* ... */

之后,我们就可以使用 sprite.less 提供的 .sprite mixin 添加背景图:

less 复制代码
@import (less) "./assets/sprite.less";

#main {
    // 参数为原始图片文件名
    .sprite(@webpack);
}

提示:雪碧图曾经是一种使用广泛的性能优化技术,但 HTTP2 实现 TCP 多路复用之后,雪碧图的优化效果已经微乎其微 ------ 甚至是反优化,可以预见随 HTTP2 普及率的提升,未来雪碧图的必要性会越来越低,因此建议读者们了解作用与基本原理即可,不必深究。

图像优化:响应式图片

移动互联网时代,我们需要面对的客户端设备越来越多样复杂,分辨率从 PC 到平板电脑再到移动终端跨度极大:

这会带来一个问题:同一张图片(主要是位图)在不同设备中,如果显示尺寸大于原始尺寸,最终效果会有明显颗粒感;而如果显示尺寸小于原始尺寸,又会造成带宽浪费。理想的解决方案是为不同设备提供不同的分辨率、不同尺寸的图片 ------ 也就是所谓的响应式图片

Webpack 中有不少能够自动生成响应式图片的组件,例如: resize-image-loaderhtml-loader-srcsetresponsive-loader 等,以 responsive-loader 为例,首先安装依赖:

csharp 复制代码
yarn add -D responsive-loader sharp

之后,修改配置:

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.(png|jpg)$/,
      oneOf: [{
        type: "javascript/auto",
        resourceQuery: /sizes?/,
        use: [{
          loader: "responsive-loader",
          options: {
            adapter: require("responsive-loader/sharp"),
          },
        }],
      }, {
        type: "asset/resource",
      }],
    }],
  }
};

注意,实践中我们通常没必要对项目里所有图片都施加响应式特性,因此这里使用 resourceQuery 过滤出带 size/sizes 参数的图片引用,使用方法:

js 复制代码
// 引用图片,并设置响应式参数
import responsiveImage from './webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024';

const Picture = function () {
  return (
    <img
      srcSet={responsiveImage.srcSet}
      src={responsiveImage.src}
      sizes="(min-width: 1024px) 1024px, 100vw"
      loading="lazy"
    />
  );
};

上例的引用参数 './webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024'; 最终将生成宽度分别为 300、600、1024 三张图片,之后设置 img 标签的 srcset 属性即可实现图片响应式功能。

此外,我们还能简单地通过 size 参数精确控制不同条件下的图像尺寸:

css 复制代码
.foo {
    background: url("./webpack.jpg?size=1024");
}

@media (max-width: 480px) {
    .foo {
        background: url("./webpack.jpg?size=300");
    }
}

提示:除本文提及的基本功能外,responsive-loader 还提供了许多用于控制产物、压缩比等特性的配置项,有需要的同学可到 官网 展开阅读。

总结

在 Webpack 5 之前,我们需要使用 file-loaderurl-loader 等 Loader 加载图片或其它多媒体资源文件,这些加载器各有侧重点,需要根据实际场景择优选用;而 Webpack 5 之后引入了 Asset Module 模型,自此我们只需要设置适当的 module.rules.type 配置即可,不需要为多媒体资源专门引入 Loader。

在加载之外,我们还可以借助 Webpack 生态一系列组件低成本实现图像压缩、雪碧图、响应式图片等优化措施

相关推荐
橙子味冰可乐1 小时前
isprintable()方法——判断字符是否为可打印字符
java·前端·javascript·数据库·python
yunpeng.zhou1 小时前
logging 模块简单使用记录
java·前端·数据库
奋斗吧程序媛3 小时前
使用vue动态给同一个a标签添加内容 并给a标签设置hover,悬浮文字变色,结果鼠标悬浮有的字上面不变色
前端·javascript·vue.js
王天平·Jason Wong5 小时前
vue3弹窗usehook
前端·javascript·vue.js
小跳不会Coding5 小时前
vue开发网站--关于window.print()调取打印
前端·javascript·vue.js
concisedistinct6 小时前
深入浅出:npm常用命令详解与实践
前端·npm·node.js·工具·modules
碎像7 小时前
使用AI工具 Baidu Comate 辅助编码 快速定位修改Bug
java·前端·后端·bug·intellij idea
snow@li7 小时前
工程化:Commitlint / 规范化Git提交消息格式
前端·git
下雪天的夏风7 小时前
React@16.x(43)路由v5.x(8)常见应用场景(5)- 滚动条复位
前端·javascript·react.js
苦逼的猿宝7 小时前
重温react-08(createContext使用方式)
前端·javascript·vue.js·react.js·前端框架