# 图片资源处理

# 图片引入方式

图片是前端项目必不可少的静态资源,在日常开发中,可能会在下面三种情况使用图片:

  1. HTML 中通过<img>标签等方式引入;
  2. CSS 中通过src等方式引入;
  3. JavaScript 中使用图片的 URL 或者内容(比如 Canvas 等)。

最笨最直接的方式就是直接写死线上的地址,例如在页面中,引入<img>如下:

<img src="http://s.bxstatic.com/foo/bar.png" />

上面地址的http://s.bxstatic.com是一个 CDN (opens new window) 静态域名,后面是完整的路径,这样上线的时候地址就可以直接使用,线下开发的时候可以提前将静态资源打包好上传到线上。这样操作很费劲,而且 CDN 每次静态资源更新都要需要刷新缓存,如果使用 MD5 命名图片的时候就更麻烦了。

在 Webpack 中,则可以使用 loader 的方式完成图片的引入。

例如在 CSS 文件中,直接相对路径使用背景图片:

.bg-img {
    background: url(./foo/bar.png) no-repeat;
}

在 HTML 中也可以直接使用相对路径:

<img src="../../foo/bar.png" />

问题:

  • 怎么让 Webpack 识别图片,并且能够打包输出呢?
  • 上面的路径不喜欢使用相对路径,怎么办?有没有使用alias的方式?

# url-loader

这时候就需要借助 loader 了,这里有两个 loader 可以使用:file-loader (opens new window)url-loader (opens new window)

file-loaderurl-loader是经常在一些 Webpack 配置中看到的两个 loader,下面介绍下两者的区别:

  • file-loader:能够根据配置项复制使用到的资源(不局限于图片)到构建之后的文件夹,并且能够更改对应的链接;
  • url-loader包含 file-loader 的全部功能,并且能够根据配置将符合配置的文件转换成 Base64 方式引入,将小体积的图片 Base64 引入项目可以减少 http 请求,也是一个前端常用的优化方式。

下面以url-loader为例说明下 Webpack 中使用方法。

首先是安装对应的 loader:npm install -D url-loader

下面创建一个项目,目录结构如下:

├── package.json
├── public
│   └── index.html
├── src
│   ├── assets
│   │   ├── big.png      # 大图片
│   │   ├── middle.jpg   # 中等大小图片
│   │   └── small.png
│   ├── index.css
│   └── index.js
└── webpack.config.js    # webpack配置

首先在index.css中引入 small.png

.bg-img {
    background: url(./assets/small.png) no-repeat;
}

然后在index.js中引入了index.csslarge.png

import img from './assets/big.png';
import style from './index.css';
console.log(img, style);

最后在index.html中通过<img>引入large.png

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Image</title>
  </head>
  <body>
    <img src="./assets/img/large.png" alt="背景图" />
  </body>
</html>

这时候修改webpack.config.js

// webpack.config.js
const HTMLPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devtool: false,
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ['html-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: {
          loader: 'url-loader'
        }
      }
    ]
  },
  plugins: [
    new HTMLPlugin({
      template: './src/index.html'
    })
  ]
};

执行webpack之后的 log:

图片描述

这时候发现,打包出来的文件都比较大,通过查看内容发现,的图片被Base64处理了,然后直接引入了:

图片描述

这是因为url-loader本身优先是将资源Base64引入的。虽然图片Base64可以减少 http 请求,但是对于1M+这么大的图片都Base64处理,范围增加了 CSS、JavaScript 等文件的大小,而且将这么大的Base64反解成可以使用的图片渲染出来,时间消耗也是很大的。

所以这时候需要使用url-loaderlimit选项来控制不超过一定限制的图片才使用Base64

{
    test: /\.(png|svg|jpg|gif)$/,
    use: {
        loader: 'url-loader',
        options: {
            limit: 3*1024 // 3k
        }
    }
}

这时候再执行webpack,发现多打出一个ad19429dc9b3ac2745c760bb1f460892.png的图片,这张图片就是large.png的图片,因为超过了limit=3*1024显示所以没有被处理成Base64

图片描述 继续查看index.htmlmain.jsindex.js打包出来的文件),发现使用large.png的地址都被 Webpack 自动替换成了新的路径ad19429dc9b3ac2745c760bb1f460892.png

# HTML 和 CSS 中使用 alias

前面提到过,除了使用相对路径的方式引入静态资源,还可以使用别名(alias)的方式,url-loader也会给处理这种情况的引用。

修改index.htmlindex.css

<img src="@assets/img/large.png" alt="背景图" />
.bg-img {
    background: url(@assets/img/small.png) no-repeat;
}

然后修改webpack.config.js增加resolve.alias

module.exports = {
    //...
    resolve: {
        alias: {
            '@assets': path.resolve(__dirname, './src/assets')
        }
    }
    //...
};

这时候执行webpack发现报错了:

ERROR in ./src/index.css (/webpack-tutorial/node_modules/css-loader/dist/cjs.js!./src/index.css)
Module not found: Error: Can't resolve './@assets/img/small.png' in '/webpack-tutorial/packages/chapter-02/static/src'
 @ ./src/index.css (/webpack-tutorial/node_modules/css-loader/dist/cjs.js!./src/index.css) 4:41-75
 @ ./src/index.css
 @ ./src/index.js

ERROR in   Error: Child compilation failed:
  Module not found: Error: Can't resolve './@assets/img/large.png' in '/webpack-tutorial/packages/chapter-02/static/src':
  Error: Can't resolve './@assets/img/large.png' in '/webpack-tutorial/packages/chapter-02/static/src'

这是因为在 HTML 和 CSS 使用alias必须要前面添加~,即:

<img src="~@assets/img/large.png" alt="背景图" />
.bg-img {
    background: url(~@assets/img/small.png) no-repeat;
}

修改完后,直接执行webpack既可以看到正确的结果了。

Tips:HTML 中使用<img>引入图片等静态资源的时候,需要添加html-loader配置,不然也不会处理静态资源的路径问题。

svg-url-loader (opens new window) 的工作原理类似于 url-loader,除了它利用 URL encoding (opens new window) 而不是 Base64 对文件编码。对于 SVG 图片这是有效的,因为 SVG 文件恰好是纯文本,这种编码规模效应更加明显,使用方法如下:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-url-loader',
        options: {
          // 小于 10kB(10240字节)的内联文件
          limit: 10 * 1024,
          // 移除 url 中的引号
          // (在大多数情况下它们都不是必要的)
          noquotes: true
        }
      }
    ]
  }
};

Tips:svg-url-loader 拥有改善 IE 浏览器支持的选项,但是在其他浏览器中更糟糕。如果你需要兼容 IE 浏览器,设置 iesafe: true选项。

# 图片优化

图片体积是个经常诟病的问题,一个页面中,完全一样内容的图片,在肉眼可见的范围内并不一定有差异但是体积却相差甚大。

所以图片优化也是在前端项目中经常做的事情,在 Webpack 中可以借助img-webpack-loader (opens new window)来对使用到的图片进行优化。

它支持 JPG、PNG、GIF 和 SVG 格式的图片,因此在碰到所有这些类型的图片都会使用它。

# 安装
npm install image-webpack-loader --save-dev

image-webpack-loader (opens new window)这个 loader 不能将图片嵌入应用,所以它必须和 url-loader 以及 svg-url-loader 一起使用。

为了避免同时将它复制粘贴到两个规则中(一个针对 JPG/PNG/GIF 图片, 另一个针对 SVG ),使用 enforce: 'pre' 作为单独的规则涵盖在这个 loader:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'image-webpack-loader',
        // 这会应用该 loader,在其它之前
        enforce: 'pre'
      }
    ]
  }
};

通过enforce: 'pre'提高了 img-webpack-loader 的优先级,保证在url-loadersvg-url-loader之前就完成了图片的优化。

另外img-webpack-loader (opens new window)默认的配置就已经适用于日常开发图片的压缩优化需求了,但是如果你想更进一步去配置它,参考插件选项 (opens new window)。要选择指定选项,请查看国外牛人写的一个图像优化指南 (opens new window)

# 其他资源处理

# 字体、多媒体

对于字体、富媒体等静态资源,可以直接使用url-loader或者file-loader进行配置即可,不需要额外的操作,具体配置内容如下:

{
    // 文件解析
    test: /\.(eot|woff|ttf|woff2|appcache|mp4|pdf)(\?|$)/,
    loader: 'file-loader',
    query: {
        // 这么多文件,ext不同,所以需要使用[ext]
        name: 'assets/[name].[hash:7].[ext]'
    }
},

Tips:如果不需要 Base64,那么可以直接使用 file-loader,需要的话就是用url-loader,还需要注意,如果将正则(test)放在一起,那么需要使用[ext]配置输出的文件名。

# JSON/CSV/XML数据

如果项目需要加载的类似 JSON、CSV、TSV 和 XML 等数据,那么需要单独给它们配置相应的 loader。对 JSON 的支持实际上是内置的,类似于 Node.js,这意味着import Data from'./data.json'导入数据默认情况将起作用。要导入 CSV,TSV 和 XML,可以使用csv-loader (opens new window)xml-loader (opens new window)

首先是安装它们的 loader:npm i -D xml-loader csv-loader,然后增加文件 loader 配置如下:

 {
    test: /\.(csv|tsv)$/,
    use: [
    'csv-loader'
    ]
},
{
    test: /\.xml$/,
    use: [
    'xml-loader'
    ]
}

现在,您可以导入这四种类型的数据中的任何一种(JSON,CSV,TSV,XML),并且导入它的 Data 变量将包含已解析的 JSON 以便于使用。

# 配置 CDN 域名

一般静态资源上线的时候都会放到 CDN,假设的 CDN 域名和路径为:http://bd.bxstatic.com/img/,这时候只需要修改output.publicPath即可:

module.exports = {
    //..
    output: {
        publicPath: 'http://bd.bxstatic.com/img/'
    }
    //..
};

修改后执行webpack打包后的结果如下:

<body>
  <img src="http://bd.bxstatic.com/img/ad19429dc9b3ac2745c760bb1f460892.png" alt="背景图" />
  <script src="http://bd.bxstatic.com/img/main.js"></script>
</body>

说明 Webpack 为自动替换了路径,并且加上了 CDN 域名。