本文已参与「新人创作礼」活动,一起开启掘金创作之路
本篇文章源码已传到 github: github.com/Plasticine-…
前两章我们讲了加载css资源相关的内容,这篇文章我们来探讨一下在webpack中加载图片、字体等资源,也就是说我们需要在js中直接import图片等资源并在html中使用它们
1. 加载图片案例场景
以加载图片为例,先编写入口js文件
// src/index.js
import './css/index.css';
// ESModule 或者 CommonJS 方式将图片作为模块使用
import img1 from './img/img1.jpg';
// const img1 = require('./img/img.jpg').default;
(() => {
const init = () => {
const el = document.createElement('div');
// 1. 使用图片 -- 通过 img 标签的 src 属性
const fooImg = new Image();
fooImg.src = img1;
fooImg.style.width = '300px';
fooImg.style.height = '300px';
fooImg.style.objectFit = 'cover';
// 2. 使用图片 -- 通过 css 设置 background-image 属性
const barEl = document.createElement('div');
barEl.style.width = '300px';
barEl.style.height = '300px';
barEl.className = 'bg-img';
el.appendChild(fooImg);
el.appendChild(barEl);
document.body.appendChild(el);
};
init();
})();
/* src/css/index.css */
.bg-img {
background-image: url('../img/img2.jpg');
background-size: cover;
}
这里使用了两种方式使用图片:
img标签的src属性css中设置background-image属性,通过url()函数引用图片
第一种方式中可以使用import xxx from 'img/xxx.jpg'这样的形式或者require('img/xxx.jpg')这种形式,也就是ES Module和Common JS都支持,但是要注意:
file-loader版本如果是4.x的话,require()加载图片资源直接使用即可file-loader版本在5.0以上,require()加载的图片资源要使用default属性,即require('img/xxx.jpg').default
直接打包会遇到如下错误
这是因为入口文件
index.js中加载了img.jpg文件,因此img.jpg会加入到webpack的依赖图中,当加载.jpg文件的时候,会到配置文件中寻找配置的相关loader去处理它,但是由于并没有配置loader去处理jpg文件,因而会报错
2. 使用 file-loader 处理图片资源
这里我们需要一个叫file-loader的loader去处理图片文件
pnpm i file-loader -D
file-loader的作用就是帮助我们处理import/require引入的文件资源,并将这些资源放到输出的dist目录中,如果不配置输出目录则默认是直接放到dist目录下,这个是可以配置的,比如想要配置输出到dist/img目录也是可以的
先简单用一下吧,直接将file-loader写进webpack.config.js中
/**
* @type { import('webpack').Configuration }
*/
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpe?g|png|gif)/,
use: ['file-loader'],
},
],
},
};
打包后的结果
可以看到,被引用的图片会被直接复制到
dist目录下,并且用一串hash值作为文件名
2.1 使用 file-loader 的 placeholder 配置文件名
打包出来的图片文件名是可以配置的,使用file-loader提供的placeholder占位符来进行配置
根据官方文档的说明,我们做出如下配置
/**
* @type { import('webpack').Configuration }
*/
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]-[sha256:hash:8]-[emoji].[ext]',
},
},
],
},
],
},
};
接下来打包看看生成的文件名:img-3a8d7299-🏗️.jpg,生成了这样一个文件名,和我们配置的规则符合
2.2 配置文件存放路径
根据file-loader的官方文档,我们还可以使用outputPath配置存放路径
/**
* @type { import('webpack').Configuration }
*/
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]-[sha256:hash:8]-[emoji].[ext]',
outputPath: 'img',
},
},
],
},
],
},
};
注意:每次重新打包都需要将dist目录删除,否则旧的打包文件仍然会保留而不是被替代,只有文件名相同时才会替代,这点之后会通过一个**plugin**解决
2.3 file-loader 在 webpack5 中的问题
心细的读者可能发现了,file-loader的那个文档还是webpack4时候的,事实上到了webpack5开始,官方已经不推荐使用file-loader了,而是使用Asset Modules进行替代
If you have rules defined for loading assets using raw-loader, url-loader, or file-loader, please use Asset Modules instead as they're going to be deprecated in near future.
所以一步步跟着做到现在时会发现,打包的结果当中会发现打包结果当中,对于直接在js中导入的图片资源是没有啥问题的,但是css文件中通过url导入的图片是无法正常显示的,并且打包结果中多出了一个多余的图片文件
并且该图片文件的内容根本不是图片二进制数据,而是一串文本
如果使用
webpack4则上述配置不需要进行任何更改即可正常工作,但是如果实在是想在webpack5中使用的话,就需要对配置作出如下修改,或者使用Asset Modules,这个后面会讲
这是
webpack5官方文档中说的,要想在webpack5中使用file-loader,就需要将type配置为javascript/auto
/**
* @type { import('webpack').Configuration }
*/
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
// 使用 file-loader
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]-[sha256:hash:8]-[emoji].[ext]',
outputPath: 'img',
esModule: false,
},
},
],
type: 'javascript/auto',
},
],
},
};
还做出的修改是file-loader的options中添加一项esModule: false
这样修改的原因是css-loader会将url()的资源解析为url(require('xxx')),在遇到图片文件时,触发了配置好的.jpg规则,使用file-loader进行解析
而file-loader在解析的时候默认使用ESModule的方式解析的,使用ESModule的方式去解析require那肯定是无效的,所以需要先将esModule置为false,让其使用CommonJS的方式去解析模块
现在重新打包就可以看到效果啦
可以看到两种方式加载的图片资源都成功被打包
3. 使用 url-loader 处理图片资源
前面我们使用file-loader让webpack能够处理在js中直接导入图片的行为,以及在css中使用url()加载的图片
现在我们思考一个问题,如果项目当中有很多图片都需要打包的话,全部都直接复制到打包结果中,那会导致打包结果十分巨大,大部分都是图片资源,那么有没有一种能够将图片打包成base64编码的方式,而不是直接复制图片呢?
当然也不能够将任何图片都直接用base64编码处理,我们希望最理想的结果是在某一个指定阈值内,使用base64编码图片,而超出该阈值的时候,就还是将其打包成图片文件的方式
3.1 配置 url-loader
这时候就要用到url-loader了,首先安装url-loader
pnpm i url-loader -D
然后进行配置,根据url-loader的官方文档(当然,仍然是webpack4下的,因为webpack5中已经弃用)它具有如下配置项
| Name | Type | Default | Description |
|---|---|---|---|
| limit | {Boolean\|Number\|String} | true | Specifying the maximum size of a file in bytes. |
| mimetype | {Boolean\|String} | based from mime-types | Sets the MIME type for the file to be transformed. |
| encoding | {Boolean\|String} | base64 | Specify the encoding which the file will be inlined with. |
| generator | {Function} | () => type/subtype;encoding,base64_data | You can create you own custom implementation for encoding data. |
| fallback | {String} | file-loader | Specifies an alternative loader to use when a target file's size exceeds the limit. |
| esModule | {Boolean} | true | Use ES modules |
我们可以使用limit配置项去配置前面说到的阈值,我使用的两张图片一张是2.94MB一张是4.13MB的,那就将阈值设置为3MB吧,这样的话最终打包结果应当是2.94MB的图片使用base64编码处理,而4.13MB的图片以文件的方式被打包
// 使用 url-loader
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 3 * 1024 * 1024, // 3MB
},
},
],
type: 'javascript/auto',
},
我们还希望大于3MB的图片被打包成文件,但是配置项中貌似并没有看到能让我们配置文件打包相关的配置项,其实在超出limit时,默认会使用file-loader
If the file size is equal or greater than the limit file-loader will be used (by default) and all query parameters are passed to it. Using an alternative to file-loader is enabled via the fallback option.
根据官方文档的说明,当解析超出limit限制的文件时,我们在options中配置的配置项会被传递到file-loader中,当然这是默认会交给file-loader处理,我们也可以在fallback配置项中指定使用别的loader
既然如此,那我们直接在options中填写file-loader的配置项即可
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 3 * 1024 * 1024, // 3MB
// 超出 limit 阈值时默认会使用 file-loader 处理 并且配置项会被传递给 file-loader
name: '[name]-[sha256:hash:8]-[emoji].[ext]',
outputPath: 'img',
esModule: false,
},
},
],
type: 'javascript/auto',
},
当然,和前面配置file-loader一样,因为file-loader、url-loader都是已经在webpack5中被废弃了,因此我们还需要添加上type: 'javascript/auto'和options.esModule: false
虽然是麻烦了点,但是由于讲解的是loader的使用,因此还是要这样做,如果嫌麻烦也可以安装webpack4,不过后面会讲解webpack5官方推荐的方式,不会再弄这么多不必要的配置了!
3.2 不要盲目使用 base64 编码图片
现在我们来打包看看结果吧
可以看到打包结果中确实只有一张图片了,和我们的预期一样!再打开浏览器看看另外一张图片是如何被处理的
可以看到,
2.94MB的那张图片(也就是第一张图片)是被进行base64编码处理了,不过实际使用中我们只会对头像这种比较小的图片进行base64编码处理,如果对大图片也是用base64处理的话,实际上编码后的结果并没有比图片本身小多少
可以看到,我们单个
js文件就已经接近4MB了,就是因为在js中存放了图片的base64编码结果才这样的
因此千万不要无脑将所有图片都使用base64编码处理哦,我们使用base64编码的主要目的还是为了减少**http**请求
对于那些数量多,但是体积小的图片文件,如果以文件的方式打包的话会产生多条http请求,而如果把它们全都编码成base64存在js文件中,那http请求只会有一次,这样才是合理使用url-loader的场景。
4. 使用 Asset Modules 处理图片资源
前面使用file-loader和url-loader处理图片资源是webpack4时期的主流方式,现在到了webpack5了,我们需要使用新的方式去处理图片资源
Asset Modules的官方文档
从官方文档中可以直到:
asset/resource替代了原来的file-loaderasset/inline替代了原来的url-loaderasset/source替代了原来的raw-loader(一种用于将文件加载成字符串的loader)asset替代了原来的url-loader的limit配置
那么我们就来用一下吧,首先是最基本的加载图片
// 使用 asset/resource 加载图片资源
{
test: /\.(jpe?g|png|gif)$/,
type: 'asset/resource',
},
直接打包,结果如下
打包成功,并且浏览器中可以正常加载图片,但是如何和之前一样想要配置打包的文件名以及存放路径怎么办呢?
有两种方式,我的理解就是全局配置和局部配置
4.1 打包资源文件名配置
4.1.1 全局配置
在
output.assetModuleFilename中配置文件名和存放路径
module.exports = {
output: {
// 全局配置资源文件打包路径和文件名
assetModuleFilename: 'img/[name]-[hash:8][ext]',
},
};
注意:**[ext]**占位符已经包括了**.**,因此不需要再额外配置一个**.**上去,即不需要写成img/[name]-[hash:8].[ext]
并且不支持[sha256:hash]这样指定hash算法,以及不能使用[emoji]生成可爱的文件名了,要想使用这样的文件名占位符,还是需要使用file-loader
打包结果如下
4.1.2 局部配置
就是在具体的
rules项中去配置,通过generator去配置
// 使用 asset/resource 加载图片资源
{
test: /\.(jpe?g|png|gif)$/,
type: 'asset/resource',
generator: {
filename: 'img/[name]-[hash:8][ext]',
},
},
4.2 实现 url-loader 的 limit 效果
You can change this condition by setting a Rule.parser.dataUrlCondition.maxSize option on the module rule level of your webpack configuration
type改为assetparser.dataUrlCondition.maxSize中配置阈值
// 使用 asset/resource 加载图片资源
{
test: /\.(jpe?g|png|gif)$/,
type: 'asset',
generator: {
filename: 'img/[name]-[hash:8][ext]',
},
parser: {
dataUrlCondition: {
maxSize: 3 * 1024 * 1024, // 3MB
},
},
},
5. 加载字体文件
项目中除了使用图片资源外,还经常会使用到字体或者字体图标等资源,那么它们又该如何被webpack加载呢?
首先先准备一下案例环境,我们去阿里的iconfont图标库网站生成一份字体图标代码
- 随便找一个图标,下载其
svg文件
- 上传图标
- 点击右上角的购物车按钮
- 下载代码
5.1 font class 方式使用字体图标
解压后能够看到源码,并且有一个html文件打开后可以学习如何使用字体图标,我们使用font class的方式使用字体图标,即导入css文件,并添加相应类名即可使用
// 使用 iconfont 字体图标
import './font/iconfont.css';
const el = document.createElement('div');
// 使用字体图标
const iconEl = document.createElement('i');
iconEl.style.fontSize = '128px';
iconEl.className = 'iconfont icon-shopping';
el.appendChild(iconEl);
使用asset/resource配置字体文件的存放位置
{
test: /\.ttf$/,
type: 'asset/resource',
generator: {
filename: 'font/[name]-[hash:8][ext]',
},
},
打包后的效果如下
5.2 Symbol 方式使用字体图标
可以看到字体图标生效了,但是为啥和一开始在网站上下载的图标不一样呢?好像没有颜色诶,我们打开解压后的源码中的html文档看一看
原来使用这种方式的字体图标是不支持多色显示的,要想使用多色显示,得使用
Symbol方式
那我们就试试这种方式吧
// src/index.js
// Symbol 方式使用 iconfont 字体图标
import './font/iconfont';
// Symbol 方式使用字体图标
const symbolIconEl = document.createElement('svg');
const useEl = document.createElement('use');
useEl.setAttribute('xlink:href', '#icon-shopping');
symbolIconEl.className = 'icon';
symbolIconEl.ariaHidden = true;
symbolIconEl.appendChild(useEl);
/* src/css/index.css */
.icon {
width: 10em;
height: 10em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
现在就能看到彩色图标了
通过对图片资源和字体资源在webpack中的使用,不难发现,对于其他任何类型的资源处理也是类似的
只要是想要以文件的方式直接放到打包结果中的话,先配置一条rule,匹配想要处理的文件类型,然后指明type,就是用asset/resource即可,更详细的内容建议阅读webpack5官方文档,本篇文章只是一个开篇,让你能够快速了解webpack的一些概念,如果想要深入了解还是建议阅读官方文档,官方文档才是最好的学习途径!