前言
Webpack 是一个 JavaScript 的模块打包器。在 Webpack 的世界,一切等待打包的源码都是模块,一个文件就是一个模块。
Webpack 基于 Nodes.js 开发,采用 CommonJS 模块化规范,所以默认只能处理 .js
和 .json
类型的模块。而项目中经常用到的文件类型有 .css
、less
、 scss
这类样式文件,也有图片资源比如 .png
,.jpg
等,还有字体文件比如.tff
、woff
等,对于使用前端框架开发的项目,还有 .vue
、.jsx
等文件类型。然而这些类型的模块 Webpack 是不认识的,需要对应的 loader
模块加载器来处理一下,从而让 Webpack 识别和打包。
本文主要介绍 Webpack5 的一个新特性——Assets Module。它是一种新的用来处理图片和字体资源模块的方式。
Asset Modules
在 webpack5 以前,通常使用 file-loader 和 url-loader 来处理图片和字体文件等资源,形如:
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
}
而 webpack5 提供了一个新特性叫作 Asset Modules,在内部实现了对资源模块的支持。Asset Modules 提供了4种资源模块的类型,而无需再额外配置其他的 loader 来对资源模块进行处理,分别是:
asset/resource
:构建出一个单独的文件并导出 URL。相当于以前的file-loader
。asset/inline
:导出资源模块的Data URI 内嵌到 html 或 css 文件。相当于以前的url-loader
。asset/source
:导出资源模块的源代码。相当于以前的raw-loader
。asset
:在导出 Data URI 和导出一个单独文件之间自动进行选择。以前可以通过使用url-loader
的limit
配置项来实现。
下面就这几种 Assets Module 的类型进行介绍。
准备工作
创建一个演示环境:
mkdir assets-module
cd assets-module
pnpm init
安装依赖:
pnpm add -D webpack webpack-cli css-loader style-loader
准备好的初始文件有:
webpack 的配置文件中对于 .css
做了配置:
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}
asset/resource
该配置相当于以前的 file-loader
,用来将模块单独导出一个文件,并提供一个 URL 来使用该文件。
在 index.html 中写一个盒子,通过样式设置背景图:
<body>
<div class="guodegang"></div>
<script src="./dist/main.js"></script>
</body>
在 index.css 中设置一个样式类:
.guodegang {
width: 600px;
height: 400px;
background-size: cover;
background-image: url(./assets/images/guodegang.png);
}
入口文件引入该样式文件:
import './index.css'
webpack 配置对图片资源的处理规则:
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset/resource'
}
执行打包命令:
pnpm webpack
打包的结果是生成一个 hash 值命名的图片文件:
浏览器打开 index.html 文件:
asset/inline
该配置相当于以前的 url-loader
。将文件通过 base64
编码转为 Data URI
的格式 ,并内联到使用它的文件中。
在 index.html 中设置一个新的盒子:
<body>
<div class="lijian"></div
<script src="./dist/main.js"></script>
</body>
在 index.css 中设置一个样式类:
.lijian {
width: 600px;
height: 350px;
background-size: cover;
background-image: url(./assets/images/lijian.webp);
}
配置模块的打包规则:
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset/inline'
}
执行打包命令,看看打包的结果,会发现 dist 目录下并没有生成新的文件。但是我们打开 index.html,发现图片文件其实已经被打包好了:
这是因为 asset/inline
的配置,会将源码中引入的模块转译为 Data URI 内嵌到输出文件中。
一个问题
将文件编译为 Data URI 使用,可以节省 HTTP 请求,是一个性能优化的点。但是将图片文件经过 base64 编码转为 Data URI,体积会增加大约33%。这其中涉及到一些编码的知识,有兴趣的朋友可以亲自尝试一下。所以,对于一些比较大的文件来说,转为 Data URI 会明显增加打包后文件的体积,从而会加大对带宽资源和流量的需求。
前端性能优化中最基本的两条规则就是减少 HTTP 请求,压缩资源减少文件体积。但是这个配置项,刚好是截然相反。能减少 HTTP 请求,但会增加文件体积。
那么该如何均衡这两种方式呢?换句话说,该如何确定什么时候使用 asset/inline
,什么时候使用 asset/resource
呢?
在以前的开发中有一种雪碧图的优化方案,就是将一些小的图标放到一张文件中使用,这样很多个图标就只需要发送一次 HTTP 请求就可以了。
所以, asset/inline
这种方式,更适合于一些打包一些体积较小的文件,将其内联来减少请求的数量。对于较大的文件,还是使用 asset/resource
的方式,打包成单独的文件。
那么具体该如何设置,让webpack 智能的根据文件大小来执行不同的打包方式呢?下面的 asset
就是了。
asset
使用这个配置,默认小于 8kb 的文件将被视为inline
模块类型,将转为 Data URI 以内联的方式使用;大于 8kb 的文件将被视为resource
模块类型,打包成单独的文件。
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset'
}
可以通过 parser.dataUrlCondition.maxSize
属性来修改默认的大小:
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
// 生成Data URI 的条件
dataUrlCondition: {
// 当资源模块不超过 4kb 时,生成 DataURI,超过 4kb 时,单独打包成文件
maxSize: 4 * 1024 // 4b
}
}
}
一般,在开发中我们使用默认的配置就好。
aseet/source
这种用的比较少,相当于以前的 raw-loader。示例配置:
{
test: /\.txt/,
type: 'asset/source'
}
处理字体文件
字体文件和图片同属于资源模块,同样使用上面的配置就好:
{
test: /\.(eot|ttf|otf|woff2?)$/,
type: 'asset'
}
使用一下看看效果。
index.html 中使用字体图标:
<body>
<div>
<i class="iconfont icon-home"></i>
<i class="iconfont icon-user"></i>
</div>
<script src="./dist/main.js"></script>
</body>
入口文件中引用字体图标的样式类:
import './assets/fonts/iconfont.css'
执行打包命令,看下效果:
由于字体文件比较小,不足 8kb,所以字体文件被编码为 Data URI 进行使用了。
打包文件的重命名和路径修改
默认打包后的文件,会统一放到输出目录中,文件名为 hash 值。
当资源文件过多时,比较混乱,不易管理。所以 webpack 还提供了两个配置项和一些占位符,可以实现自定义模块的输出路径和文件名:
- output.assetModuleFilename:对所有模块生效
- generator.filename:对具体某个类型的模块生效
- [name]:模块的原始文件名
- [hash]:模块文件的哈希值
- [ext]:模块的原始扩展名
示例配置:
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
generator: {
filename: 'img/[name].[hash:8][ext]'
}
},
{
test: /\.(eot|ttf|otf|woff2?)$/,
type: 'asset',
generator: {
filename: 'fonts/[name].[hash:8][ext]'
}
}
对于体积较大的文件,将图片文件打包进 dist/img
目录下,将字体文件打包进 dist/fonts
目录下。
再次打包看下效果:
总结
本文介绍了 Webpack5 的一个新特性 Assets Module 资源模块,主要用来代替以前 file-loader、 url-loader 和 raw-loader 来对图片、字体文件等资源模块进行处理。Webpack5 提供了4种类型资源模块,用法和区别已经在上文中演示过了,大家可根据需要灵活选用。同时,还可以根据配置自定义模块的输出路径和文件名,方便打包资源的管理。
在阅读文档和使用的过程中,个人感觉,与其说是 Assets Module 提供了四种模块类型,不如说是针对资源模块提供了4种不同的打包方式,将 type
理解为方式,而非类型,似乎更能体现 asset
、asset/resource
、asset/inline
、asset/raw
的名字上的区别。或许是我思考过度了,你认为呢?欢迎朋友们留言评论,一起讨论!