Skip to content

webpack(二) ​

本页对应 webpack,共 6 段视频,约 50 分钟。

学习目标 ​

  • 能配置 babel-loader、@babel/core 和 @babel/preset-env,并通过打包结果验证箭头函数、let/const 等高版本语法被降级转换。
  • 能使用 npm 安装并引入 axios,在注册按钮点击事件中提交用户名和密码,并根据响应或错误对象展示服务器返回的提示信息。
  • 能安装并配置 webpack-dev-server,在 package.json 中添加开发启动命令,验证修改入口文件或样式文件后页面自动重新打包并更新。
  • 能通过 --mode 分别设置 development 和 production 打包模式,并对比输出结果,说明开发模式便于调试、生产模式进行压缩优化,且命令行模式优先级高于配置文件。
  • 能配置 devtool: 'inline-source-map',在浏览器控制台中定位报错对应的原始文件、行号和列号,并说明生产环境应关闭 source map。
  • 能配置 resolve.alias 将 @ 解析为 src 目录的绝对路径,并将项目内模块的 import 路径改写为以 @ 开头的别名路径且正常运行。

知识点 ​

目标:让 Webpack 能处理高版本 JS 语法 ​

本节的目标是让 Webpack 集成 Babel 编译器功能,处理我们写的 JS 代码。也就是说,入口文件里可以使用箭头函数、let/const 等 ECMAScript 2015+ 语法,经过 Webpack + Babel 打包后,能转换成向后兼容的写法,方便运行在当前和旧版本浏览器或其他环境中。

为什么需要:Webpack 本身主要负责模块打包、资源整合,不会自动把所有高版本 JS 语法降级。实际项目里如果直接写箭头函数、let/const,旧环境可能不认识,所以需要 Babel 参与转译。
怎么想:Babel 负责“翻译语法”,Webpack 负责“组织打包”,两者之间用 babel-loader 连接。
怎么做:先写一段待降级代码,再安装 Babel 相关包,最后在 webpack.config.js 的 module.rules 里配置 loader。
易错点:不要把 Babel 当成打包工具;真正让 Webpack 调用 Babel 的是 babel-loader。

Babel 和 babel-loader 是什么 ​

Babel 是一个 JavaScript 语法编译器,将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。babel-loader 的作用是让 Webpack 可以使用 Babel 转译 JavaScript 代码。

为什么需要:只安装 Babel 核心还不够,Webpack 需要 loader 才能把某类文件交给对应工具处理。
怎么想:看到 .js / .mjs 文件,就交给 babel-loader;babel-loader 再按 @babel/preset-env 的规则转译。
怎么做:安装 babel-loader、@babel/core、@babel/preset-env,然后在 Webpack 配置中增加规则。
易错点:@babel/core 是编译器核心,@babel/preset-env 是预设规则,babel-loader 是 Webpack 的桥梁,三者不要混用。

先写一段待降级的源码 ​

在入口文件里编写一段映射数组元素、每个数值 + 1 的代码,并要求用箭头函数:

js
/**
 * 目标:让 Webpack + Babel 编译降级 JS 语法
 */
const arr = [1, 2, 3]
const result = arr.map(val => val + 1)
console.log(result)

为什么需要:先有高版本语法,才能观察 Babel 是否把它降级。
怎么想:箭头函数和 const 都是典型的新语法,打包后应能看到普通 function 和 var 等兼容写法。
怎么做:把代码放到入口文件,例如 src/main.js,然后继续安装依赖、配置 loader。
易错点:转写稿演示时提醒,打包后变量名可能被 Webpack 混淆成短名字,例如看到类似 Y 的变量,不要误以为代码坏了;它不影响浏览器运行。

安装 Babel 相关依赖 ​

下载 Babel、babel-loader、core 本地软件包:

bash
npm i babel-loader @babel/core @babel/preset-env -D

为什么需要:项目要使用这些本地软件包,才能让 Webpack 调用 Babel。
怎么想:这些包只在开发、构建阶段使用,所以加 -D,等价于 --save-dev,安装到开发依赖。
怎么做:在项目根目录打开集成终端执行命令。转写稿里也提醒,终端粘贴可用右键粘贴,不要按错快捷键。
易错点:包名写错会导致安装失败;@babel/preset-env 前面的 @babel/ 不能漏。

配置 webpack.config.js ​

配置 webpack.config.js,让 Webpack 拥有 Babel 转译功能:

js
// ...

module.exports = {
  // ...
  module: { // 加载器
    rules: [ // 规则列表
      // ...
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/, // 排除指定目录里的 js (不进行编译降级)
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'] // 预设规则
          }
        }
      }
    ],
  }
};

为什么需要:Webpack 要知道“哪些文件用哪个 loader、用什么规则”。
怎么想:test: /\.m?js$/ 匹配 .js 和 .mjs;exclude 排除 node_modules、bower_components 里的 JS,避免把我们下载的包也重新编译降级;use.loader 指定 babel-loader;options.presets 使用 @babel/preset-env 预设规则。
怎么做:把规则对象放进 module.rules 数组里,不要放到 plugins 或其他位置。
易错点:exclude 很重要,不排除 node_modules 会拖慢构建甚至引入不必要转译;presets 是数组,不要写成普通字符串;规则要写在 rules 里。

打包并观察降级结果 ​

配置完成后执行打包,运行 dist/index.html 观察效果。转写稿演示时,打包后到 dist/app.js 里查看:原本用 const 定义的变量变成了 var,map 里的箭头函数被 Babel 编译成了 function。

为什么需要:只有看打包产物,才能确认 Babel 是否真正参与。
怎么想:源码里是箭头函数,打包产物里若出现普通 function,说明降级生效;变量名被压缩混淆是 Webpack 生产打包的常见表现。
怎么做:执行构建命令,打开 dist/index.html,再到输出 JS 中观察关键字变化。
易错点:不要只盯变量名,变量名可能被压缩;重点看语法结构是否被转译。若没有生效,检查 loader 规则是否匹配、依赖是否安装、配置是否保存。

三个包分别做什么 ​

模块作用
@babel/coreJs 编译器,分析代码
@babel/preset-envbabel 预设,规则
babel-loader让 webpack 翻译 js 代码

为什么需要:理解分工,排查问题时才知道该看哪一层。
怎么想:@babel/core 做分析转换,@babel/preset-env 提供规则集合,babel-loader 让 Webpack 接入 Babel。
怎么做:按“写源码 → 装包 → 配 loader → 打包观察”的顺序完成。
易错点:少装一个包、预设名写错、规则位置写错,都会导致转译不生效。

小结 ​

Babel 编译器的作用是:把 ECMAScript 2015+ 语法向后转换,兼容低版本浏览器。Webpack 集成 Babel 的关键步骤是:编写高版本语法代码;下载 babel-loader、@babel/core、@babel/preset-env;在 webpack.config.js 的 module.rules 中配置 babel-loader 和 @babel/preset-env;最后打包并查看输出结果。

本节目标与整体思路 ​

为什么需要:前面的注册用户案例如果直接在 HTML 里用 script 引入 axios,就不适合 Webpack 工程化项目。现在项目已经由 Webpack 管理,应该用 npm 下载依赖,让 Webpack 把依赖和业务代码一起打包,最终运行 dist/index.html。
怎么想:点击注册按钮后,先收集用户名和密码,做长度校验;再用 axios 向注册接口发 POST 请求;成功和失败都根据服务器返回的 message 提示用户。
怎么做:先 npm i axios,再到 src/main.js 里 import axios from 'axios',绑定 .login-btn 点击事件,完成请求并打包验证。
易错点:不要继续用外链方式引 axios;npm 包不是直接写在 HTML 里就能用,必须经过 Webpack 打包处理后再运行输出页面。

注册用户功能图解如下:

注册用户功能图解

用 npm 安装 axios ​

为什么需要:前端要提交数据到后端,核心工具是 axios。现在项目使用 npm 管理依赖,所以应把 axios 下载到当前项目,而不是找 CDN 地址。
怎么想:转写稿中明确说“前端想要提交数据到后端,需要使用 axios;现在包管理器叫 npm,所以用 npm 把 axios 下载到当前项目中”。
怎么做:在项目根目录打开集成终端,执行:

bash
npm i axios

易错点:命令要在当前 Webpack 项目目录下执行;下载后依赖在 node_modules 中,业务代码里仍需要 import 引入,不能直接写 axios 就假设它可用。

在 src/main.js 中引入并绑定注册按钮 ​

为什么需要:axios 下载后只是项目依赖,必须进入入口文件,参与 Webpack 的模块依赖图,才会被一起打包。
怎么想:入口文件是 src/main.js,注册按钮已有 .login-btn,所以先引入 axios,再给按钮绑定点击事件,在事件处理函数里写注册逻辑。
怎么做:引入 axios:

js
import axios from 'axios'

然后绑定点击事件,获取用户名和密码:

js
document.querySelector('.login-btn').addEventListener('click', () => {
  const username = document.querySelector('.username').value
  const password = document.querySelector('.password').value
})

易错点:选择器要和页面结构一致;用户名、密码要在点击时读取,不要写在事件外面导致拿到旧值。

编写注册请求与成功/失败处理 ​

为什么需要:注册不仅是收集输入,还要按接口要求把 username、password 提交给服务器,并根据成功或失败给出提示。
怎么想:转写稿中先做长度校验,校验通过后再发请求;请求地址来自注册账号接口文档,方式是 POST,请求体用 data 传用户名和密码;成功取 result.data.message,失败取 error.response.data.message。
怎么做:完整业务代码如下:

js
/**
 * 目标:基于 axios 完成注册用户功能
 */
import axios from 'axios'
document.querySelector('.login-btn').addEventListener('click', () => {
  const username = document.querySelector('.username').value
  const password = document.querySelector('.password').value

  if (!checkUserName(username)) {
    alert('用户名长度要大于等于8位!!!')
    return
  } else if (!checkPassWord(password)) {
    alert('密码长度要求大于等于6位')
    return
  }

  console.log('用户名和密码长度符合要求')
  axios({
    url: 'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data: {
      username,
      password
    }
  }).then(result => {
    alert(result.data.message)
  }).catch(error => {
    alert(error.response.data.message)
  })
})

易错点:method 是 POST;请求体放在 data 中;username、password 与变量同名时可简写;失败提示不是 error.data.message,而是 error.response.data.message;长度校验不通过要 return,避免继续发请求。

打包并运行 dist/index.html 验证 ​

为什么需要:源码里使用了 import 和 npm 包,不能直接用浏览器打开 src/main.js 运行,必须让 Webpack 翻译、整合、打包。
怎么想:转写稿中完成代码后执行打包命令,然后到输出文件夹中运行 HTML 页面;测试成功注册,也测试重复用户名时是否展示服务器错误提示。
怎么做:执行打包命令,例如:

bash
npm run build

打包后运行 dist/index.html,输入用户名和密码点击注册,观察成功提示;再注册相同用户名,观察错误提示。
易错点:不要直接运行源码文件;要运行打包输出目录中的页面。转写稿也提到,每改一次代码都手动打包不方便,这是后续开发服务器要解决的问题。

小结:npm 包如何作用到前端项目 ​

为什么需要:理解 npm 包与 Webpack 的关系,才能知道为什么 import axios 后页面能发请求。
怎么想:axios 被 npm 下载到 node_modules,在 src/main.js 中通过 import 成为模块依赖;Webpack 打包时把它和业务代码一起处理,输出到 dist,HTML 页面再引入打包后的资源。
怎么做:记住流程:npm i axios → import axios → 写业务 → npm run build → 运行 dist/index.html。
易错点:npm 下载的包不是“自动出现在页面全局”的,它需要被 Webpack 打包处理后,再引入到 HTML 文件中运行。

为什么需要 webpack-dev-server ​

为什么需要: 在前面的注册用户案例里,代码写完要执行打包命令,再到输出目录里看页面;每改一次提示文字或样式,都要重新打包、刷新,非常麻烦。开发阶段最怕这种重复劳动。

怎么想: 需要一个工具替我们做三件事:启动本地 Web 服务、自动监听代码变化、把最新结果快速同步到浏览器。这样改完保存就能看效果,不必每次手动打包。

怎么做: 把 webpack-dev-server 作为开发依赖装进项目:

bash
npm i webpack-dev-server --save-dev

易错点: 它是开发阶段工具,不是生产部署方案;安装时用 --save-dev 更符合用途。转写稿里说的“外 ber p.d server”就是 webpack-dev-server,不要写错包名。

它到底做了什么 ​

为什么需要: 如果只知道“启动服务器”,容易误以为它会像 build 一样在 dist 里生成文件。理解它的工作方式,才能正确调试。

怎么想: 它启动一个本地 Web 服务,用 webpack 打包源码,但打包结果主要放在内存中;同时监听入口及其依赖的模块,代码变化后重新打包变化部分,并把最新结果同步到浏览器。

怎么做: 启动后终端会显示本地服务地址和端口(转写稿示例是 8080),按住 Ctrl 点击地址即可访问打包后的项目网页。

易错点: 开发服务器运行时通常看不到新的 dist 输出文件,因为它在内存里工作;真正输出到 dist 是执行打包命令时的事。热更新范围也不是“项目里任意文件”,而是从入口开始被引入的模块。

集成步骤:安装与配置命令 ​

为什么需要: 包装好后还需要一个固定命令来启动,否则每次都要记一长串参数。

怎么想: 分两步:先装包,再在 package.json 的 scripts 里加自定义命令,最后用 npm run dev 启动。

怎么做: 配置开发命令,并指定开发模式:

json
"scripts": {
  // ...
  "dev": "webpack serve --mode=development"
},

然后启动:

bash
npm run dev

易错点: 真正执行的命令是 webpack serve,不是把包名直接当命令写;--mode=development 的单词和等号不要写错。示例里的 // ... 只是省略示意,真实 package.json 不能保留这种注释。

启动与验证热更新 ​

为什么需要: 配完不代表生效,必须通过修改代码验证“保存后页面是否立即变化”。

怎么想: 启动开发服务器后,先访问终端提示的地址;再修改入口文件或入口引入的 JS/CSS,保存后不手动打包,直接看页面。

怎么做: 执行 npm run dev,打开提示的域名加端口;例如修改注册案例里用户名长度判断的提示文字,保存后直接点击注册观察;再修改样式文件里的文字颜色,保存后看页面是否更新。停止服务按 Ctrl+C。

易错点: 端口以终端提示为准;修改了没有被入口引入的文件,不一定会触发页面更新;如果保存后无变化,先看终端是否有报错,再确认修改文件是否在入口依赖链上。

开发模式与生产模式的提醒 ​

为什么需要: 开发服务器常配开发模式,正式上线则要生产模式,两者目标不同。

怎么想: 开发模式方便调试和热更新;生产模式会对代码做压缩、混淆,输出到 dist 供上线。命令行里的 --mode 优先级高于配置文件里的 mode。

怎么做: dev 命令用 --mode=development;打包上线命令可用 --mode=production。开发阶段用 npm run dev,上线前再执行正式打包。

易错点: 不要用开发服务器代替上线打包;不要把生产模式误写成别的单词。开发服务器只负责快速开发,不负责最终部署产物。

小结 ​

为什么需要: 回顾作用,避免只会敲命令却不知道它在做什么。

怎么想: 它的核心是“本地 Web 服务 + 内存打包 + 监听变化 + 快速反馈”。

怎么做: 安装 webpack-dev-server,配置 dev 命令,运行 npm run dev,访问提示地址,改代码保存验证,Ctrl+C 停止。

易错点: 包名、webpack serve 命令、开发模式拼写、内存输出和 dist 输出这几处最容易混淆。

答案
  • 启动 Webpack 开发服务器,会启动一个 Web 服务,实时检测代码变化重新打包,并快速反应最新效果到浏览器页面上

目标 ​

本节要弄清 Webpack 的“打包模式”到底是什么、有哪两种、如何设置,以及它对最终代码和环境的影响。学完后,你应当能根据“开发调试”还是“上线部署”选择不同模式,并知道命令行与配置文件同时设置时谁生效。

为什么需要打包模式 ​

为什么需要:Webpack 不只是把模块拼在一起,还会根据当前目标做不同优化。开发时我们希望调试快、能实时加载、支持模块热替换;上线时我们希望体积小、代码被压缩、资源更轻量。如果只有一种行为,就很难同时满足这两类场景。

怎么想:把“打包模式”理解成告诉 Webpack 当前处于什么阶段。阶段不同,Webpack 采用的内置优化策略就不同。

怎么做:先认识两种固定名称的模式:

模式名称模式名字特点
开发模式development调试代码,实时加载,模块热替换等
生产模式production压缩代码,资源优化,更轻量等

易错点:模式名字是 Webpack 规定好的固定单词,不能随手写成 dev、prod 之类;本节笔记中的 development 和 production 要拼写准确。

设置方式一:配置文件 mode ​

为什么需要:如果项目希望把模式写在配置里,Webpack 提供了配置文件选项,适合统一管理配置。

怎么想:在 webpack.config.js 导出的配置对象中增加 mode 选项,Webpack 读取后就会按该模式执行内置优化。

怎么做:

js
// ...

module.exports = {
  // ...
  mode: 'production'
}

易错点:这种方式不够灵活。开发阶段要写 development,上线时还要记得手动改成 production;一旦忘记切换,就可能出现“开发时按生产模式打包”或“上线时没压缩”的问题。

设置方式二:命令行 --mode ​

为什么需要:同一个项目通常既有开发命令,也有上线构建命令。把模式放到命令行参数里,可以针对不同命令使用不同模式,更灵活。

怎么想:在 package.json 的 scripts 中,为开发命令和构建命令分别追加 --mode=...。这样执行不同脚本时,Webpack 自然使用不同模式。

怎么做:

json
"scripts": {
  "build": "webpack --mode=production",
  "dev": "webpack serve --mode=development"
}

易错点:--mode 是命令行参数,等号两边不要随意加空格;webpack serve 对应开发服务器场景,webpack 对应构建输出场景,命令不要写混。

优先级与推荐做法 ​

为什么需要:如果配置文件和命令行都写了 mode,必须明确谁说了算,否则容易出现“我明明改了配置却没生效”的困惑。

怎么想:命令行参数更靠近本次执行命令,因此优先级更高。

怎么做:记住结论:命令行设置的优先级高于配置文件中的,推荐用命令行设置。这样 npm run dev 用开发模式,npm run build 用生产模式,切换成本低。

易错点:不要同时在两处写不同模式还依赖配置文件生效;排查模式问题时,先看实际执行的脚本命令里有没有 --mode。

观察差异:build 与 dev 的输出 ​

为什么需要:只看概念容易混,实际打包一次就能看出生产模式和开发模式的目标不同。

怎么想:生产模式面向线上用户,重点是体积更小、打开更快,所以会压缩、混淆;开发模式面向开发者,重点是调试和热更新,所以不会做极致压缩。

怎么做:在转写稿演示中,执行 npm run build 后打开 dist 下的输出文件,会发现代码被压缩成一行;VS Code 因自动换行看起来像多行,实际是一行,变量名也被混淆。再把模式改为 development 打包,输出内容没有被极致压缩,还能看到 src/main.js 以及 utils/check.js 等模块范围。开发服务器热更新时,也只针对发生变化的模块处理并同步到页面。

易错点:不要拿生产模式的压缩结果去逐行阅读调试;开发模式也不等于“完全不打包”,它只是优化目标不同。上线时应使用 production,开发调试时使用 development。

小结 ​

两种模式的区别是:开发模式注重代码热替换更快,让开发调试代码更便捷;生产模式注重项目体积更小、更轻量,适配不同浏览器环境。设置时既可在 webpack.config.js 中写 mode,也可在 package.json 命令行加 --mode;命令行优先级更高,推荐用命令行区分开发与上线。

目标:在开发环境精准定位报错源码位置 ​

这一节要解决的问题是:代码经过 Webpack 打包后,浏览器控制台里的报错还能不能准确指回我们写的源代码。答案是可以,但需要开启 source map。source map 可以准确追踪 error 和 warning 在原始代码中的位置。这里的“原始代码位置”,具体就是报错或警告所在的行数和列数。

为什么需要 source map ​

为什么需要:开发时我们写的是 src 下的源码,但浏览器实际运行的是 Webpack 打包、压缩、混淆后的 JS 文件。代码一旦被压缩和混淆,控制台里看到的报错位置往往对应打包后的文件,无法正确定位到源代码的行数和列数。

怎么想:先承认问题存在——不是代码没有报错,而是报错位置“翻译”不回源码。转写稿里的演示思路是:先在源码中故意制造一个错误,例如把 console 相关单词写错,然后启动开发服务器,打开浏览器开发者工具观察报错。此时报错会指向打包后的 JS 文件,代码被压缩混淆后,很难直接看出原始位置。代码少时还能猜,文件多、代码多时就很难定位。

怎么做:使用 Webpack 提供的 devtool 配置项,选择一种 source map 模式。本节采用 inline-source-map。

易错点:不要以为 source map 会“修复”错误,它只负责记录并映射源码位置;真正要改的还是源代码里的拼写或逻辑问题。

怎么想:用 devtool 记录源码位置 ​

为什么需要:Webpack 需要知道以什么方式生成源码位置信息,所以要通过固定配置项 devtool 告诉它。

怎么想:devtool 需要设置一种 source map 模式,模式名是固定的,不能随意拼写。本节使用 inline-source-map。它的作用是把源码的位置信息一起打包在 JS 文件内,也就是把编写代码时每一行、每一列的位置信息记录进去。

怎么做:在 webpack.config.js 的导出配置对象中增加 devtool:

js
// ...

module.exports = {
  // ...
  devtool: 'inline-source-map'
}

易错点:devtool 是 Webpack 配置项,不是业务代码里的变量;inline-source-map 的单词要写完整,不能写成普通字符串以外的形式。修改配置后,开发服务器通常需要重新启动,才能读取新的配置。

怎么做:配置后观察调试效果 ​

为什么需要:只看配置不够,要在真实报错中验证 source map 是否生效。

怎么想:转写稿的调试过程是:先在 src 源码里故意写错一个单词,启动 Webpack 开发服务器,打开页面并按 F12 查看控制台。未配置 source map 时,报错路径指向打包后的 JS 文件,虽然控制台可能给出打包文件的行列号,但和源码对不上,代码一多就很难找。配置 devtool: 'inline-source-map' 后,重新启动开发服务器,再刷新页面,报错信息会指向原始源码文件,例如转写中提到的 main.js 的原始行号和列号;点击报错位置可以看到对应源码,再回到代码区按行数定位,就能发现是单词多写了一个字母之类的拼写问题。

怎么做:按“制造错误 → 启动开发服务器 → 打开控制台 → 配置 devtool → 重启并刷新 → 点击报错定位源码”的顺序验证。

易错点:不要只看终端是否打包成功,要打开浏览器控制台看报错路径;不要在生产环境保留 source map。转写稿明确提醒:source map 一般用于开发环境调测,生产上线环境不建议使用,防止别人查看 error 和 warning 时直接点击就看到源码位置;生产环境应把 devtool 关掉。

小结:开发调错用 source map,生产要关闭 ​

本节的核心结论是:Webpack 打包会压缩和混淆代码,所以控制台可能无法准确定位源码位置;在开发环境通过 devtool: 'inline-source-map' 记录源码位置信息,可以准确追踪 error 和 warning 的原始行列位置。它适合开发调试,不适合生产环境。回答小结问题:为何打包后控制台无法准确定位源码位置?因为 Webpack 把代码压缩和混淆了。

为什么需要解析别名 ​

当项目里的模块越来越多,import 或 require 引入目标模块时,常常要写一长串相对路径,例如从当前文件一层层回到 src,再进入 utils:

js
import { checkUsername,  checkPassword } from '../src/utils/check.js'

这样写有两个直接问题:一是路径长、容易写错;二是以后目录调整或文件移动时,很多引入路径都要跟着改。Webpack 提供的解析别名,就是让我们给常用目录起一个短名字,引入时用短名字代替长路径。笔记中给出的目标很明确:设置 Webpack 如何设置路径别名,方便我们引入目标模块。

怎么想:把常用目录映射成短前缀 ​

解析别名的核心思路不是“改变文件真实位置”,而是告诉 Webpack:打包解析模块时,遇到某个前缀,就把它替换成配置好的绝对路径。笔记中引用了 Webpack 的 resolve.alias:配置模块如何解析,创建 import 或 require 的别名,来确保模块引入变得更简单。

因此我们可以这样想:项目源代码大多在 src 下,那么把 src 映射成 @,以后写 @/utils/check.js 就等价于从 src 往下找 utils/check.js;也可以把 src/utils 单独映射成 MyUtils,以后写 MyUtils/check.js。别名名字可以自定义,但它最终代表哪个目录,必须由 path.resolve 明确指定。

怎么做:在 webpack.config.js 中配置 resolve.alias ​

在 webpack.config.js 导出的配置对象中,增加 resolve.alias。笔记中的配置代码如下:

js
// ...

module.exports = {
  // ...
  resolve: {
    alias: {
      MyUtils: path.resolve(__dirname, 'src/utils'),
      '@': path.resolve(__dirname, 'src')
    }
  }
}

这里的 resolve 是 Webpack 固定的配置项,表示解析相关配置;alias 是别名配置对象。path.resolve(__dirname, 'src/utils') 表示以当前配置文件所在目录为基准,拼出 src/utils 的绝对路径。@ 则指向 src 的绝对路径。

配置完成后,原来较长的引入路径就可以简化。笔记中给出的写法是:

js
import { checkUsername,  checkPassword } from 'MyUtils/check.js'
import { checkUsername,  checkPassword } from '@/utils/check.js'

修改代码路径后,要重新打包或重启开发服务器观察效果是否正常。转写稿中的调试过程是:改完 import 路径后,重新启动 Webpack 开发服务器,访问本地服务地址,检查页面样式、JS 代码以及 utils 下校验工具函数是否都正常。若页面和功能都正常,说明别名解析生效。

易错点与调试提醒 ​

第一,@ 不是 Webpack 天生固定代表 src,它代表什么完全取决于 alias 里怎么配置。笔记小结也强调:看在 Webpack 配置中的别名路径是什么,就会在打包时替换成哪个路径使用。

第二,path.resolve(__dirname, 'src') 中的 src 要写对。__dirname 是当前配置文件所在文件夹的绝对地址,后面拼 src 才能指向项目源码目录;不要误写成其他层级。

第三,别名主要替换我们自己创建的模块路径。像从 NPM 下载的包,仍然直接写包名即可,不需要也不应该硬套 @。转写稿也特别提醒:只有自己创建定义的模块文件需要写路径,内置模块或 NPM 管理的模块直接写模块名。

第四,改完 webpack.config.js 后,开发服务器通常需要重新启动才能读取新配置。转写稿中就是先终止开发服务器,再重新启动,然后访问页面验证。不要只改配置不重启,就误判“别名没生效”。

第五,别名要和实际目录结构对应。写 @/utils/check.js 时,要确认 src/utils/check.js 真实存在;写 MyUtils/check.js 时,要确认 MyUtils 已映射到 src/utils。

小结 ​

路径中的 @ 符号代表什么意思?答案不是固定的,而是看 Webpack 配置中的别名路径是什么,就会在打包时替换成哪个路径使用。本节推荐把 @ 配成 src 的绝对路径,这样从 src 往下找资源时,引入路径更短、更清晰,也更方便后续维护。

课件图示 ​

课件第18页:source map:可以准确追踪error 和warning 在原始代码的位置

课件第15页:案例 注册用户- 完成功能 需求:点击注册按钮,提交用户名和密码到服务器完成注册

课件第14页:babel 定义:是一个JavaScript 语法编译器,将采用ECMAScri

课件第16页:webpack-dev-server:快速开发应用程序 作用:启动Web 服务,

课件第17页:打包模式:告知Webpack 使用相应模式的内置优化 分类: 设置: 方式1:在

课件第9页:案例 注册用户- 长度判断 需求:点击注册按钮,判断用户名和密码长度是否符合要求

课件第12页:加载器less-loader:把less 代码编译为css 代码,还需要依赖le

课件第3页:定义: 静态模块:指的是编写代码过程中的,html,css,js,图片等固定内容

代码示例 ​

示例一:webpack.config.js —— 集成 babel-loader + 解析别名 + source map ​

js
const path = require('path')

module.exports = {
  // 打包模式:开发模式(调试代码、实时加载、模块热替换)
  mode: 'development',

  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'app.js',
    clean: true
  },

  // 开发环境调错:把源码位置信息一起打包进 JS 文件内
  devtool: 'inline-source-map',

  module: { // 加载器
    rules: [ // 规则列表
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/, // 排除指定目录里的 js(不进行编译降级)
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'] // 预设规则
          }
        }
      }
    ]
  },

  resolve: {
    alias: {
      MyUtils: path.resolve(__dirname, 'src/utils'),
      '@': path.resolve(__dirname, 'src')
    }
  }
}

要点解释与易错点:

  • test: /\.m?js$/ 同时匹配 .js 和 .mjs;exclude 里的 node_modules 一定要写,否则 npm 下载的包也会被 babel 重新转译,打包极慢。
  • options.presets 必须写成数组 ['@babel/preset-env'],且三个包 babel-loader @babel/core @babel/preset-env 都要用 -D 装到本地,缺一个都会报“找不到模块”。
  • path.resolve(__dirname, 'src') 中的 __dirname 是当前配置文件所在文件夹的绝对地址,别名值必须是绝对路径,写相对路径别名不生效。
  • devtool: 'inline-source-map' 只适合开发环境,生产环境要关掉,防止别人直接点开报错信息看到源码位置。
  • 打包后控制台点击报错,路径会变成 main.js:82 这种原始行号;不开这个选项时只能看到压缩混淆后的 app.js 行号,定位不到源码。

示例二:src/main.js —— 箭头函数降级验证 + axios 注册用户 ​

js
/**
 * 目标:让 Webpack + Babel 编译降级 JS 语法
 */
const arr = [1, 2, 3]
const result = arr.map(val => val + 1)
console.log(result)

/**
 * 目标:基于 axios 完成注册用户功能
 */
import axios from 'axios'
import { checkUsername, checkPassword } from '@/utils/check.js'

document.querySelector('.login-btn').addEventListener('click', () => {
  const username = document.querySelector('.username').value
  const password = document.querySelector('.password').value

  if (!checkUsername(username)) {
    alert('用户名长度要大于等于8位!!!')
    return
  } else if (!checkPassword(password)) {
    alert('密码长度要求大于等于6位')
    return
  }

  console.log('用户名和密码长度符合要求')

  axios({
    url: 'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data: {
      username,
      password
    }
  }).then(result => {
    alert(result.data.message)
  }).catch(error => {
    alert(error.response.data.message)
  })
})

要点解释与易错点:

  • import 语句是模块语法,必须写在文件顶层(可以放在其他语句前后,但都在模块作用域最外层);不能写在点击回调函数里面,否则 Webpack 直接报语法错误。
  • 引入 npm 包写模块名 'axios' 即可(Webpack 自动去 node_modules 找),只有自己写的模块才需要写路径,改成别名后就是 '@/utils/check.js',对应 resolve.alias 里 @ → src。
  • axios 的请求体用 data 选项,username、password 属性名与变量同名可以简写;失败回调里是 error.response.data.message(response 这一层不能漏),和成功回调的 result.data.message 不同。
  • 打包后在 dist/app.js 里能看到 const 被降级成 var、箭头函数变成 function,变量名会被 Webpack 混淆成 y 之类的短名——这是极致压缩的正常现象,不影响运行。
  • 这个文件必须被 entry 或 entry 引入的模块链引到,否则 Webpack 根本不会处理它。

示例三:package.json 的 scripts —— 开发服务器 + 两种打包模式 ​

json
{
  "scripts": {
    "build": "webpack --mode=production",
    "dev": "webpack serve --mode=development"
  }
}

要点解释与易错点:

  • 命令是 webpack serve(没有 r),写成 webpack server 会直接报错找不到命令;启动后终端会给出域名+端口号(如 http://localhost:8080),按住 Ctrl 点击即可打开。
  • 开发服务器打包结果输出在内存中,所以 dist 目录里看不到最新文件;只有执行 npm run build 才会真正写到 dist 下,最终给用户运行的是 dist/index.html。
  • 命令行 --mode 的优先级高于 webpack.config.js 里的 mode 选项,所以推荐用命令行设置,开发和上线各用一条命令,不用来回改配置文件。
  • development 与 production 两个单词是固定的,写错会报模式不合法;生产模式会把代码压缩混淆成一行,体积更小、上线加载更快。
  • 开发服务器是常驻进程,要停止它按 Ctrl + C(转写稿里提到按两下);另外只有被入口引入过的模块(js/css)改动才会触发热更新。

常见错误 ​

1. babel-loader 配好了,产物里箭头函数、let 依然原样

  • 现象:dist/app.js 里还能看到 =>,低版本浏览器仍报语法错误。
  • 原因:options.presets 漏写或包名写错;test 正则没命中目标文件;exclude 误写成 /src/ 或 /(src)/,把源码也排除了。
  • 修法:确认 @babel/core、@babel/preset-env、babel-loader 三个包都在 devDependencies 里;presets 用数组写 ['@babel/preset-env'];exclude 只排 node_modules。排查时先用 npx babel src/main.js 单独验证预设是否生效,再怀疑 webpack 侧。

2. 报 Module not found: Can't resolve 'axios' / Can't resolve 'babel-loader'

  • 现象:打包直接失败,提示找不到模块。
  • 原因:包装在项目目录之外(在父级目录执行了 npm i);或装了 babel-loader 却没装 @babel/core(peer 依赖缺失,报错文案常被误读成 loader 不存在)。
  • 修法:cd 到有 package.json 的那一层再安装;装完核对 package.json 的依赖项;import 只写包名,不要手写 ./node_modules/axios。

3. 用 dev-server 改了代码,浏览器毫无反应

  • 现象:npm run dev 起来了,但保存代码后页面不变。
  • 原因:浏览器里打开的是 dist/index.html(file:// 协议)而不是终端提示的 http://localhost:8080;或者改的文件没有被入口 import 链引用到;或终端进程已经 Ctrl+C 退出了。
  • 修法:只访问 dev-server 提示的地址;dev 模式下产物在内存里,dist 目录不会有新文件,不要拿 dist 判断是否生效。

4. 上线包体积没变小,源码还能被直接看到

  • 现象:build 之后产物还是一大坨可读代码,甚至能在控制台点到源码位置。
  • 原因:mode 只在配置文件里写了 development 就忘了改;或 devtool: 'inline-source-map' 一直挂着带进了生产包。
  • 修法:build 命令固定带 --mode=production(命令行优先级高于配置文件),并让 devtool 跟随模式动态取值,生产环境设为 false。

5. 配了 @ 别名,却报 Can't resolve '@/utils/check.js'

  • 现象:改完 import 路径就打包失败。
  • 原因:忘了 const path = require('path');path.resolve(__dirname, 'src') 里的 'src' 拼错;或者 alias 的 key 写成 '@/'(带斜杠)导致匹配不上。
  • 修法:alias 的 key 不带斜杠,只用 '@';确认 __dirname 指向的是配置文件所在目录;改完路径后必须重启 dev-server 才读到新配置。

6. 注册功能一失败,控制台又抛一个 TypeError

  • 现象:接口报错时 alert 没弹出来,反而提示 Cannot read properties of undefined (reading 'data')。
  • 原因:catch 里直接写了 error.response.data.message,而网络层错误(断网、跨域被拦)时 error.response 是 undefined。
  • 修法:用可选链兜底 error.response?.data?.message || '请求失败,请稍后重试';同时确认请求是 POST + data(写成 params 会变成查询串,后端收不到 body)。

7. 配置文件和命令行都写了 mode,结果和预期不符

  • 现象:配置文件写 production,npm run dev 出来的却是压缩代码,热更新行为也怪。
  • 原因:命令行参数优先级高于配置文件,两个地方同时写就成了隐性覆盖。
  • 修法:只在一处设置,推荐全放 scripts 里,让配置文件的 mode 留空。

练习 ​

1. 排错题 下面这段配置打包后箭头函数没有被降级,请指出所有可疑点并修正:

js
module.exports = {
  module: { rules: [
    { test: /\.js$/, exclude: /src/, use: 'babel-loader' }
  ]}
}

参考要点:exclude 误排源码;use 缺少 options.presets;test 未覆盖 .mjs。

2. 动手题 把 devtool 改成跟随打包模式:开发模式用 inline-source-map,生产模式关闭。要求 npm run dev 和 npm run build 两条命令互不干扰,且不依赖手工改配置文件。

3. 动手题 在 webpack.config.js 中配置 @ 指向 src,然后把项目里所有引用自身源码的 import 改写成 @/... 形式;import axios from 'axios' 保持不变,并说明为什么它不需要改。

4. 场景题 把注册案例的错误处理改造成三种提示:字段校验不通过、服务器返回业务错误(如"用户名已存在")、网络/跨域异常。写出 catch 分支的判断逻辑。

5. 上线前检查清单 基于本组笔记,列出把项目从开发态切到上线态必须确认的 5 件事(模式、source map、产物位置、命令、依赖归属)。

原视频对照 ​

#视频时长
1Webpack集成babel编译器10:43
2案例-注册用户完成09:54
3Webpack开发服务器07:12
4Webpack打包模式08:51
5Webpack开发调试sourcemap07:01
6Webpack设置解析别名路径07:13

小结 ​

  1. loader 的 test/exclude 写错时不会报错,只会静默不生效——所以验证永远要落在产物上,而不是"配置看起来对了"。
  2. babel 三件套分工明确:core 干活、preset-env 定规则、loader 做桥梁,少任何一个都跑不起来,且报错文案不会直接告诉你少了谁。
  3. 一旦引入 dev-server,"打包"这个动作就分成了两条路:开发走内存 + 热更新,上线走磁盘 + 压缩,判断是否生效的方式完全不同,别再拿 dist 当唯一真相。
  4. mode 不是可有可无的开关,它决定了压缩、tree-shaking 等一整套内置优化;把它写死在配置文件里迟早出事,放进 scripts 才是可持续的做法。
  5. source map 是开发期的调试工具,不是项目能力——它和"源码安全"天然对立,生产环境关掉它是纪律问题,不是可选项。
  6. 路径别名的价值在项目变大后才显现,但它同时引入了"配置与代码的隐式契约":别名一改,全项目的 import 都可能失效,所以要早配、早统一。
  7. npm 装包 + import 包名,是工程化项目引入第三方库的唯一正路;script 标签那套 CDN 引入方式一旦混进来,就会出现"本地能跑、打包报错"的双轨混乱。
  8. 接口层代码必须假设"失败是常态":error.response 可能不存在,兜底提示写不写,决定了用户看到的是友好文案还是白屏。