主题
处理样式中的图片资源与处理样式中的字体文件资源(Webpack 2/3)
本页对应 课堂视频,共 5 段视频,约 1 小时 9 分。
学习目标
- 能够为 Webpack 配置
css-loader与style-loader,正确处理.css文件,并说明二者执行顺序及样式注入页面的结果。 - 能够安装并配置
less、less-loader,按从右到左的 loader 执行顺序处理.less文件,并通过打包产物验证样式生效。 - 能够使用 Webpack 5 资源模块处理样式中的图片资源,配置
type、parser.dataUrlCondition.maxSize、generator.filename等选项,并验证小图转 Base64、大图输出文件及自定义输出路径。 - 能够配置样式中的字体文件资源,使用资源模块将其输出到指定目录,并通过页面字体效果和构建产物验证配置正确。
- 能够使用
HtmlWebpackPlugin处理 HTML 模板,配置模板路径、输出文件名、JS 自动注入位置、压缩与缓存参数,并验证生成的 HTML 与静态资源引用关系。 - 能够区分样式资源与 HTML 资源的处理方式,独立完成一次包含 CSS、Less、图片、字体和 HTML 的 Webpack 构建,并检查输出目录结构与页面运行效果。
知识点
为什么样式里的背景图也要交给 webpack
样式里最常见的图片用法就是背景图:background: url(../images/bg.png)。这个地址写在 CSS/Less 里,浏览器加载页面时会再去请求图片。问题是,一旦样式也要经过 webpack 打包,图片还能不能按原路径找到?默认情况下,webpack5 已经能识别样式中的图片资源,不需要我们再像 webpack4 那样额外安装 file-loader、url-loader。但“能打包”不等于“打包得合理”:它默认会把图片直接输出到 dist 根目录,和 js、html 混在一起,文件一多就像“一锅粥”。所以这一节要解决两件事:小图是否转成 base64,以及图片输出到哪里、叫什么名字。
先看默认行为:能打包,但输出位置不理想
我们准备一个 src/images 目录,里面放 png、jpg、gif、webp 等格式的图片,然后在 Less 里写背景图,例如:
less
.p1 {
width: 400px;
height: 200px;
background: url(../images/bg01.jpg);
}先不配置图片规则,直接打包。转写稿里的现象是:图片确实被打进 dist 了,页面刷新也能看到效果;但图片全堆在 dist 根目录。也就是说,webpack5 默认能处理,只是输出结果不符合项目要求。这里要注意路径:url() 是相对于当前写样式的文件来算的,不要拿打包后的目录去反推源码路径。写代码时只保证源码里的相对路径正确,打包后 webpack 会帮你改地址。
配置图片规则:asset + dataUrlCondition
要控制图片,仍然写在 module.rules 里。核心配置是 type: 'asset',它会根据图片大小自动选择:小图转成 base64 内联到 CSS 里,大图输出成单独文件。转写稿里把阈值设为 20KB:
js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 20 * 1024
}
},
generator: {
filename: 'static/images/[hash:8][ext][query]'
}
}
]
}
}test 用来匹配样式里引用到的图片格式;maxSize 表示小于 20KB 的图片转 base64;generator.filename 控制大图输出路径和文件名:static/images/ 是输出目录,[hash:8] 是 8 位哈希,[ext] 保留原扩展名,[query] 保留原地址后面的查询参数。
验证:小图变 base64,大图进 static/images
配置后再打包,转写稿里观察到:dist 里的图片数量变少了,原来六七张图只剩四张左右;打开页面检查元素,小图的 url() 已经变成 data:image/...;base64,... 一长串,而大图仍然是正常地址。这样做的好处是:小图直接内联进 CSS,少一次网络请求;坏处是图片太大还转 base64,会让 CSS 文件非常臃肿。所以阈值不能乱调大。
[query] 也容易误解:它只是“原来有 query 就保留”,不会凭空给没有 query 的地址加随机数。转写稿里给某个图片地址加了 ?v=1 之类的参数,打包后该参数仍保留;原本没有的,打包后也不会有。
易错点与小结
第一,webpack5 处理样式图片不需要额外装 loader,但需要写规则来调整输出;如果只想直接输出文件、不判断大小转 base64,可以用 type: 'asset/resource'。第二,test 里的点要转义,写成 \.,否则正则含义会变。第三,输出目录写在 generator.filename 里,不要随意改总出口 output.path,否则会影响 js、html 等所有资源。第四,图片路径始终相对当前样式文件书写。掌握这套“匹配文件 → 指定 asset 类型 → 控制阈值 → 控制输出名”的套路,后面处理字体文件也是同一套机制。
字体文件为什么要交给 Webpack
为什么需要:@font-face 里的字体不是“写死在 CSS 里的字符”,而是独立的 .woff2、.woff、.ttf、.eot、.svg 文件。样式里通过 url() 引用它们,Webpack 如果不认识这类资源,就无法把字体复制到输出目录,也无法把 CSS 中的旧路径替换成打包后的新路径。
怎么想:把字体看成“样式中使用的外部文件”。它和背景图类似,都是 CSS 通过 URL 引用的资源;区别是字体通常不需要转成 base64,直接输出成单独文件更合适。
怎么做:在 module.rules 中增加一条针对字体扩展名的规则。Webpack5 可以自动导入样式中需要的所有文件资源;如需设置输出目录,就要用 generator.filename。
js
module: {
rules: [
...
{
test: /\.(ttf|woff2?|woff|svg|eot)$/,
type: "asset/resource",
generator: {
filename: "static/fonts/[hash:8][ext][query]",
},
},
...
],
},易错点:test 后要有逗号;正则里的点建议转义;woff2? 已经能匹配 woff 和 woff2,笔记中再写 woff 是冗余但不算错。generator.filename 只控制这条规则产出的文件路径,不是 output.filename。
先判断用 asset/resource 还是 asset
为什么需要:Webpack5 内置了以前 file-loader、url-loader 的能力,但两种 type 行为不同。字体文件一般体积不小,若被自动转成 data URI 塞进 CSS,会让样式文件明显变大。
怎么想:字体要“发一个单独文件,并导出 URL”,所以选 asset/resource。asset 会在 data URI 和单独文件之间自动选择,适合图片按体积分流;字体不需要这个自动选择。
怎么做:本节笔记给出的区别是:
1. `asset/resource` 发送一个单独的文件并导出 URL。之前通过使用 `file-loader` 实现。
2. `asset` 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 `url-loader`,并且配置资源体积限制实现。易错点:不要因为“Webpack5 能自动处理”就以为输出目录也自动符合项目要求。转写稿里演示过:不配置时字体也能被打包,但默认位置可能直接落在 dist 根目录,和图片、JS 混在一起,所以还要加 generator。
在样式中写 @font-face
为什么需要:只有资源配置规则还不够,CSS 里必须用 @font-face 声明字体,浏览器才知道字体名称、格式和文件地址。
怎么想:src 中按浏览器支持情况列出多种格式,format() 是格式提示;路径要相对“当前这个样式文件”来写,不是相对项目根目录。
怎么做:保留笔记中的写法:
css
@font-face {
font-family: "FZSJ";
src: url("../fonts/FZSJ-WSMQSJW.woff2") format("woff2"),
url("../fonts/FZSJ-WSMQSJW.woff") format("woff"),
url("../fonts/FZSJ-WSMQSJW.ttf") format("truetype"),
url("../fonts/FZSJ-WSMQSJW.eot") format("embedded-opentype"),
url("../fonts/FZSJ-WSMQSJW.svg") format("svg");
font-weight: normal;
font-style: normal;
}易错点:url("../fonts/...") 是相对当前 CSS/Less 文件解析的。转写稿里特别提醒:不要考虑打包后的位置,只保证写代码时相对路径正确;打包后 Webpack 会自动替换 URL。页面使用字体时写 font-family: "FZSJ";,名称要和 @font-face 中一致。
打包与验证
为什么需要:配置写完必须验证三件事:字体文件是否输出到目标目录、CSS 中 URL 是否被替换、页面是否真正用上字体。
怎么想:先跑打包,再看 dist/static/fonts 是否出现字体文件;再打开页面检查元素,确认字体生效。转写稿中先打包,发现字体能被处理,但默认输出位置不理想;加上 generator 后再打包,字体进入 static/fonts,这才是想要的结果。
怎么做:运行打包命令。笔记中写作 npx wepback,这是拼写错误,实际应执行:
bash
# 笔记中写作 npx wepback,实际命令是:
npx webpack易错点:如果页面没生效,先查 @font-face 路径、字体名称、浏览器是否支持该格式;如果输出目录不对,查 generator.filename;如果 CSS 没被处理,查样式相关 loader 是否已配置。[hash:8] 是 8 位哈希,[ext] 保留扩展名,[query] 会保留原 URL 后面的查询参数,常用于缓存参数场景。
先理解:样式里的“其他文件”指什么
为什么需要:样式文件里除了图片,还可能通过 @font-face 引入字体文件,或在某些场景下引用音频、视频等外部资源。这些资源如果不进入打包输出目录,页面上线后就可能找不到文件。
怎么想:Webpack 5 会把样式里引入的外部文件当成资源模块处理。字体文件、图片文件在 Webpack 眼里本质相似,都是“样式中使用的外部资源”。
怎么做:先按普通方式在样式里写相对路径,例如 @font-face 的 src 指向当前样式文件旁边的字体目录;先打包观察结果。
易错点:路径要相对“当前写样式的文件”来写,不要相对项目根目录或 HTML 文件乱猜。转写稿里反复强调:只参考当前写代码的文件,其他不用管。
默认行为:不配置也能打包,但输出位置不受控
为什么需要:很多同学以为字体、音视频必须装额外 loader。实际上 Webpack 5 已内置资源模块能力,字体文件在样式里被引用后通常能自动打包并生效。
怎么想:先验证“能不能打包”,再决定“要不要配置输出目录”。转写稿的调试过程是:先不配置打包,字体能打包、页面也生效,但文件直接落到 dist 根目录,和 JS、图片混在一起,不理想。
怎么做:先执行打包,打开 dist 看资源是否生成;再刷新页面确认字体是否生效。若能生效但位置乱,就进入下一步配置。
易错点:不要因为“默认能打包”就以为输出结构也符合要求;默认文件名和目录往往不符合项目规范。
配置 asset/resource:直接复制并返回 URL
为什么需要:我们希望字体等资源输出到固定目录,例如 static/media,并保留扩展名、加 hash,避免缓存和重名。
怎么想:字体文件不需要像小图片那样转 Base64,所以用 asset/resource 更合适:它把文件直接输出,并返回最终 URL。转写稿也说明:图片可能用 asset 判断大小,小图转 Base64;字体则统一直接导入。
怎么做:在 module.rules 中增加规则,test 匹配扩展名,type 写 asset/resource,generator.filename 指定输出路径和文件名。字体配置与下面“其他文件资源”的写法同理,只是把 test 换成字体扩展名。
易错点:asset/resource 不会做“小文件转 Base64”的判断;如果误用 asset,行为会不同。[ext] 表示原扩展名,[hash:8] 表示 8 位 hash,[query] 表示保留原 URL 查询参数;原来没有查询参数就不会凭空生成。
本节笔记给出的其他文件资源配置如下,可作为同一套机制的示例:
js
module: {
rules: [
{
test: /\.(mp3|mp4|avi)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
]
}调试与验证:看输出目录,也看引用路径
为什么需要:配置写完不代表一定正确,要看打包结果和页面引用是否同步更新。
怎么想:配置 generator.filename 后,Webpack 会把资源放到指定目录,并自动改写样式中的引用地址。我们不需要手工改打包后的 CSS。
怎么做:重新打包,检查 dist/static/media 下是否出现字体或其他资源;再打开页面确认资源请求路径是否正确。转写稿中字体配置后输出到 static/media,说明“这才是王道”。
易错点:不要手工去改 dist 里的引用;只要源码路径写对,Webpack 会自动处理。还要注意:笔记中的 mp3|mp4|avi 是“其他文件资源”的通用示例,转写稿也提醒样式里通常加不了视频,视频一般用 video 标签;这里重点是掌握“样式中外部资源按同一套资源模块机制处理”的方法。
为什么需要处理 HTML 文件资源
webpack 默认能处理的是 JavaScript 模块依赖,但 HTML 文件不能像 CSS、Less 那样直接当成 JS 模块导入。本节的目标不是“把 HTML 导入 JS”,而是:以 public/index.html 为模板,生成最终输出目录中的 HTML 文件,并让插件自动把打包后的 JS 引入进去。
也就是说,HTML 的处理思路和样式资源不同:样式资源通常要经过 loader 转成 webpack 能识别的模块;HTML 则借助 HtmlWebpackPlugin 插件完成模板复制、目标文件生成和脚本自动注入。
安装插件
先安装 html-webpack-plugin,它是开发依赖:
bash
npm i html-webpack-plugin -D这里注意区分 loader 和 plugin:loader 主要处理模块转换,plugin 用来扩展 webpack 的构建流程。HtmlWebpackPlugin 属于插件,所以不是写到 module.rules 里,而是写到 plugins 数组里。
配置思路:先导入,再 new 实例
插件需要先在配置文件中 require 进来,再通过 new HtmlWebpackPlugin({...}) 创建实例,放进 plugins 数组。转写稿里特别提醒过:plugins 是数组,因为一个项目里可能使用多个插件;插件配置写错时,webpack 可能直接报“无效的配置对象”,所以要检查 plugins 拼写和数组结构。
按本节笔记,配置如下:
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
...
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
template: path.resolve(__dirname, "public/index.html"),
// 目标文件地址,目标文件会在输出目录的里面
filename: 'page/[index]-[hash].html'
// 注意:输出的模板页面会默认引入打包后的JS文件
// inject:false,// 不会引入JS文件
inject: "body",// 在body尾部引入JS文件
hash:true,// 为JS文件增加后缀,可以去除缓存
minify: {
removeAttributeQuotes:true,// 移除双引号
removeComments:true,// 移除注释
collapseWhitespace:true// 代码进行折叠
}
}),
...
],
}注意:上面是按笔记保留的配置片段。实际写配置时,filename 这一行后面要补逗号,否则会引发语法或配置错误;这是手写配置时很容易漏掉的点。
各配置项怎么理解
template 指定模板文件。这里用 path.resolve(__dirname, "public/index.html"),表示以 public/index.html 为模板生成最终 HTML。
filename 指定目标文件地址,并且目标文件会放在输出目录里面。笔记示例写的是 page/[index]-[hash].html,表示生成到输出目录下的 page 目录中。实际使用时要留意模板占位符写法,按插件支持的方式来写。
inject 控制是否以及在哪里自动引入打包后的 JS。默认会自动引入;inject: false 表示不引入,inject: "body" 表示在 body 尾部引入。转写稿也解释了:脚本放 body 尾部是常见习惯,避免阻塞页面结构渲染。
hash: true 会给引入的 JS 地址增加后缀,用于减少浏览器缓存带来的问题。转写稿里提到,文件变化后地址参数变化,浏览器就不容易继续用旧缓存。
minify 用于压缩 HTML:removeAttributeQuotes 移除能移除的双引号,removeComments 移除注释,collapseWhitespace 折叠空白。目的都是减小文件体积。
HTML 文件注意事项
最重要的一点:不要在 HTML 中手动写 <script> 引入打包后的 JS 文件。HtmlWebpackPlugin 会自动引入。如果你自己再写一遍,就可能重复引入。
模板文件 public/index.html 只是“模板”,最终生成结果在输出目录中。构建后应检查输出目录里的 HTML 是否生成、script 是否被自动插入,以及路径是否与输出目录一致。
运行与验证
运行:
js
npx webpack构建后重点看两处:一是输出目录中是否生成了配置的 HTML 文件;二是打开生成的 HTML,确认插件自动引入了打包后的 JS。若配置了 inject: "body",脚本应在 body 尾部;若配置 inject: false,则不会自动引入。调试时先检查 plugins 是否拼写正确、插件是否 new、template 路径是否存在,再检查各配置项之间的逗号和大括号是否完整。
先分清:HTML 里的资源为什么要单独处理
webpack 默认会从入口 JS 出发分析依赖:JS 里 import 的资源、CSS 里 url() 引用的图片和字体,都能进入依赖图。但 HTML 里直接写 <img src="...">、<video src="...">、<audio src="..."> 时,这些地址不是 JS 模块导入,webpack 默认不会主动收集。转写稿视频3里也专门提醒过:HTML 中写 img 标签引入图片,和样式里写背景图,“这两种情况能区分吗?不一样的”。所以本节笔记要解决的是:让 webpack 能处理 HTML 文件内容中所使用的图片、视频、音频等资源。
怎么想?把 .html 也纳入 module.rules,交给 html-loader 解析。它负责打包 HTML 中所使用的图片、视频、音频等资源。怎么做?先安装:
bash
npm i html-loader -D易错点:html-loader 不是 html-webpack-plugin。转写稿视频5里说“这个还用 loader 吗?不用 loader……直接把 HTML 打包到 dist,同时自动引入 JS”,讲的是 HTML 文件生成与注入,属于插件职责;本节笔记只配置 html-loader,两者不要混为一谈。
最小配置怎么落地
安装后,在 webpack.config.js 的 module.rules 中加规则。笔记给出的配置是:
js
module: {
rules: [
{
// 导入 html 文件内容所使用的的资源,如图片、音视频等
test: /\.html$/i,
use: ["html-loader"],
},
],
},为什么这样写?test 用正则匹配 .html 文件,/i 表示忽略大小写;use 告诉 webpack 命中后使用 html-loader。转写稿里反复强调 loader 配置先看 module.rules,规则是数组,因为“规则多的去了”,每条规则是对象;use 也是数组,因为可能不止一个 loader。这里虽然只有一个,仍按数组写。
怎么做?把规则放进 module.rules,确认 html-loader 已装到开发依赖。运行:
bash
npx webpack易错点:只安装不配置,等于没生效;test 写成 /\.html/ 少了 $ 可能匹配过宽;配置对象拼写错误会导致“无效的配置对象”。转写稿视频5就出现过 plugin 少写 s 的调试过程——配置项一个字母错了,整个打包就报错,所以先看报错定位,再检查 module、rules、test、use 是否写对。
HTML 标签中的图片、视频、音频如何被收集
怎么想?HTML 里的资源地址是写在标签属性里的,html-loader 的职责就是解析 HTML 内容,把其中引用的资源交给 webpack 处理。笔记明确说它“负责打包 html 中所使用的图片、视频、音频等”。所以做法不是去 JS 里逐个 import 图片,而是在 HTML 中正常写标签,并保证路径相对于当前 HTML 文件正确。
怎么做?在 HTML 中写 <img>、<video>、<audio> 等标签,引用项目里的资源;配置好规则后运行 npx webpack,让资源进入构建流程。验证时看输出目录中资源是否被产出,以及最终页面引用是否指向构建后的资源。
易错点:第一,路径别凭感觉写。转写稿视频4讲字体时强调“你就参考你当前写的这个文件”,HTML 里的相对路径也要以当前 HTML 文件为基准。第二,不要以为 html-loader 会自动生成最终 HTML 页面;它处理的是 HTML 内容中的资源引用。转写稿视频5中 html-webpack-plugin 的 template、filename、inject、minify 等,是另一条职责线,不能拿来解释 html-loader。第三,HTML 文件本身通常不作为 JS 模块导入,别把“处理 HTML 内资源”和“把 HTML 当 JS 模块打包”混掉。
运行与调试:先让规则命中,再看资源产出
运行 npx webpack 后,如果资源没被处理,按顺序排查:html-loader 是否安装;module.rules 是否写在正确层级;test 是否命中 .html;HTML 中资源路径是否写错;是否真的执行了 webpack。转写稿里处理 less 时也是这个思路:先打包看报错,报错说明缺少对应 loader,再安装、再配置;配置改完重新打包验证。这里同理,不要一上来怀疑 loader 功能,先确认规则有没有命中。
最后记住边界:本节笔记的核心只有 html-loader 的安装、介绍、配置和 npx webpack 验证。转写稿视频5里的 HTML 文件输出、自动注入 JS、inject: "body"、minify 去引号/注释、filename 加 hash 等,是 html-webpack-plugin 的知识,可作为后续章节理解,但不要写进本节 html-loader 的配置里。
先说明本节范围
本节笔记标题写作“处理 JS 资源”,但转写稿实际讲的是:CSS、Less、样式中的图片、字体文件以及 HTML 资源的处理。下面严格按转写稿整理,不扩展到未讲内容;笔记中未附图片,因此不复现图片。转写稿里的同音字按上下文纠正为 loader、style-loader、css-loader、less-loader、html-webpack-plugin、asset/resource、base64、hash、inject、minify 等。
处理 CSS:为什么要两个 loader
为什么需要:Webpack 默认把入口 JS 当模块处理,CSS 不是 JS,直接 import './xxx.css' 会报错。
怎么想:要让 CSS 先成为 Webpack 能识别的模块,再让样式真正进入页面。转写稿明确说:css-loader 负责把 CSS 编译成 Webpack 能识别的模块;style-loader 负责动态创建 style 标签,把 CSS 内容放进页面。
怎么做:安装两个开发依赖,在 module.rules 里写规则:test 匹配 .css 结尾文件,use 写 ['style-loader', 'css-loader']。
易错点:use 数组执行顺序是从右向左。先让 css-loader 把 CSS 变成模块,再由 style-loader 创建标签,所以 style-loader 写在左边、css-loader 写在右边。转写稿还提醒:test 里的点最好转义;不转义有时也能跑,但正规写法要转义。
js
// 按转写稿字段整理,未补未讲字段
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
}处理 Less:为什么比 CSS 多一个 loader
为什么需要:项目里可能写 Less,但浏览器和 Webpack 不能直接识别 Less。
怎么想:先由 less 把 Less 编译成 CSS,再由 css-loader 变成模块,最后由 style-loader 让样式生效。转写稿强调 less-loader 负责导入/衔接 Less 文件,less 本身负责编译。
怎么做:安装 less 和 less-loader,规则中 test 匹配 .less,use 写成 ['style-loader', 'css-loader', 'less-loader']。
易错点:less-loader 要放在最右边,因为执行从右往左:先编译成 CSS,再走 CSS 的两个 loader。转写稿还提到新版安装 less-loader 可能自动带上 less,但文档要求装两个时,按文档来更稳。导入 Less 时扩展名不要省。
js
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }样式中的图片:小图转 base64,大图保留地址
为什么需要:CSS 背景图也是独立资源,默认打包后可能全堆在输出目录里;小图若单独请求会增加一次网络请求。
怎么想:Webpack 5 已内置资源模块,不必再装 file-loader、url-loader。小图转成 base64 直接写进 CSS,减少请求;大图仍输出文件,避免 CSS 过大。转写稿用 20KB 作阈值演示。
怎么做:规则匹配图片格式,type: 'asset',用 parser.dataUrlCondition.maxSize 控制阈值,用 generator.filename 指定输出目录和文件名。
易错点:阈值不要过大,否则 CSS 臃肿;filename 里的 [hash]、[ext]、[query] 要理解含义。转写稿演示了 query 会保留原地址后面的查询参数。
js
{
test: /\.(png|jpe?g|gif|webp)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 20 * 1024 } },
generator: { filename: 'static/images/[hash:8][ext][query]' }
}字体文件:和图片同一套资源机制
为什么需要:Web 字体是独立文件,CSS 中 @font-face 引用的字体也要被打包。
怎么想:转写稿指出,在 Webpack 眼里,样式中引用的图片和字体本质都是“样式中的其他资源”,可采用同一套资源模块机制;区别是图片可能需要小图 base64,字体一般直接导入。
怎么做:匹配字体扩展名,使用 type: 'asset/resource',再用 generator.filename 指定 static/fonts/...。
易错点:CSS 里写的相对路径要相对于当前 CSS/Less 文件,不要按打包后的目录去想;路径写对后,打包位置变化时 Webpack 会自动改引用。字体规则不需要 dataUrlCondition。
js
{
test: /\.(ttf|woff2?|eot|svg)$/,
type: 'asset/resource',
generator: { filename: 'static/fonts/[hash:12][ext][query]' }
}处理 HTML:插件负责模板、注入与压缩
为什么需要:HTML 不应像 CSS 那样被 import 进 JS;它要作为模板输出到 dist,并自动引入打包后的 JS。
怎么想:用 html-webpack-plugin:指定模板、输出文件名、是否注入 JS、注入位置、压缩选项。转写稿演示了 inject: 'body' 把脚本放到 body 最后;inject: false 则不自动引入。
怎么做:安装插件,在配置中 require 后 new HtmlWebpackPlugin({ template, filename, inject, minify })。minify 可移除注释、能去则去属性引号、折叠空白。
易错点:plugins 是数组;插件要先导入再 new;配置字段拼写错误会报“无效配置对象”。转写稿还演示了输出 JS 文件名加 hash 以避免缓存,以及把 JS 输出到 static/js 后,HTML 会自动更新引用路径。
js
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
inject: 'body',
minify: { removeComments: true, removeAttributeQuotes: true, collapseWhitespace: true }
})
]小结
这一节的调试主线是:先让 Webpack 识别资源,再控制资源输出形式,最后让 HTML 自动引用结果。CSS/Less 关注 loader 顺序;图片关注 asset 与阈值;字体关注 asset/resource;HTML 关注插件配置。遇到报错时,转写稿反复建议先看错误信息,再对照文档字段,不要凭记忆硬写。
课堂画面





代码示例
示例 1:webpack.config.js —— 处理样式、图片、字体、媒体与 HTML
js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "dist"),
// JS 也输出到 static 下,和图片、字体保持一致的目录风格
filename: "static/js/[name]-[hash:8].js",
clean: true,
},
module: {
rules: [
// 1. CSS:css-loader 编译成模块,style-loader 动态创建 style 标签
{
test: /\.css$/,
use: ["style-loader", "css-loader"], // 执行顺序:从右向左
},
// 2. LESS:less-loader 编译 -> css-loader -> style-loader
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
// 3. 样式中的图片:小图转 base64,大图输出单独文件
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 20 * 1024, // 小于 20KB 转 data URI
},
},
generator: {
filename: "static/images/[hash:8][ext][query]",
},
},
// 4. 样式中的字体文件:直接输出文件
{
test: /\.(ttf|woff2?|woff|svg|eot)$/,
type: "asset/resource",
generator: {
filename: "static/fonts/[hash:8][ext][query]",
},
},
// 5. 样式中的其他媒体资源
{
test: /\.(mp3|mp4|avi)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
// 6. HTML 中使用的图片、视频、音频等
{
test: /\.html$/i,
use: ["html-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
template: path.resolve(__dirname, "public/index.html"),
// 目标文件地址,会在输出目录 dist 里面
filename: "page/[index]-[hash].html",
// 注意:输出的模板页面会默认引入打包后的 JS 文件
// inject: false, // 不会引入 JS 文件
inject: "body", // 在 body 尾部引入 JS 文件
hash: true, // 为 JS 文件增加后缀,去除缓存
minify: {
removeAttributeQuotes: true, // 移除双引号
removeComments: true, // 移除注释
collapseWhitespace: true, // 代码折叠
},
}),
],
mode: "development",
};要点与易错点:
- loader 顺序是从右向左执行:
use: ["style-loader", "css-loader"]先执行css-loader(把 CSS 编译成 webpack 能识别的模块),再执行style-loader(动态创建<style>标签塞进<head>)。LESS 多一个less-loader,它必须写在最右边,先编译成 CSS。 asset与asset/resource的区别:asset会在 data URI 和单独文件之间自动选择(靠parser.dataUrlCondition.maxSize判断);asset/resource永远发送单独文件(相当于以前的file-loader)。字体不需要 base64,所以用asset/resource。[hash:8][ext][query]:[hash:8]是 8 位哈希,[ext]保留原扩展名,[query]保留原地址中的查询参数(如?7)。写错占位符会直接导致文件名不符合预期。- HTML 里不要手写
<script src="...">:HtmlWebpackPlugin会自动注入打包后的 JS;写死路径反而会因为[hash]变化而 404。 filename: "page/[index]-[hash].html"是笔记中的写法,[index]并非标准占位符,实际使用若不生效可改为"page/index-[hash].html"。- 需要安装的包:
style-loader css-loader less less-loader html-webpack-plugin html-loader(均用-D装到开发依赖)。
示例 2:src/less/app.less —— 在样式中使用字体文件与图片资源
less
/* 引入字体文件资源 */
@font-face {
font-family: "FZSJ";
src: url("../fonts/FZSJ-WSMQSJW.woff2") format("woff2"),
url("../fonts/FZSJ-WSMQSJW.woff") format("woff"),
url("../fonts/FZSJ-WSMQSJW.ttf") format("truetype"),
url("../fonts/FZSJ-WSMQSJW.eot") format("embedded-opentype"),
url("../fonts/FZSJ-WSMQSJW.svg") format("svg");
font-weight: normal;
font-style: normal;
}
@color: #e4393c;
@width: 400;
.books {
width: @width;
height: (@width / 5); // LESS 除法要加小括号
background: @color;
font-family: "FZSJ";
margin: 20px;
}
/* 样式中的背景图,路径基于当前 less 文件所在位置 */
.p1 {
width: 400px;
height: 200px;
background: url("../images/webp.webp");
}
.p2 {
width: 400px;
height: 200px;
background: url("../images/bg01.jpg?7"); // 带 query,配置里用 [query] 保留
}要点与易错点:
- 路径是相对“当前写代码的文件”,不是相对
dist。app.less在src/less/下,字体在src/fonts/,所以写../fonts/xxx.woff2。写错路径 webpack 会直接报“找不到模块”。 @font-face中多个src用逗号分隔,浏览器按支持的格式挑一个;format(...)不能漏。- 图片和字体在 webpack 眼里是一回事:都是“样式中引用的外部文件”,同一套
asset机制处理;区别只在于图片配了maxSize做 base64,字体用asset/resource直接输出。 - 小图(小于 20KB)会被转成
data:image/...;base64,xxxx直接写进 CSS,好处是少一次网络请求,坏处是 CSS 体积变大,所以不要把大图也设成转 base64。 - LESS 除法要加括号
(@width / 5),否则按新语法可能不生效。 - 样式中的文件资源 webpack5 默认也能打包,但默认会全部平铺到
dist根目录,目录很乱,所以要用generator.filename指定输出目录。
示例 3:src/app.js 与 public/index.html —— 入口与 HTML 模板
js
// src/app.js
import "./less/app.less";
import "./css/style.css";
console.log("webpack 处理样式资源");html
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack 资源处理</title>
</head>
<body>
<!-- 这里的 script 不要手写,HtmlWebpackPlugin 会自动注入 -->
<div class="books">字体测试 FZSJ</div>
<p class="p1"></p>
<p class="p2"></p>
<!-- html-loader 会处理标签里的图片、视频、音频 -->
<img src="../src/images/bg01.jpg" alt="bg" />
</body>
</html>要点与易错点:
- 入口 JS 只负责
import样式:import "./less/app.less"不需要起变量名,扩展名不能省略。LESS 一般只需要在入口导入一个文件,其余样式在 LESS 内部继续@import。 - HTML 模板中不要手动引入打包后的 JS,
HtmlWebpackPlugin会自动注入;如果设inject: false就完全不会引入,页面自然跑不起来。 inject: "body"表示把<script>放到body尾部;hash: true会给 JS 地址加?xxxxxxxx随机后缀,防止浏览器缓存旧文件。minify三项分别做:去属性双引号、去注释、折叠空白,目的是减小 HTML 体积;注意带逗号等特殊字符的属性值不会被去引号。html-loader只负责 HTML 标签里引用的图片/视频/音频;HTML 本身不需要被 import 到 JS 里,它是通过HtmlWebpackPlugin的template指定的。- 运行打包命令:
npx webpack,产物在dist目录,JS、图片、字体分别落在static/js、static/images、static/fonts下,HTML 里的引用路径由插件自动改好。
常见错误
1. loader 数组顺序写反,样式不生效甚至报错
- 现象:
import "./css/app.css"后打包成功,但页面没有任何样式;或报Module parse failed/You may need an appropriate loader。 - 原因:
use数组的执行顺序是从右向左(内部按 pop 取),css-loader必须先于style-loader执行;写成["css-loader", "style-loader"]就等于先创建 style 标签再解析 CSS,链路断裂。 - 修法:
use: ["style-loader", "css-loader"];less/sass 场景最右侧放预处理器 loader:["style-loader", "css-loader", "less-loader"]。改完先删dist再看结果。
2. 只装了 less-loader,没装 less 本体
- 现象:打包报
Cannot find module 'less'。 - 原因:
less-loader只负责把 less 文件接入 webpack 模块系统,真正把 less 编译成 CSS 的是less编译器;某些版本会自动带上依赖,某些版本不会,导致环境不一致。 - 修法:显式安装
npm i less less-loader -D,并在package.json中锁定版本,不要依赖"碰巧被自动装上"。
3. 想让小图自动转 base64,却写了 asset/resource
- 现象:配置了
maxSize之类的体积阈值,但dist里所有图片仍然是独立文件,CSS 里依旧是 URL。 - 原因:
asset/resource等价于旧的file-loader,无条件输出单独文件;只有asset(等价url-loader)才会按parser.dataUrlCondition.maxSize在 data URI 与独立文件之间自动选择。 - 修法:图片规则改
type: "asset"并配parser: { dataUrlCondition: { maxSize: 20 * 1024 } };字体等不需要体积判断的资源保持asset/resource。
4. HTML 模板里手写了 <script src="../src/index.js">
- 现象:打包后页面 404,或脚本被执行两次(一次 404、一次正常),控制台报找不到
/src/index.js。 - 原因:
HtmlWebpackPlugin生成 HTML 时会自动注入打包后的 JS;手写的 script 指向的是源码路径,源码目录不会出现在dist中。 - 修法:模板中删掉所有 script 引用,只保留业务结构;需要调整位置用
inject: "body" | "head" | false,需要防缓存用hash: true。
5. 插件配置写错位置或拼错键名
- 现象:
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. - 原因:插件是
plugins(复数,数组,元素是new XxxPlugin()实例),不是plugin;也常被误写进module.rules(那是 loader 的位置)。 - 修法:顶层写
plugins: [new HtmlWebpackPlugin({...})],插件用require引入后再new;报错时先逐字核对plugins/rules/module三个键。
6. 改了 output.path 或 filename 想"整理目录",结果全乱
- 现象:本来只想让图片进
static/images,结果 HTML、JS、字体全都换了位置,页面白屏。 - 原因:
output是全局出口,影响所有产物;单类资源的输出位置由该规则自己的generator.filename决定。 - 修法:只改对应规则的
generator.filename(如"static/images/[hash:8][ext][query]"),不要动output.path;改完确认[ext]没漏,否则产物没有扩展名。
7. HTML 里用 <img src> 引图片,图片没被打包
- 现象:打包后图片 404,
dist里找不到该图。 - 原因:webpack 默认只解析 JS/CSS 中
import、url()这类依赖,HTML 标签属性里的路径不会被当作模块处理。 - 修法:加
html-loader规则{ test: /\.html$/i, use: ["html-loader"] };同时注意路径写法要与模板文件位置对应。
8. url() 路径写错基准,字体/图片全部 404
- 现象:CSS 编译通过,但浏览器控制台一堆 404,字体不生效。
- 原因:
url()的相对路径以当前这个 CSS/less 文件所在目录为基准,不是以 HTML 或项目根目录为基准;常按 HTML 的位置去写。 - 修法:先确定当前文件所在目录,再写
../fonts/xxx.woff2;打包后不必关心产物路径,插件会自动重写引用。
练习
练习 1(loader 链排错) 给出配置 use: ["css-loader", "less-loader", "style-loader"],说明打包后页面会发生什么,并写出正确顺序及每一步的职责边界(只写职责,不写实现)。
练习 2(资源类型选择) 项目里有 6 张图(2 张 < 20KB,4 张 > 100KB)、3 个字体文件、1 段 mp4。请为这三类资源分别选择 type,并说明为什么字体不套用图片的方案;写出对应的 generator.filename 目录规划。
练习 3(HTML 链路完整搭建) 从零把 public/index.html 作为模板打包到 dist/page/,要求:JS 注入到 body 尾部、文件名带 hash 防缓存、去掉注释与多余引号、压缩折叠。列出需要安装的包、配置要点,以及模板中必须删掉的内容。
练习 4(产物目录整理) 在不修改 output.path 的前提下,让 JS、图片、字体分别落到 static/js、static/images、static/fonts,并验证 HTML 中的引用是否被自动修正。写出改动的字段位置和验证方法。
练习 5(区分"源码路径"与"产物路径") 某同学把 less 里的 url("../images/bg.png") 改成了 url("static/images/bg.png"),理由是"打包后图片就在这"。请判断对错,并说明正确的思考顺序应该是什么。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 处理样式资源-css | 13:51 |
| 2 | 处理样式资源-less | 11:35 |
| 3 | 处理样式中的图片资源 | 16:20 |
| 4 | 处理样式中的字体文件资源 | 09:58 |
| 5 | 处理HTML资源 | 17:57 |
小结
- webpack 眼里没有"图片/字体/视频",只有"模块"。样式里
url()引用的任何外部文件走的是同一套机制,区别仅在于是否做体积判断(assetvsasset/resource)。 - loader 链就是加工工序:数组从右往左执行,越靠右越"原始"。预处理器 →
css-loader→style-loader是固定工序,顺序错了链路必断。 - Webpack5 的 asset modules 已取代
file-loader/url-loader,不必再装这两个包;但旧写法基本仍兼容,遇到老项目不必急着重构。 - loader 处理模块内容,plugin 干预构建流程,两者配置位置、引入方式、值的类型都不同,报
Invalid configuration object时优先怀疑这个。 generator.filename管单类资源,output管全局。整理产物目录时改前者,动后者会波及所有产物。- HTML 由插件接管:模板里只写业务结构,JS/CSS 引用交给插件注入,手写 script 一定会与产物路径冲突。
- 开发者只对源码路径负责,产物路径由 webpack 反推。写
url()时盯住当前文件所在目录,打包后引用会被自动重写,不需要也不应该手动对齐dist结构。 - 开发模式与生产模式的产物形态不同:开发期样式内联进 JS(
style-loader),生产期应抽离成独立 CSS 文件——这是模式选择问题,不是配置对错问题。