# 图片资源处理
# 图片引入方式
图片是前端项目必不可少的静态资源,在日常开发中,可能会在下面三种情况使用图片:
- HTML 中通过
<img>
标签等方式引入; - CSS 中通过
src
等方式引入; - 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-loader
和url-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.css
和large.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-loader
的limit
选项来控制不超过一定限制的图片才使用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.html
和main.js
(index.js
打包出来的文件),发现使用large.png
的地址都被 Webpack 自动替换成了新的路径ad19429dc9b3ac2745c760bb1f460892.png
。
# HTML 和 CSS 中使用 alias
前面提到过,除了使用相对路径的方式引入静态资源,还可以使用别名(alias
)的方式,url-loader
也会给处理这种情况的引用。
修改index.html
和index.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-loader
和svg-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 域名。