Skip to content

处理样式中的图片资源与处理样式中的字体文件资源(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

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

示例 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处理样式资源-css13:51
2处理样式资源-less11:35
3处理样式中的图片资源16:20
4处理样式中的字体文件资源09:58
5处理HTML资源17:57

小结 ​

  1. webpack 眼里没有"图片/字体/视频",只有"模块"。样式里 url() 引用的任何外部文件走的是同一套机制,区别仅在于是否做体积判断(asset vs asset/resource)。
  2. loader 链就是加工工序:数组从右往左执行,越靠右越"原始"。预处理器 → css-loader → style-loader 是固定工序,顺序错了链路必断。
  3. Webpack5 的 asset modules 已取代 file-loader / url-loader,不必再装这两个包;但旧写法基本仍兼容,遇到老项目不必急着重构。
  4. loader 处理模块内容,plugin 干预构建流程,两者配置位置、引入方式、值的类型都不同,报 Invalid configuration object 时优先怀疑这个。
  5. generator.filename 管单类资源,output 管全局。整理产物目录时改前者,动后者会波及所有产物。
  6. HTML 由插件接管:模板里只写业务结构,JS/CSS 引用交给插件注入,手写 script 一定会与产物路径冲突。
  7. 开发者只对源码路径负责,产物路径由 webpack 反推。写 url() 时盯住当前文件所在目录,打包后引用会被自动重写,不需要也不应该手动对齐 dist 结构。
  8. 开发模式与生产模式的产物形态不同:开发期样式内联进 JS(style-loader),生产期应抽离成独立 CSS 文件——这是模式选择问题,不是配置对错问题。