这是我参与更文挑战的第5天,活动详情查看: 更文挑战
前言
目前项目已经可以加载了 css 文件了,这篇文章将会介绍如何加载资源文件(如:图片)。
旧版本与 webpack5+ 加载资源的区别
在 webpack5 之前,可能需要使用 raw-loader、file-loader、url-loader 来加载资源。
- raw-loader:将文件作为字符串导入
- file-loader:处理文件的路径并输出文件到输出目录
- url-loader:有条件将文件转化为 base64 URL,如果文件大于 limit 值,通常交给 file-loader 处理。
在 webpack5+,以上方法已经过时了,webpack5 使用了“资源模块”来代替以上 loader。 官方是这样解释“资源模块”的。
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
而“资源模块”类型有四种。
- asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
- asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
- asset/source: 导出资源的源代码。之前通过使用 raw-loader 实现。
- asset: 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
asset/resource
asset/resource 起到两个作用,一个是用来解析文件的 URL,另外一个是将目标文件输出到打包目录。
我们先在 src 目录里创建一个 assets 目录,里面新建一个 img 目录,再把一张测试图片复制进来。
在 webpack.config.js 进行配置。
module: {
rules: [
...
{
test: /\.(jpe?g|png|svg|gif)/i,
type: 'asset/resource'
}
]
}
在 index.js 引入图片,如果不引入的话,webpack 是不会输出这张图片,因为 webpack 认为这张图片没有被用过。
import '../assets/img/simao.jpg'
(2022.5.28,上面这种引入虽然会打包出图片,但是打包后的 js 上不会带有相关的图片内容,更正一下这里引入图片的示例,这里这样展示会好一点)
首先在 index.html 写上一个 img 标签
index.html
<img class="img" src="">
然后在 index.js 引入图片,并且插入到 img 标签里
index.js
import img from '../assets/img/simao.jpg'
document.querySelector('.img').setAttribute('src', img)
运行 npm run build。
图片打包到了 dist 目录了,main.js 也引入了图片,并且图片名默认是以哈希字符串命名(命名规则:[hash][ext][query]
)。
不过图片是打包到 dist 根目录上,我们再配置一下,让图片放到 dist/img 目录下
webpack.config.js
output: {
...
assetModuleFilename: 'img/[hash][ext][query]'
}
我们打包后就发现图片放到指定目录下了。
这种方法看起来不错,但是资源文件不单止是图片,可能还会是字体、下载文件,这些文件的类型应该也是 asset/resource,难道我们都统一放到 img 目录下吗?
显然不合理,我们可以改用第二种方案,局部去指定目录。
module.exports = {
...
output: {
...
// assetModuleFilename: 'img/[hash][ext][query]' // 全局指定资源文件输出位置
}
...
module: {
rules: [
...
{
test: /\.(jpe?g|png|svg|gif)/i,
type: 'asset/resource',
generator: {
filename: 'img/[hash][ext][query]' // 局部指定输出位置
}
},
]
}
}
asset/inline
浏览器请求一个带有图片的网页,会发出 http 请求下载相应的图片,有多少张图片,就有多少个 http 请求,这样是很耗费图片服务器资源以及损耗速度的,想象一下需要加载的图片可能就几kb,却要发出一个 http 请求。
因此将小图片转化为 base64 字符串是一个不错的选择(雪碧图的技术也是为了解决上述问题而出现,有兴趣的可以深究一下)。asset/inline 就是起到这个作用。
记得把上面写的 asset/resource 规则注释掉,防止冲突。
webpack.config.js
module: {
rules: [
...
// {
// test: /\.(jpe?g|png|svg|gif)/i,
// type: 'asset/resource',
// generator: {
// filename: 'img/[hash][ext][query]' // 局部指定输出位置
// }
// },
{
test: /\.(jpe?g|png|svg|gif)/i,
type: 'asset/inline',
},
]
}
index.js
import img from '../assets/img/simao.jpg'
document.body.style.background = `url(${img})`
build 一下。
可以发现,图片没有打包出来了,如果你在这个时候打开 main.js,会发现里面有一串很长的字符串,那个字符串就是 base64 字符串。
在浏览器打开 dist/index.html,或运行 npm run dev,网页的背景图将会预期展现出来。
注意了,asset/inline 会将所有符合规则的资源都变为 base64 字符串,也即是比较大的图片也会转化为 base64,base64 占用的空间可能会比原图片占用的空间还要大(可以动手试试),这种情况直接输出图片会更加好。
那么该怎么做判断,让小图片去转化为 base64,让大图片去直接输出到打包目录呢?
在 webpack5 里,asset 资源类型可以解决这个问题。
asset
asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64,如果图片大于或等于限制,则使用 asset/resource 处理,如果图片小于限制 asset/inline 处理。asset 相当于一个判断工具。
项目里的图片(simao.jpg)大小为10kb,现在,我将一张 3kb 图片(名为:webpack.svg)放进 src/asset/img 里做测试。
同样地,注释之前设置的规则,然后新增新的规则。
webpack.config.js
module: {
rules: [
...
// {
// test: /\.(jpe?g|png|svg|gif)/i,
// type: 'asset/resource',
// generator: {
// filename: 'img/[hash][ext][query]' // 局部指定输出位置
// }
// },
// {
// test: /\.(jpe?g|png|svg|gif)/i,
// type: 'asset/inline',
//},
{
test: /\.(jpe?g|png|svg|gif)/i,
type: 'asset',
generator: {
filename: 'img/[hash][ext][query]' // 局部指定输出位置
},
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 限制于 8kb
}
}
}
]
}
index.js 引入 webpack.svg,并设置 body 的背景图为 webpack.svg。
index.js
import img from '../assets/img/simao.jpg'
import img2 from '../assets/img/webpack.svg'
// 更换为 img2 背景图
document.body.style.background = `url(${img2})`
npm run build,发现 dist/img 有图片文件(原 simao.jpg),simao.jpg 并没有并转化为 base64。
我们再在浏览器浏览 index.html 页面,发现 webpack.svg 被转化为 base64 并写入到 body 的背景图里。
由于 simao.jpg 大于 8kb,asset 选择用 asset/resource 处理它。
由于 webpack.svg 小于 8kb,asset 选择用 asset/inline 处理它。
asset/source
这个在实际开发中用得比较少,所以就简单说一下。
asset/source 的作用可以理解为“把目标文件的内容输出到 js 变量中”。
写一个处理 txt 文件的规则,资源类型为 asset/source。
webpack.config.js
module: {
rules: [
...
{
test: /\.txt/,
type: 'asset/source'
}
]
}
在 src/assets 创建一个 txt 目录,里面新建一个 test.txt,test.txt 内容如下:
Hello World
index.js
import txt from '../assets/txt/test.txt'
console.log(txt)
npm run dev,可以发现 “Hello World” 打印出来了
补充:webpack 别名设置
从“webpack5 的使用(一):起步”到这篇文章,有没有发现一个问题。
好像每次 import 一个资源都要写上相对路径,如果文件换目录,那不是每个 import 都要改一遍,别担心,webpack 有一个别名设置,可以设定路径别名。
设置 @ 为 src 的别名。
webpack.config.js
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
// 下面可以继续新增别名
}
}
}
index.js 里这样导入
import '@/css/index.css'
import '@/scss/index.scss'
import img from '@/assets/img/simao.jpg'
import img2 from '@/assets/img/webpack.svg'
import txt from '@/assets/txt/test.txt'
完整代码
目录
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
console.log('环境变量:', process.env.NODE_ENV)
module.exports = {
// entry: path.resolve(__dirname, '../src/js/index.js'),
entry: {
main: path.resolve(__dirname, '../src/js/index.js'),
header: path.resolve(__dirname, '../src/js/header.js'),
footer: path.resolve(__dirname, '../src/js/footer.js'),
},
output: {
// filename: 'main.js',
filename: 'js/[name].[fullhash].js',
path: path.resolve(__dirname, '../dist'),
// assetModuleFilename: 'img/[hash][ext][query]' // 全局指定资源文件输出位置和文件名
},
// devServer: {
// port: 3000,
// hot: true,
// contentBase: '../dist'
// },
plugins: [
// new HtmlWebpackPlugin({
// title: '首页'
// }),
// 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/index.html'),
filename: 'index.html',
chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/header.html'),
filename: 'header.html',
chunks: ['header']
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/footer.html'),
filename: 'footer.html',
chunks: ['footer']
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].[fullhash].css'
})
],
module: {
rules: [
{
test: /\.css$/i,
use: [
//'style-loader', 'css-loader'
MiniCssExtractPlugin.loader, 'css-loader'
]
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
]
},
// {
// test: /\.(jpe?g|png|svg|gif)/i,
// type: 'asset/resource',
// generator: {
// filename: 'img/[hash][ext][query]' // 局部指定输出位置
// }
// },
// {
// test: /\.(jpe?g|png|svg|gif)/i,
// type: 'asset/inline',
// },
{
test: /\.(jpe?g|png|svg|gif)/i,
type: 'asset',
generator: {
filename: 'img/[hash][ext][query]' // 局部指定输出位置
},
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 限制于 8kb
}
}
},
{
test: /\.txt/,
type: 'asset/source'
}
]
},
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
// 下面可以继续新增别名
}
}
}
index.js
import '@/css/index.css'
import '@/scss/index.scss'
import img from '@/assets/img/simao.jpg'
import img2 from '@/assets/img/webpack.svg'
import txt from '@/assets/txt/test.txt'
document.body.style.background = `url(${img2})`
console.log('这是一个入口文件')
console.log('环境变量:', process.env.NODE_ENV)
console.log(txt)
系列文章
webpack5 的使用(零):概念
webpack5 的使用(一):起步
webpack5 的使用(二):多个环境配置
webpack5 的使用(三):加载 css
webpack5 的使用(四):加载资源文件
webpack5 的使用(五):babel 转译 js 代码
webpack5 的使用(六):优化