前端知识点梳理,前端面试复习
一:从输入 URL 到页面渲染是一个经典的综合性考题
1.URL 的标准组成部分

一个完整的 URL 结构如下: scheme://host:port/path?query#fragmentURI 用字符串标识某一互联网资源,而URL 表示资源的地点(互 联网上所处的位置)。可见URL是URI 的子集。

URI 和 URL 的区别?
- URI (Uniform Resource Identifier) 是统一资源标识符,是一个大概念。
- URL (Uniform Resource Locator) 是统一资源定位符,它不仅标识资源,还提供了找到资源的方式(比如协议)。可以理解为 URL 是 URI 的子集。
为什么 URL 中有些字符会被转义(如 %20)?
- URL 只能使用 ASCII 字符集。特殊字符(如空格、中文)必须通过
encodeURI或encodeURIComponent进行编码。
1. 什么是“同源”?
根据我们刚刚讨论的 URL 组成,只有当两个 URL 的 协议 (Protocol)、域名 (Domain) 和 端口 (Port) 均相同时,才被称为同源。
https://a.com/page1和https://a.com/page2—— 同源http://a.com和https://a.com—— 跨域(协议不同)https://a.com和https://b.com—— 跨域(域名不同)https://a.com和https://a.com:8080—— 跨域(端口不同)
2. 常见的跨域解决方案
在面试中,你通常需要给出以下三种最核心的方案:
A. CORS (Cross-Origin Resource Sharing) - 最主流
这是 W3C 标准,由后端通过设置 HTTP 响应头来告诉浏览器:“我允许这个源访问我的资源”。
- 核心响应头:
Access-Control-Allow-Origin: *(或指定的域名)。 - 简单请求 vs 预检请求:对于复杂请求(如
PUT、DELETE或自定义 Header),浏览器会先发一个OPTIONS方法的请求,称为预检请求。
B. JSONP (JSON with Padding) - 兼容老旧浏览器
利用了 <script> 标签不受同源策略限制的特性。
- 限制:仅支持 GET 请求。
- 前端构建工具配置(如 Vite/Webpack):
原理代码实现:
/** * 模拟一个简单的 JSONP 实现 */ function jsonp({ url, params, callbackName }) { return new Promise((resolve) => { // 1. 创建 script 标签 const script = document.createElement('script'); // 2. 将回调函数挂载到 window,供服务端调用 window[callbackName] = function(data) { resolve(data); document.body.removeChild(script); // 善后工作 delete window[callbackName]; }; // 3. 构建 URL,带上 callback 参数 const query = { ...params, callback: callbackName }; const queryString = Object.keys(query).map(key => `${key}=${query[key]}`).join('&'); script.src = `${url}?${queryString}`; // 4. 插入页面,触发请求 document.body.appendChild(script); }); } // 使用示例 // jsonp({ url: 'http://api.test.com/data', callbackName: 'handleRes' }).then(res => console.log(res));C. Proxy (代理) - 开发环境最常用
通过“中间人”绕过浏览器限制。浏览器访问同源的代理服务器,代理服务器再去请求目标服务器(服务器之间没有同源策略)。
// vite.config.js 示例 export default { server: { proxy: { '/api': { target: 'http://backend-api.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } }3. 面试官深挖:Cookie 如何在跨域中携带?
这是一个高频追问点。默认情况下,跨域请求不带 Cookie。
- 前端:在 Ajax/Fetch 请求中设置
withCredentials = true。 - 后端:设置响应头
Access-Control-Allow-Credentials: true。 - 注意:此时
Access-Control-Allow-Origin不能设为通配符*,必须指定具体域名。
总结建议
在面试时,你可以这样总结:“跨域是浏览器的安全屏障,解决它的核心思路要么是让服务器明确许可(CORS),要么是利用标签特性(JSONP),要么是避开浏览器环境(Proxy)。”
2. DNS 解析过程

1. DNS 解析过程:将域名变为 IP 地址
当你输入 www.example.com 时,计算机并不认识这个字符串,它需要通过 DNS(域名系统)找到对应的 IP 地址。详细步骤(递归 + 迭代):
- 浏览器缓存/操作系统缓存:首先检查浏览器自身是否有该域名的解析记录,如果没有,再检查操作系统的
hosts文件。 - 本地 DNS 服务器(LDNS):通常是你接入的网络服务商(ISP)。
- 根域名服务器(Root Nameserver):LDNS 如果没有缓存,会去问根服务器:“我知道
.com在哪吗?”。 - 顶级域名服务器(TLD Nameserver):根服务器指向
.com服务器,LDNS 再去问.com服务器:“example.com在哪?”。 - 权威域名服务器(Authoritative Nameserver):最后找到负责
example.com的权威服务器,拿到具体的 IP 地址,返回给浏览器并缓存。
2. TCP/IP 分层管理:数据是如何打包的?
在建立连接前,我们要理解数据是怎么通过网络栈传输的。TCP/IP 通常分为四层(或 OSI 七层模型,面试常考四层):

1.应用层:
应用层决定了向用户提供应用服务时通信的活动。 TCP/IP协议族内预存了各类通用的应用服务。比如, FTP (File Transfer Protocol, 文件传输协议)和DNS (Domain Name System, 域名系统)服务就是其中两类。 HTTP 协议也处于该层。
2.传输层:
传输层对上层应用层,提供处于网络连接中的两台计算机之间的 数据传输。 在传输层有两个性质不同的协议: TCP (Transmission Control Protocol,传输控制协议)和UDP (User Data Protocol,用户数据 报协议)。
3.网络层(又名网络互连层)
网络层用来处理在网络上流动的数据包。数据包是网络传输的最小 数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对 方计算机,并把数据包传送给对方。 与对方计算机之间通过多台计算机或网络设备进行传输时,网络层 所起的作用就是在众多的选项内选择一条传输路线。
4.链路层(又名数据链路层,网络接口层)
用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱 动、NIC (Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴 均在链路层的作用范围之内。
图解


3. TCP 三次握手:确保双方都有收发能力
拿到 IP 后,客户端需要与服务器建立 TCP 连接。为什么是三次?因为要确保双向通信链路都是通畅的。

- 第一次握手:客户端发送
SYN(Synchronize) 包,序列号为x。
状态:客户端进入 SYN_SENT。服务器确认了“客户端发送能力正常”。
- 第二次握手:服务器返回
SYN+ACK(Acknowledgment) 包,序列号y,确认号x+1。
状态:服务器进入 SYN_RCVD。客户端确认了“服务器接收和发送能力都正常”。
- 第三次握手:客户端发送
ACK包,序列号x+1,确认号y+1。
状态:双方进入 ESTABLISHED。服务器确认了“客户端接收能力正常”。
4. 四次挥手的详细过程
想象客户端(Client)主动发起断开请求:
- 第一次挥手 (FIN):客户端发送一个
FIN(Finish) 报文,用来告诉服务器:“我没有数据要发给你了,我要关闭发送通道”。
状态:客户端进入 FIN_WAIT_1 状态。
- 第二次挥手 (ACK):服务器收到
FIN,回复一个ACK。意思是:“收到了,但我可能还有数据没发完,你等我一下”。
状态:服务器进入 CLOSE_WAIT,客户端进入 FIN_WAIT_2。此时连接处于“半关闭”状态。
- 第三次挥手 (FIN):服务器处理完最后的数据,向客户端发送
FIN。意思是:“好了,我也发完了,我也要关了”。
状态:服务器进入 LAST_ACK。
- 第四次挥手 (ACK):客户端收到服务器的
FIN,回复最后一个ACK。意思是:“收到,祝好”。
状态:客户端进入 TIME_WAIT 状态,等待 2MSL 后彻底关闭。服务器收到 ACK 后立即 CLOSED。
面试高频追问:为什么会有 TIME_WAIT?
这是四次挥手最常被问到的技术细节。客户端在发送完最后一个 ACK 后,并不会立即关掉连接,而是要等待 2MSL(Maximum Segment Lifetime,报文最大生存时间)。
原因有两个:
- 确保最后的 ACK 能够到达服务器:如果最后一个 ACK 丢了,服务器会超时重传第三次的
FIN。客户端只有在TIME_WAIT期间才能重发 ACK。 - 防止“已失效的请求”干扰:等待足够长的时间,让本次连接产生的所有报文都在网络中消失。这样下次建立相同 IP 和端口的连接时,就不会收到上一次连接残留的旧数据。
代码与性能层面:CLOSE_WAIT 过多怎么办?
作为前端或全栈开发者,如果监控发现服务器出现大量 CLOSE_WAIT 状态,通常是因为程序 Bug。
- 原因:对方发了
FIN,但你的代码没有调用close()方法关闭 Socket(例如后端连接池没释放,或者长连接逻辑出错)。 - 后果:会占用大量文件描述符,导致新连接无法建立。
5.HTTP 缓存策略
HTTP 缓存就是:浏览器把请求过的资源存起来,下次用的时候直接从本地拿,不再麻烦服务器。
1. 缓存的整体流程
当浏览器发起请求时,它会遵循以下逻辑:
- 先看强缓存:如果命中了,直接用,不发请求到服务器。
- 再看协商缓存:如果没有强缓存或已过期,就带着“凭证”去问服务器:“我这资源还能用吗?”。
- 返回结果:
- 服务器说“没变”:返回 304 Not Modified,浏览器继续用本地的。
- 服务器说“变了”:返回 200 OK 和新内容
2. 强缓存 (Strong Cache)
特点:不需要发送 HTTP 请求,直接从内存 (from memory cache) 或磁盘 (from disk cache) 读取。
关键响应头:
- Expires (HTTP/1.0):
- 值是一个绝对时间(如
Expires: Wed, 21 Oct 2025 07:28:00 GMT)。 - 缺点:受限于客户端本地时间,如果用户改了系统时间,缓存会失效。
- 值是一个绝对时间(如
- Cache-Control (HTTP/1.1) - 推荐:
- 使用相对时间,优先级高于
Expires。 - 常用指令:
max-age=3600:缓存 1 小时。no-cache:不使用强缓存,直接进入协商缓存阶段。no-store:完全不缓存,每次都要重新下载。
- 使用相对时间,优先级高于
3. 协商缓存 (Negotiation Cache)
特点:必须发请求到服务器,由服务器决定是否使用缓存。

4.模拟代码场景:如何设置缓存?
如果你在写一个 Node.js (Express) 后端,你可以这样控制缓存:
const express = require('express'); const app = express(); app.get('/static/logo.png', (req, res) => { // 设置强缓存:1年 (单位:秒) res.setHeader('Cache-Control', 'public, max-age=31536000'); // 或者设置协商缓存 (Express 默认会自动处理 ETag) // res.setHeader('Cache-Control', 'no-cache'); res.sendFile(__dirname + '/logo.png'); }); app.listen(3000);5. 面试官杀手锏:用户操作对缓存的影响
这是很多候选人会忽略的细节:
- 地址栏回车 / 链接跳转:有效,强缓存和协商缓存都正常工作。
- F5 刷新:失效。浏览器会在请求头带上
Cache-Control: max-age=0,跳过强缓存,直接发起协商缓存。 - Ctrl + F5 (强制刷新):全部失效。跳过所有缓存,直接从服务器拉取最新的。
总结建议
在面试中谈论缓存时,你可以顺便带出 “大前端部署实践”:
“通常我们会给 HTML 设置 no-cache(走协商缓存),而给静态资源(JS/CSS/图片)设置超长强缓存。为了更新这些资源,我们会给文件名加上 Content Hash。这样只要代码变了,文件名就变,浏览器就会请求新文件,而没变的文件依然能秒开。”6.状态码
1. 状态码分类概览

2xx - 成功
- 200 OK:请求成功,有返回内容。
- 204 No Content:请求成功,但没有响应主体(常用于
OPTIONS预检请求或删除操作)。 - 206 Partial Content:客户端发送了范围请求(
RangeHeader),服务器成功返回了部分内容。常用于断点续传或大视频分段加载。
3xx - 重定向
- 301 Moved Permanently:永久重定向。浏览器会自动缓存新地址,下次直接跳新地址。
- 302 Found:临时重定向。下次访问还是会请求原地址。
- 304 Not Modified:重点! 协商缓存命中,服务器告诉浏览器:“东西没变,你直接用本地缓存吧”。
4xx - 客户端错误
- 400 Bad Request:请求语法错误(比如 JSON 格式写错了)。
- 401 Unauthorized:未授权,需要身份验证(比如没登录)。
- 403 Forbidden:服务器理解请求,但拒绝执行(比如你有登录,但没有管理员权限访问该页面)。
- 404 Not Found:资源不存在。
- 405 Method Not Allowed:方法不支持(比如接口只准
POST,你用了GET)。
5xx - 服务器错误
- 500 Internal Server Error:后端代码报错了。
- 502 Bad Gateway:充当网关或代理的服务器(如 Nginx)尝试执行请求时,从上游服务器接收到无效响应。
- 503 Service Unavailable:服务器超载或停机维护。
- 504 Gateway Timeout:网关超时,上游服务器没在规定时间内返回数据。
2.面试官进阶:301 和 302 对 SEO 的影响?
- 301 (永久):搜索引擎在抓取时会把旧地址的权重(PR值)转移到新地址。如果你换了新域名,一定要做 301。
- 302 (临时):权重不会转移。如果滥用 302,可能会被搜索引擎判定为 URL 劫持。
总结建议
当面试官问你状态码时,不仅要说出数字含义,最好能结合实际业务场景。比如谈到206聊聊视频大文件下载,谈到304聊聊刚才说的 HTTP 缓存,谈到401/403聊聊权限控制。
7.HTTP和 HTTPS的区别
1.核心区别汇总

2. HTTPS 的“安全”是如何实现的?
HTTPS 并不是一种新的协议,而是 HTTP + SSL/TLS。它主要解决了 HTTP 的三个安全问题:
- 机密性(Encryption):防止数据被中间人窃听。
- 完整性(Integrity):防止数据在传输过程中被篡改。
- 身份认证(Authentication):确认你访问的网站确实是“官宣”的那个,而不是钓鱼网站。
3. HTTPS 的握手过程(核心考点)
这是面试官最喜欢问的细节。它结合了对称加密和非对称加密的优点。
- 客户端请求:客户端(浏览器)向服务器发起 HTTPS 请求,连接到 443 端口,发送支持的加密算法列表。
- 服务器响应:服务器选择一套加密算法,并发送自己的数字证书(包含服务器公钥)。
- 客户端验证:
- 浏览器检查证书是否过期、颁发机构是否可信。
- 如果验证通过,客户端生成一个随机数(预主密钥)。
- 非对称加密传输密钥:客户端用服务器的公钥加密这个随机数,发给服务器。
- 服务器解密:服务器用自己的私钥解密,得到这个随机数。
- 对称加密传输数据:现在双方都有了这个随机数,它将作为对称加密的密钥。此后的所有数据传输都使用这个随机数进行加密。
为什么这样设计?非对称加密(公钥/私钥)虽然安全,但速度慢。对称加密(一个密钥)速度快,但密钥传输不安全。结论:用非对称加密来安全地传输对称加密的密钥,然后用对称加密来传数据。
4. 代码层面:前端需要做什么?
作为前端开发者,你不需要编写加密算法,但你需要知道:
- Mixed Content 警告:如果你的 HTTPS 页面中引用了 HTTP 的静态资源(如图片、脚本),浏览器会报错或拦截。
- HSTS (HTTP Strict Transport Security):一种安全策略,强制浏览器只使用 HTTPS 与服务器通信。
// 在 Node.js (Express) 中开启 HTTPS 的简单示例 const https = require('https'); const fs = require('fs'); const express = require('express'); const options = { key: fs.readFileSync('server.key'), // 私钥 cert: fs.readFileSync('server.crt') // 证书 }; const app = express(); https.createServer(options, app).listen(443, () => { console.log('HTTPS Server running on port 443'); });5. 面试官可能追问:什么是中间人攻击(MITM)?
如果黑客伪造了证书,而用户忽略了浏览器的安全警告点击了“继续访问”,黑客就可以解密你的数据。这就是为什么证书的有效性验证至关重要。
二:浏览器渲染原理
1. 渲染模式的分水岭:后端返回了什么?
1.服务端渲染 (SSR - Server Side Rendering)
- 后端返回:一个包含了完整 DOM 结构的 HTML 字符串。
- 浏览器起点:拿到 HTML 后立即开始解析并显示内容。
- 特点:SEO 友好,首屏加载快(白屏时间短)。
- 现代框架:Next.js (React), Nuxt.js (Vue)。
2.客户端渲染 (CSR - Client Side Rendering)
- 后端返回:一个几乎为空的 HTML 壳子(通常只有一个
<div></div>)和一堆 JS 文件。 - 浏览器起点:必须等待 JS 下载并执行完毕后,由 JS 动态创建 DOM 节点。
- 特点:用户体验流畅(页面切换无刷新),但首屏可能较慢。
- 现代框架:普通的 Vue-cli 或 Create-React-App 项目。
2. 浏览器渲染的详细流水线 (The Critical Rendering Path)
无论数据是怎么来的,一旦浏览器拿到了 HTML、CSS 和 JS,就会进入关键渲染路径。这是面试中最核心的流程:
第一步:构建对象模型
1.构建 DOM 树:浏览器将 HTML 字节流解析为一个个令牌(Tokens),然后转换成节点,最后构建成 DOM Tree
当你通过element.style.height获取高度时,你访问的是 DOM 节点上的属性。特性:它只能获取到写在 HTML 标签style属性中的值。局限性:如果高度是通过外部 CSS 文件或.class定义的,这里返回的是空字符串。阶段:处于 DOM 构建 阶段,尚未经过 CSSOM 的计算
2.构建 CSSOM 树:解析所有的 CSS(包括外部文件和内联样式),构建出 CSSOM Tree。
第二步:合并成渲染树 (Render Tree)
当你调用window.getComputedStyle(element).height时,你访问的是 CSSOM 树 的最终计算结果。特性:无论高度来自 ID、Class 还是继承,它都会返回经过计算的像素值(例如"200.5px")。阶段:处于 合并渲染树 (Render Tree) 之后,但在物理布局完成之前。
浏览器将 DOM 和 CSSOM 合并。
注意:display: none的节点不会出现在渲染树中,但visibility: hidden的节点会。
第三步:布局 (Layout / Reflow)
计算每个节点在屏幕上的确切位置和大小。想象成在一个白纸上画方块,确定坐标。
这些 API 会触发浏览器的 强制同步布局 (Forced Synchronous Layout),即强制浏览器立即计算 Layout (Reflow) 步骤。clientHeight: 内容高度 + 内边距 (Padding)。offsetHeight: 内容高度 + 内边距 + 边框 (Border)。getBoundingClientRect().height: 元素在屏幕上的物理尺寸(受transform: scale()缩放影响后的最终视觉高度)。阶段:处于 Layout/Reflow 之后。
第四步:绘制 (Paint / Repaint)
将节点的像素信息(颜色、边框、阴影等)绘制到屏幕上。
第五步:合成 (Composite)
如果页面有复杂的层级(如 3D 转换、Canvas、video),浏览器会将它们分层处理,最后合成到一起。
3. 为什么 JS 会阻塞渲染?
原理:默认情况下,HTML 解析器遇到 <script> 标签时会暂停,去下载并执行 JS。因为 JS 可能会操作 DOM 或修改 CSS(导致前面的工作白费)解决方案:
- defer:脚本异步下载,等待 HTML 解析完成后执行。
- async:脚本异步下载,下载完立即执行(可能中断解析)
4. 面试必考:重排 (Reflow) 与 重绘 (Repaint)

5. 总结与扩展
从后端返回 HTML 开始,到屏幕显示出图像,浏览器经历了一个非常复杂的流水线。
- SSR 提前在服务器做好了“构建对象模型”的一大部分工作。
- CSR 把这些工作全丢给了浏览器的 JS 引擎。
三:V8引擎
V8 引擎(Chrome 和 Node.js 的核心)之所以快,是因为它摒弃了传统的“解释执行”,采用了 JIT (Just-In-Time) 即时编译技术。
核心执行流程:
- 解析(Parser):将源代码转为 抽象语法树(AST)。
- 词法分析:把代码拆成一个个不可再分的词(Tokens)。
- 语法分析:根据语法规则把 Tokens 组成树状结构。
- 解释(Ignition):解释器将 AST 转为字节码(Bytecode)并开始执行。
- 字节码比机器码轻量,可以跨平台运行。
- 优化(TurboFan):编译器会标记“热点代码”(执行次数很多的函数),将其直接编译为高效的机器码。
- 去优化(Deoptimization):如果热点代码的变量类型发生了变化(例如原本传数字,后来传了字符串),V8 会把机器码退回到字节码。
面试加分点:为什么 V8 提倡写“类型确定”的代码? 因为类型一旦变化,就会触发 Deoptimization,导致性能陡降。这也是为什么 TypeScript 在大型项目中能间接提升性能(通过规范类型减少 V8 的猜疑)。
四: 事件循环 (Event Loop):异步调度的灵魂
事件循环是 JS 实现非阻塞 I/O 的核心。由于 JS 是单线程的,它必须通过一个机制来协调同步代码和异步任务。
任务分类:
- 宏任务 (Macrotask):
script(整体代码),setTimeout,setInterval,I/O,UI rendering。 - 微任务 (Microtask):
Promise.then,MutationObserver,process.nextTick。
执行顺序 (Event Loop Tick)
- 执行一个宏任务(最开始是同步代码)。
- 执行过程中如果遇到微任务,放入微任务队列。
- 当前宏任务执行完后,立即清空所有的微任务队列。
- (关键点):更新渲染(Update Rendering)。
- 检查是否有 Web Worker 任务,开始下一个宏任务。
代码实战分析:
console.log('1'); // 同步 setTimeout(() => { console.log('2'); // 宏任务 }, 0); Promise.resolve().then(() => { console.log('3'); // 微任务 }); console.log('4'); // 同步 // 输出顺序:1 -> 4 -> 3 -> 2解释:1 和 4 先入栈执行。执行完后,检查微任务队列发现 3,执行 3。最后开启下一个循环,执行宏任务 2。
五: 浏览器缓存与渲染结合:Service Worker
如果说 HTTP 缓存是“自动挡”,那么 Service Worker 就是“手动挡”。它是运行在浏览器后台的独立线程。Service Worker 的地位:它充当了浏览器与网络之间的代理服务器。它可以拦截网络请求,并根据策略决定是走网络、走缓存,还是直接返回一段自定义内容。
关键特性:
- 离线能力:即使没网,也能通过缓存加载页面(PWA 的核心)。
- 推送通知:可以在后台接收服务器消息。
- 不能直接操作 DOM:必须通过
postMessage与主线程通信。
生命周期与缓存结合:
- Install 阶段:通常用来预缓存静态资源(App Shell)。
- Activate 阶段:清理旧缓存。
- Fetch 阶段:拦截请求,实现缓存优先或网络优先策略。
// service-worker.js 示例 self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { // 如果缓存命中,直接返回;否则走网络请求 return response || fetch(event.request); }) ); }); 总结:三者的联动
- V8 决定了 JS 代码运行的快慢。
- Event Loop 决定了任务执行的时机,以及是否会卡住 UI 渲染。
- Service Worker 决定了资源加载的来源,是渲染流水线的“物料补给站”。

六:页面加载完整流程图

七:打包工具webpack的出现
1. 前端开发的痛点(Webpack出现前)
2014年前的前端开发环境:
- 无模块化:所有JS文件通过
<script>标签手动引入,依赖管理混乱 - 手动合并:需要手动复制粘贴代码或使用Grunt/Gulp简单合并
- 无构建优化:没有代码压缩、Tree Shaking、代码分割
- 工具链不统一:每个项目需要不同的构建流程
- 性能问题:
- HTTP/1.1的队头阻塞:每个文件一个请求
- 无缓存策略:每次更新都要重新下载所有文件
- 无懒加载:所有代码一次性加载

2. 模块化开发的需求(CommonJS的出现)

新的问题:CommonJS是Node.js的模块系统,在浏览器中无法直接运行,需要转换。
3.Webpack的诞生(2012年)
核心概念:模块打包器



构建过程:
- 从入口开始:Webpack从配置的入口文件开始
- 解析依赖:分析
require()、import等语句 - 递归构建:对每个依赖模块重复步骤2
- 生成依赖图:最终形成一个完整的模块依赖关系图

模块加载-Loaders的工作原理:

常见Loaders:
babel-loader:ES6+ → ES5css-loader:处理CSS中的@import和url()style-loader:将CSS注入到DOMfile-loader:处理文件资源(图片、字体)url-loader:小文件转Base64,大文件走file-loader
插件系统(Plugins)
Plugins与Loaders的区别:
- Loaders:在单个文件转换时工作(一对一)
- Plugins:在整个构建过程中工作(全局)

Webpack配置字典
1、基础配置模块
1.1 entry(入口配置)

详细说明:
| 配置类型 | 适用场景 | 示例 | 说明 |
|---|---|---|---|
| 单入口 | 简单应用、SPA | './src/index.js' | 所有代码打包到一个文件 |
| 多入口 | 多页面应用 | {main: './src/index.js', admin: './src/admin.js'} | 每个入口生成独立的bundle |
| 数组入口 | 需要前置依赖 | ['./src/polyfills.js', './src/index.js'] | 按顺序打包,最后合并 |
| 动态入口 | 条件打包 | 函数返回对象 | 运行时确定入口,适合复杂场景 |
最佳实践:
- SPA应用:使用单入口 + 动态导入(懒加载)
- MPA应用:使用多入口 + 按需加载
- 库开发:使用单入口 + libraryTarget
1.2 output(输出配置)

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| path | string | 输出目录的绝对路径 | path.resolve(__dirname, 'dist') |
| filename | string/function | 入口文件名模板 | [name].[contenthash:8].js |
| chunkFilename | string | 非入口 chunk 文件名模板 | [name].[contenthash:8].chunk.js |
| publicPath | string | 资源公共路径 | '/' 或 'https://cdn.example.com/' |
| assetModuleFilename | string | 静态资源文件名模板 | 'assets/[hash][ext][query]' |
| clean | boolean/object | 是否清理输出目录 | true 或 { dry: true } |
| library | object | 库配置(打包库时使用) | { name: 'MyLibrary', type: 'umd' } |
| globalObject | string | 全局对象(UMD 模式) | 'this' 或 'window' |
占位符说明:
| 占位符 | 说明 | 示例 |
|---|---|---|
[name] | chunk 名称 | main |
[id] | chunk ID | 0 |
[hash] | 基于模块内容的哈希 | a1b2c3d4 |
[contenthash] | 基于 chunk 内容的哈希 | e5f6g7h8 |
[chunkhash] | 基于 chunk 的哈希 | i9j0k1l2 |
[ext] | 文件扩展名(不带点) | js |
[query] | URL 查询字符串 | ?v=1 |
2、模块解析规则(Module Rules)
2.1 JS/TS 处理

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| test | RegExp | 匹配文件扩展名 | /\.(js|jsx)$/ |
| exclude | RegExp/Array | 排除的目录 | /node_modules/ |
| include | RegExp/Array | 包含的目录 | [path.resolve(__dirname, 'src')] |
| use | String/Object/Array | 使用的 Loader | 'babel-loader' 或 { loader: 'babel-loader', options: {...} } |
| parser | Object | 解析器配置 | { amd: false } |
| generator | Object | 生成器配置 | { dataUrl: (content) => ... } |
| type | String | 模块类型 | 'javascript/auto' |
| sideEffects | Boolean/Array | 副作用标记 | false |
Loader 执行顺序:
- pre:所有 Loader 之前执行
- normal:普通 Loader(默认)
- post:所有 Loader 之后执行
2.2 CSS 处理(生产环境)

详细说明:
| Loader | 作用 | 生产环境配置 | 开发环境配置 |
|---|---|---|---|
| MiniCssExtractPlugin.loader | 提取 CSS 到文件 | MiniCssExtractPlugin.loader | style-loader |
| css-loader | 处理 @import 和 url() | css-loader | css-loader |
| postcss-loader | 自动添加前缀、压缩 | postcss-loader + cssnano | postcss-loader + autoprefixer |
CSS 模块示例:


2.3 SASS/SCSS 处理

详细说明:
| Loader | 顺序 | 作用 | 配置要点 |
|---|---|---|---|
| style-loader/MiniCssExtractPlugin.loader | 1 | 注入或提取 CSS | 生产环境用 MiniCssExtractPlugin |
| css-loader | 2 | 处理 CSS 导入 | importLoaders 需要包含后续 loader 数量 |
| postcss-loader | 3 | 自动添加前缀 | 配置 autoprefixer |
| sass-loader | 4 | 编译 SASS | 可以添加全局变量 |
2.4 图片资源处理

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| type: 'asset' | 字符串 | 自动内联/外置 | 'asset' |
| parser.dataUrlCondition | 对象 | 内联条件 | { maxSize: 8192 } |
| generator.filename | 字符串 | 输出文件名 | 'images/[name].[hash:8][ext]' |
| use | 数组 | 额外处理 loader | ['image-webpack-loader'] |
文件大小决策逻辑:
- < 8KB(默认):内联为 base64(减少 HTTP 请求)
- ≥ 8KB:外置为独立文件(可以缓存)
2.5 字体文件处理

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| type | 字符串 | 模块类型 | 'asset/resource' |
| generator.filename | 字符串 | 输出文件名 | 'fonts/[name].[hash:8][ext]' |
| generator.publicPath | 字符串 | 公共路径 | '/' |
2.6 JSON/YAML/XML 处理

详细说明:
| 文件类型 | Loader | 配置要点 |
|---|---|---|
| JSON | 内置(type: 'json') | 可以使用 JSON5 支持注释 |
| YAML | yaml-loader | 需要安装 js-yaml |
| XML | xml-loader | 需要安装 xml2js |
| TOML | toml-loader | 需要安装 toml |
3、解析配置(Resolve)

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| extensions | Array | 自动解析的扩展名 | ['.js', '.jsx', '.json'] |
| alias | Object | 路径别名 | {'@': path.resolve(__dirname, 'src')} |
| modules | Array | 模块搜索目录 | ['node_modules', 'src'] |
| mainFields | Array | package.json 字段顺序 | ['module', 'main', 'browser'] |
| symlinks | Boolean | 是否解析符号链接 | true |
| unsafeCache | Boolean/RegExp | 缓存解析结果 | true 或 /node_modules/ |
路径别名使用示例:
// 传统方式(深层路径) import Header from '../../../components/Header'; // 使用别名(更简洁) import Header from '@components/Header'; // 在 CSS 中使用(需要配置 css-loader) .container { background: url(@assets/images/bg.jpg); } 4、插件配置(Plugins)
4.1 HtmlWebpackPlugin(生成 HTML)

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| template | string | HTML 模板路径 | './src/index.html' |
| filename | string | 输出文件名 | 'index.html' |
| inject | boolean/string | 资源注入位置 | 'body' |
| chunks | Array | 注入的 chunk | ['main', 'vendor'] |
| minify | Object | HTML 压缩配置 | { removeComments: true } |
| meta | Object | meta 标签配置 | { description: '...' } |
| scriptLoading | string | 脚本加载方式 | 'defer' |
模板示例:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 其他 meta 标签 --> <%= htmlWebpackPlugin.tags.headTags %> </head> <body> <div></div> <!-- 注入的 JS --> <%= htmlWebpackPlugin.tags.bodyTags %> </body> </html> 4.2 MiniCssExtractPlugin(提取 CSS)

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| filename | string | 入口 CSS 文件名 | '[name].[contenthash:8].css' |
| chunkFilename | string | 非入口 chunk CSS 文件名 | '[name].[contenthash:8].chunk.css' |
| ignoreOrder | boolean | 忽略顺序警告 | true |
| hmr | boolean | 是否启用 HMR | process.env.NODE_ENV === 'development' |
4.3 DefinePlugin(定义全局变量)

详细说明:
| 变量名 | 说明 | 示例值 |
|---|---|---|
| process.env.NODE_ENV | Node 环境 | 'production' |
| process.env.API_BASE_URL | API 地址 | 'https://api.example.com' |
| process.env.ENABLE_ANALYTICS | 功能开关 | true |
| process.env.APP_VERSION | 应用版本 | '1.0.0' |
| __DEV__ | 开发环境标志 | false |
| __PROD__ | 生产环境标志 | true |
使用示例:
// 在代码中直接使用 if (process.env.NODE_ENV === 'production') { // 生产环境代码 disableDebug(); } if (process.env.ENABLE_ANALYTICS) { // 启用分析 initAnalytics(); } 4.4 CopyWebpackPlugin(复制静态文件)

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| from | string | 源路径 | 'public/' |
| to | string | 目标路径 | './' |
| toType | string | 目标类型 | 'dir' |
| globOptions | Object | glob 配置 | { ignore: ['**/*.tmp'] } |
| filter | function | 过滤函数 | (path) => !path.endsWith('.map') |
| transform | function | 内容转换 | (content) => compress(content) |
| transformPath | function | 路径转换 | (path) => path.replace('.js', '.min.js') |
4.5 Optimization 配置(优化)
const TerserPlugin = require('terser-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { optimization: { // 1. minimize - 是否压缩 // 作用:控制是否启用压缩 // 生产环境通常为 true minimize: process.env.NODE_ENV === 'production', // 2. minimizer - 压缩器 // 作用:指定使用的压缩器 minimizer: [ // JS 压缩器 new TerserPlugin({ // 并行压缩 parallel: true, // 使用缓存 cache: true, // 是否使用 source map sourceMap: process.env.NODE_ENV === 'development', // Terser 配置 terserOptions: { // 压缩选项 compress: { // 删除 console drop_console: true, // 删除 debugger drop_debugger: true, // 纯函数优化 pure_funcs: ['console.log', 'console.info'], // 死代码消除 dead_code: true, // 条件语句优化 conditionals: true, // 优化 if-else if_return: true, // 优化 switch switches: true, // 优化循环 loops: true, // 优化对象字面量 properties: true, // 优化函数调用 evaluate: true, // 优化算术表达式 reduce_vars: true, // 优化布尔值 booleans: true, // 优化数字 numbers: true, // 优化字符串 strings: true, // 优化正则 regexp: true, // 优化模板字符串 template_strings: true, // 优化箭头函数 arrow_functions: true, // 优化对象方法 methods: true, // 优化默认参数 defaults: true, // 优化解构 destructuring: true, // 优化 spread spread: true, // 优化 async/await async: true, // 优化 yield yield: true, // 优化 class classes: true, // 优化 let/const let_vars: true, // 优化 const const_vars: true, // 优化 for-of for_of: true, // 优化 for-in for_in: true, // 优化 export export: true, // 优化 import import: true, // 优化 module module: true, // 优化 strict mode strict: true, // 优化 this this: true, // 优化 arguments arguments: true, // 优化 eval eval: false, // 优化 hoist_vars hoist_vars: false, // 优化 hoist_funs hoist_funs: false, // 优化 hoist_props hoist_props: false, // 优化 hoist_func_decls hoist_func_decls: false, // 优化 pure_getters pure_getters: 'strict', // 优化 keep_fargs keep_fargs: false, // 优化 keep_fnames keep_fnames: false, // 优化 keep_classnames keep_classnames: false, // 优化 keep_infinity keep_infinity: false, // 优化 side_effects side_effects: true, // 优化 unused unused: true, // 优化 warnings warnings: false }, // 混淆选项 mangle: { // 保留类名 keep_classnames: false, // 保留函数名 keep_fnames: false, // Safari 10 兼容 safari10: true, // 属性名混淆 properties: { // 保留的属性名 reserved: ['__esModule', 'default'], // 是否混淆属性名 regex: null, // 是否使用 debug 属性 debug: false, // 是否使用保留字 reserved_names: null } }, // 输出选项 output: { // 保留注释 comments: false, // 保留版权注释 preserve_line: false, // 美化输出 beautify: false, // ASCII 限制 ascii_only: false, // 引号样式 quote_style: 0, // 最大行长度 max_line_len: 32000, // 保留行 preserve_line: false, // 保留语句结束 semicolons: true, // 保留缩进 indent_level: 3, // 保留缩进 indent_start: 0, // 保留斜杠 keep_quoted_props: false, // 是否使用括号 wrap_iife: false, // 是否使用括号包裹函数调用 wrap_func_args: false, // 是否使用括号包裹对象 wrap_object: false, // 是否使用括号包裹数组 wrap_array: false, // 是否使用括号包裹函数表达式 wrap_function_expressions: false, // 是否使用括号包裹箭头函数 wrap_arrow_functions: false, // 是否使用括号包裹类 wrap_classes: false, // 是否使用括号包裹条件 wrap_conditions: false, // 是否使用括号包裹循环 wrap_loops: false, // 是否使用括号包裹 switch wrap_switches: false, // 是否使用括号包裹 try-catch wrap_try_catch: false, // 是否使用括号包裹对象字面量 wrap_object_literals: false, // 是否使用括号包裹数组字面量 wrap_array_literals: false, // 是否使用括号包裹函数字面量 wrap_function_literals: false, // 是否使用括号包裹类字面量 wrap_class_literals: false, // 是否使用括号包裹条件字面量 wrap_condition_literals: false, // 是否使用括号包裹循环字面量 wrap_loop_literals: false, // 是否使用括号包裹 switch 字面量 wrap_switch_literals: false, // 是否使用括号包裹 try-catch 字面量 wrap_try_catch_literals: false, // 是否使用括号包裹对象字面量 wrap_object_literal_expressions: false, // 是否使用括号包裹数组字面量 wrap_array_literal_expressions: false, // 是否使用括号包裹函数字面量 wrap_function_literal_expressions: false, // 是否使用括号包裹类字面量 wrap_class_literal_expressions: false, // 是否使用括号包裹条件字面量 wrap_condition_literal_expressions: false, // 是否使用括号包裹循环字面量 wrap_loop_literal_expressions: false, // 是否使用括号包裹 switch 字面量 wrap_switch_literal_expressions: false, // 是否使用括号包裹 try-catch 字面量 wrap_try_catch_literal_expressions: false }, // 源码选项 sourceMap: { // 是否包含源码 includeSources: false, // 是否使用 source map file: null, // 是否使用 source map url: null } } }), // CSS 压缩器 new CssMinimizerPlugin({ // 并行压缩 parallel: true, // 使用缓存 cache: true, // 是否使用 source map sourceMap: process.env.NODE_ENV === 'development', // cssnano 配置 minimizerOptions: { preset: [ 'default', { // 优化选项 discardComments: { removeAll: true }, // 移除空规则 discardEmpty: true, // 合并规则 mergeLonghand: true, // 合并媒体查询 mergeRules: true, // 优化 calc() calc: { precision: 5 }, // 优化颜色 colormin: true, // 优化转换 reduceTransforms: true, // 优化 svgo svgo: false } ] } }) ], // 3. splitChunks - 代码分割 // 作用:提取公共代码 splitChunks: { // 4. chunks - 适用的 chunk 类型 // 选项:'initial' | 'async' | 'all' // 'initial': 入口 chunk // 'async': 异步 chunk(动态导入) // 'all': 所有 chunk chunks: 'all', // 5. minSize - 最小大小 // 作用:chunk 的最小大小(字节) // 小于此大小的 chunk 不会被分割 minSize: 30000, // 30KB // 6. maxSize - 最大大小 // 作用:chunk 的最大大小(字节) // 超过此大小的 chunk 会被分割 maxSize: 250000, // 250KB // 7. minChunks - 最小引用次数 // 作用:模块被引用的最小次数 // 才能被提取到公共 chunk minChunks: 2, // 8. maxAsyncRequests - 最大异步请求数 // 作用:按需加载时最大的并行请求数 maxAsyncRequests: 5, // 9. maxInitialRequests - 最大初始请求数 // 作用:入口 chunk 最大并行请求数 maxInitialRequests: 3, // 10. automaticNameDelimiter - 自动名称分隔符 // 作用:自动生成 chunk 名称的分隔符 automaticNameDelimiter: '~', // 11. name - chunk 名称 // 作用:自定义 chunk 名称 // 函数:(module, chunks, cacheGroupKey) => string name: (module, chunks, cacheGroupKey) => { return cacheGroupKey; }, // 12. cacheGroups - 缓存组 // 作用:定义分割规则 cacheGroups: { // 13. vendors - 第三方库 vendors: { // 测试:匹配 node_modules test: /[\\/]node_modules[\\/]/, // 优先级:数值越小优先级越高 priority: -10, // chunk 名称 name: 'vendors', // 是否复用已存在的 chunk reuseExistingChunk: true, // 是否强制分割 enforce: true }, // 14. commons - 公共代码 commons: { // 测试:匹配 src 目录下的公共代码 test: /[\\/]src[\\/]common[\\/]/, // 最小引用次数 minChunks: 2, // 优先级 priority: -20, // 名称 name: 'commons', // 复用 reuseExistingChunk: true }, // 15. react - React 相关 react: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'react', priority: 30, reuseExistingChunk: true }, // 16. styles - 样式文件 styles: { // 测试:匹配 CSS 文件 test: /\.css$/, // chunk 类型:所有 chunks: 'all', // 强制分割 enforce: true, // 名称 name: 'styles', // 优先级 priority: -5, // 复用 reuseExistingChunk: true }, // 17. dynamic - 动态导入 dynamic: { // 测试:匹配动态导入 test: /[\\/]src[\\/]pages[\\/]/, // 只对异步 chunk 生效 chunks: 'async', // 名称 name: (module, chunks, cacheGroupKey) => { return `${cacheGroupKey}-${chunks[0].name}`; }, // 优先级 priority: 10, // 复用 reuseExistingChunk: true } } }, // 18. runtimeChunk - 运行时代码 // 作用:提取 Webpack 运行时代码 // 选项:boolean | string | object // true: 提取为单独文件 // 'single': 提取为单个文件 // 'multiple': 每个入口提取一个 // object: 自定义配置 runtimeChunk: { name: 'runtime' }, // 19. moduleIds - 模块 ID 生成策略 // 作用:控制模块 ID 的生成方式 // 选项:'named' | 'deterministic' | 'hashed' | 'size' | 'total-size' // 'named': 使用模块路径(开发环境) // 'deterministic': 短数字 ID(生产环境,长期缓存) moduleIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named', // 20. chunkIds - chunk ID 生成策略 // 作用:控制 chunk ID 的生成方式 // 选项:'named' | 'deterministic' | 'natural' chunkIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named', // 21. nodeEnv - 设置 process.env.NODE_ENV // 作用:设置 process.env.NODE_ENV 的值 nodeEnv: process.env.NODE_ENV || 'development', // 22. providedExports - 导出分析 // 作用:分析模块的导出 // 用于 Tree Shaking providedExports: true, // 23. usedExports - 使用分析 // 作用:分析哪些导出被使用 // 用于 Tree Shaking usedExports: true, // 24. sideEffects - 副作用分析 // 作用:分析模块的副作用 // 用于 Tree Shaking sideEffects: true, // 25. concatenateModules - 模块合并 // 作用:合并多个模块为一个 // 用于优化包大小 concatenateModules: process.env.NODE_ENV === 'production', // 26. mangleExports - 导出混淆 // 作用:混淆导出名称 // 用于优化包大小 mangleExports: process.env.NODE_ENV === 'production', // 27. portableRecords - 便携记录 // 作用:生成便携的记录文件 // 用于持久化缓存 portableRecords: true, // 28. realContentHash - 真实内容哈希 // 作用:使用真实内容哈希 // 用于长期缓存 realContentHash: true, // 29. emitOnErrors - 错误时是否输出 // 作用:即使有错误是否输出文件 // 用于开发环境 emitOnErrors: false, // 30. checkWasmTypes - 检查 WASM 类型 // 作用:检查 WASM 模块类型 // 用于 WASM 模块 checkWasmTypes: true, // 31. minimize - 是否最小化 // 作用:覆盖 optimization.minimize // 优先级更高 minimize: true, // 32. removeAvailableModules - 移除可用模块 // 作用:移除重复模块 removeAvailableModules: true, // 33. removeEmptyChunks - 移除空 chunk // 作用:移除不包含模块的 chunk removeEmptyChunks: true, // 34. mergeDuplicateChunks - 合并重复 chunk // 作用:合并内容相同的 chunk mergeDuplicateChunks: true } }; 详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| minimize | boolean | 是否压缩 | process.env.NODE_ENV === 'production' |
| minimizer | Array | 压缩器配置 | [new TerserPlugin(), new CssMinimizerPlugin()] |
| splitChunks | Object | 代码分割配置 | { chunks: 'all', cacheGroups: {...} } |
| runtimeChunk | boolean/string | 运行时代码提取 | 'single' |
| moduleIds | string | 模块 ID 策略 | 'deterministic' |
| usedExports | boolean | 使用分析(Tree Shaking) | true |
| sideEffects | boolean | 副作用分析(Tree Shaking) | true |
5、开发服务器配置(DevServer)

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| static | Object | 静态文件配置 | { directory: path.join(__dirname, 'public') } |
| host | string | 主机地址 | 'localhost' |
| port | number/string | 端口号 | 3000 |
| open | boolean/string | 自动打开浏览器 | true |
| hot | boolean | 热模块替换 | true |
| compress | boolean | Gzip 压缩 | true |
| historyApiFallback | boolean/Object | SPA 路由支持 | true |
| proxy | Object | 代理配置 | { '/api': { target: '...' } } |
| headers | Object | 响应头 | { 'X-Custom-Header': 'value' } |
| client | Object | 客户端配置 | { overlay: { errors: true } } |
代理配置示例:
proxy: { // 简单代理 '/api': 'http://localhost:8080', // 完整配置 '/api': { target: 'http://localhost:8080', changeOrigin: true, // 更改请求头中的 host pathRewrite: { '^/api': '' }, // 重写路径 secure: false, // HTTPS 证书验证 ws: true, // WebSocket 代理 bypass: (req, res) => { // 特定条件不代理 if (req.headers.accept.includes('html')) { return '/index.html'; } } } } 6、调试与监控配置
6.1 Source Maps 配置

devtool 模式说明:
| 模式 | 速度 | 质量 | 适用场景 |
|---|---|---|---|
| eval | 极快 | 差 | 开发环境 |
| eval-source-map | 快 | 较好 | 开发环境 |
| cheap-module-eval-source-map | 快 | 中等 | 开发环境 |
| source-map | 慢 | 最好 | 生产环境(需要调试) |
| hidden-source-map | 慢 | 最好 | 生产环境(调试,但不显示) |
| nosources-source-map | 慢 | 中等 | 生产环境(不暴露源码) |
6.2 性能监控配置

7、不同场景的配置示例

库开发注意事项:
- 使用 UMD 格式:兼容 CommonJS、AMD 和全局变量
- 外部依赖:将 React 等作为外部依赖,避免重复打包
- 压缩代码:生产环境压缩,减少库体积
- 类型声明:使用 TypeScript 或生成
.d.ts文件 - Tree Shaking:确保代码无副作用,支持 Tree Shaking
8、Webpack 5 新特性配置
8.1 资源模块(Asset Modules)

8.2 持久化缓存

8.3 未来兼容性配置

9、性能优化最佳实践
9.1 构建性能优化

9.2 运行时性能优化

9.3 长期缓存优化

长期缓存策略:
- 文件名哈希:使用
[contenthash]确保内容变化时文件名变化 - 分离公共代码:提取第三方库,长期缓存
- 运行时代码分离:避免业务代码变化影响缓存
- CDN 部署:使用 CDN 加速静态资源加载
- 缓存控制:设置合适的 Cache-Control 头
10、调试与问题排查
10.1 常见问题配置

10.2 环境变量配置

八:Vite
基于我们之前的Webpack讨论,现在深入探讨Vite。前端构建工具的范式转变,从"打包"转向"按需编译
1、Vite的诞生背景与核心理念
1.1 Webpack的瓶颈
Webpack在开发环境中的问题:
// Webpack的开发流程 1. 打包整个应用 → 生成bundle.js 2. 启动本地服务器 3. 每次修改代码 → 重新打包 → 热更新 4. 项目越大,打包时间越长 性能数据对比:
- 小型项目(< 100个模块):Webpack启动时间约3-5秒
- 中型项目(100-500个模块):Webpack启动时间约5-15秒
- 大型项目(> 500个模块):Webpack启动时间可能超过30秒
1.2 Vite的核心理念
"按需编译" vs "预先打包":
Webpack:预先打包 所有模块先打包成bundle.js,浏览器下载并执行 Vite:按需编译 浏览器请求时,Vite动态编译模块并返回 无需等待整个应用打包完成 关键优势:
- 冷启动极快:无论项目多大,启动时间几乎恒定
- 热更新极快:毫秒级HMR
- 开箱即用:零配置或极简配置
- 生产优化:使用Rollup进行生产构建
2、Vite的工作原理
2.1 开发环境:ESM驱动的开发服务器
传统Webpack开发流程:
浏览器请求 → Webpack打包 → 生成bundle → 返回HTML → 执行JS ↓ 每次修改都重复 Vite开发流程:
浏览器请求 → Vite按需编译 → 动态返回ESM模块 ↓ 只编译当前需要的模块 详细流程:
- 服务器启动:Vite启动ESM开发服务器,不进行打包
- 依赖预构建:将CommonJS/UMD依赖转换为ESM并缓存
- 按需编译:当浏览器请求
/src/main.js时:- 解析导入语句
- 转换TypeScript/JSX等
- 返回ESM模块
- 动态导入:浏览器继续请求依赖的模块,Vite动态编译
2.2 依赖预构建
// node_modules中的依赖通常是CommonJS或UMD格式 // Vite需要将它们转换为ESM // 转换前(CommonJS) module.exports = { default: 'value' }; // 转换后(ESM) export default { default: 'value' }; 预构建的好处:
- 1.性能:避免每次请求都转换
- 2.兼容性:确保依赖能正确在浏览器中运行
- 3.缓存:转换结果缓存在
node_modules/.vite目录
2.3 生产构建:Rollup优化
为什么使用Rollup?
- Tree Shaking:Rollup的Tree Shaking更彻底
- 代码分割:Rollup的代码分割算法更优秀
- 模块格式:Rollup支持多种输出格式
- 插件系统:Rollup插件更专注,性能更好
3、Vite配置详解
3.1 基础配置
// vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import vue from '@vitejs/plugin-vue'; import legacy from '@vitejs/plugin-legacy'; export default defineConfig({ // 1. 基础路径 // 作用:部署路径 // 示例:部署到GitHub Pages时设置为'/repository-name/' base: '/', // 2. 开发服务器配置 server: { // 主机地址 host: 'localhost', // 端口 port: 3000, // 是否自动打开浏览器 open: true, // HTTPS配置 https: false, // 热更新配置 hmr: { overlay: true, // 错误覆盖层 clientPort: 3000 // 客户端端口 }, // 代理配置 proxy: { // 基础代理 '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') }, // WebSocket代理 '/ws': { target: 'ws://localhost:8080', ws: true, changeOrigin: true } }, // 3. 预览服务器配置 preview: { port: 4173, open: true } }, // 4. 构建配置 build: { // 输出目录 outDir: 'dist', // 静态资源目录 assetsDir: 'assets', // 是否生成source map sourcemap: false, // 是否压缩 minify: 'terser', // 'terser' | 'esbuild' | false // 代码分割配置 rollupOptions: { // 入口配置 input: { main: './index.html', // 多页面应用 // admin: './admin.html' }, // 输出配置 output: { // 手动代码分割 manualChunks: (id) => { // 将node_modules中的依赖分割到vendor if (id.includes('node_modules')) { return 'vendor'; } // 将特定模块分割 if (id.includes('src/pages')) { return 'pages'; } }, // 资源文件命名 entryFileNames: '[name].[hash].js', chunkFileNames: '[name].[hash].js', assetFileNames: '[name].[hash].[ext]' } }, // 大小警告阈值 chunkSizeWarningLimit: 500, // 500KB // 是否报告分析 reportCompressedSize: true }, // 5. CSS配置 css: { // 预处理器配置 preprocessorOptions: { scss: { additionalData: `@import "@/styles/variables.scss";` }, less: { modifyVars: { 'primary-color': '#1DA57A' } } }, // 是否提取CSS到独立文件 extract: true, // 是否启用CSS模块 modules: { // 生成的类名格式 generateScopedName: '[name]__[local]--[hash:base64:5]', // 配置文件 localsConvention: 'camelCaseOnly' }, // 是否启用source map devSourcemap: true }, // 6. 解析配置 resolve: { // 路径别名 alias: { '@': '/src', '@components': '/src/components', '@utils': '/src/utils', '@assets': '/src/assets', // 解决模块重复问题 'react': '/node_modules/react/index.js', 'react-dom': '/node_modules/react-dom/index.js' }, // 扩展名 extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.vue'], // 条件导出 conditions: ['import', 'require', 'node', 'default'], // 主字段 mainFields: ['module', 'main', 'browser'] }, // 7. 环境变量配置 envPrefix: 'VITE_', // 8. 定义全局变量 define: { '__APP_VERSION__': JSON.stringify(process.env.npm_package_version), '__BUILD_TIME__': Date.now() }, // 9. 插件配置 plugins: [ // React支持 react({ // Babel配置 babel: { plugins: [ ['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-class-properties', { loose: true }] ] }, // 是否启用Fast Refresh fastRefresh: true, // 是否启用React 17 JSX转换 jsxRuntime: 'automatic' }), // Vue支持(如果使用Vue) vue({ // Vue配置 template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('ion-') } } }), // 旧版浏览器支持 legacy({ targets: ['defaults', 'not IE 11'], polyfills: [ 'es.promise.finally', 'es/map', 'es/set', 'es.array.find-index', 'es.promise', 'es.object.assign' ], modernPolyfills: ['web.dom.iterable'] }), // SVG作为React组件 // 需要安装: vite-plugin-svgr // import svgr from 'vite-plugin-svgr'; // svgr() ], // 10. 测试配置 test: { globals: true, environment: 'jsdom', setupFiles: './tests/setup.js', coverage: { reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'tests/' ] } } }); 3.2 环境变量管理
// .env VITE_API_URL=https://api.example.com VITE_APP_NAME=My Application VITE_ENABLE_ANALYTICS=true // .env.development VITE_API_URL=http://localhost:8080 VITE_DEBUG=true // .env.production VITE_API_URL=https://api.example.com VITE_DEBUG=false 在代码中使用:
// 访问环境变量 const apiUrl = import.meta.env.VITE_API_URL; const debug = import.meta.env.VITE_DEBUG === 'true'; // 类型提示(创建 src/vite-env.d.ts) /// <reference types="vite/client" /> interface ImportMetaEnv { readonly VITE_API_URL: string; readonly VITE_APP_NAME: string; readonly VITE_ENABLE_ANALYTICS: boolean; } interface ImportMeta { readonly env: ImportMetaEnv; } 3.3 多页面应用配置
// vite.config.js import { resolve } from 'path'; import { defineConfig } from 'vite'; export default defineConfig({ build: { rollupOptions: { input: { main: resolve(__dirname, 'index.html'), admin: resolve(__dirname, 'admin.html'), about: resolve(__dirname, 'about.html'), contact: resolve(__dirname, 'contact.html') } } }, plugins: [ // 多页面HTML插件 { name: 'multi-page-plugin', transformIndexHtml(html) { return html; } } ] }); 3.4 库开发配置
// vite.config.js (库开发) import { defineConfig } from 'vite'; import { resolve } from 'path'; export default defineConfig({ build: { // 输出格式 lib: { entry: resolve(__dirname, 'src/index.ts'), name: 'MyLibrary', fileName: (format) => `my-library.${format}.js` }, // 外部依赖 rollupOptions: { external: ['react', 'react-dom'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM' } } } }, // 不需要插件(除非需要特定转换) plugins: [] }); 4、Vite vs Webpack:详细对比
4.1 开发体验对比
| 特性 | Webpack | Vite | 优势方 |
|---|---|---|---|
| 冷启动时间 | 随项目增大而增长 | 几乎恒定(<1秒) | Vite |
| 热更新速度 | 1-5秒 | <100ms | Vite |
| 配置复杂度 | 高(需要大量配置) | 低(开箱即用) | Vite |
| TypeScript支持 | 需要配置loader | 原生支持 | Vite |
| CSS处理 | 需要配置loader | 原生支持 | Vite |
| 代码分割 | 手动配置 | 自动优化 | Vite |
| Tree Shaking | 需要配置 | Rollup原生支持 | Vite |
4.2 生产构建对比
| 特性 | Webpack | Vite (Rollup) |
|---|---|---|
| Tree Shaking | 支持,但需要配置 | 支持,更彻底 |
| 代码分割 | 支持,配置复杂 | 支持,自动优化 |
| 输出格式 | 多种 | 多种,更灵活 |
| 构建速度 | 较慢 | 较快(Rollup效率高) |
| 包大小 | 较大 | 较小(Tree Shaking更优) |
5、Vite高级配置
5.1 自定义插件开发
// vite.config.js import { defineConfig } from 'vite'; // 自定义插件示例 function myPlugin() { return { name: 'my-custom-plugin', // 转换钩子 transform(code, id) { if (id.endsWith('.js')) { // 在JS文件末尾添加注释 return { code: code + '\n// My custom plugin added this', map: null }; } }, // HTML转换钩子 transformIndexHtml(html) { return html.replace( '</body>', '<script>console.log("Plugin added script")</script></body>' ); }, // 服务器中间件 configureServer(server) { server.middlewares.use((req, res, next) => { if (req.url === '/custom') { res.end('Custom response from plugin'); } else { next(); } }); } }; } export default defineConfig({ plugins: [myPlugin()] }); 5.2 CSS Modules增强配置
// vite.config.js export default defineConfig({ css: { modules: { // 生成的类名格式 generateScopedName: '[name]__[local]--[hash:base64:5]', // 导出命名约定 localsConvention: 'camelCaseOnly', // 配置文件路径 hashPrefix: 'my-project' }, // 预处理器 preprocessorOptions: { scss: { additionalData: ` @import "@/styles/variables.scss"; @import "@/styles/mixins.scss"; `, // 全局SCSS变量 javascriptEnabled: true } } } }); 5.3 性能优化配置
// vite.config.js export default defineConfig({ build: { // 代码分割策略 rollupOptions: { output: { // 手动代码分割 manualChunks: (id) => { // 分割node_modules if (id.includes('node_modules')) { // 按包名分割 const packageName = id.match(/node_modules\/([^\/]+)/)?.[1]; if (packageName) { return `vendor-${packageName}`; } return 'vendor'; } // 分割业务模块 if (id.includes('src/pages')) { const pageName = id.match(/src\/pages\/([^\/]+)/)?.[1]; if (pageName) { return `page-${pageName}`; } } if (id.includes('src/components')) { return 'components'; } if (id.includes('src/utils')) { return 'utils'; } }, // 资源文件命名(长期缓存) entryFileNames: '[name].[hash].js', chunkFileNames: '[name].[hash].js', assetFileNames: (info) => { const ext = info.name.split('.').pop(); if (ext === 'css') { return `[name].[hash].css`; } if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) { return `images/[name].[hash].[ext]`; } if (['woff', 'woff2', 'eot', 'ttf', 'otf'].includes(ext)) { return `fonts/[name].[hash].[ext]`; } return `[name].[hash].[ext]`; } } }, // 压缩配置 minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, pure_funcs: ['console.log', 'console.info'] } }, // CSS代码分割 cssCodeSplit: true, // 资源内联阈值 assetsInlineLimit: 4096, // 4KB以下内联 // 是否生成Gzip压缩 reportCompressedSize: true }, // 性能监控 logLevel: 'info', clearScreen: false }); 6、Vite的局限性与挑战
6.1 不适合的场景
- 大量CommonJS依赖的项目
- Vite需要转换CommonJS到ESM,可能有性能开销
- 解决方案:使用
optimizeDeps预构建
- 需要复杂自定义配置的项目
- Vite配置相对简单,但灵活性可能不足
- 解决方案:编写自定义插件
- 需要特定Webpack插件的项目
- 某些Webpack插件可能没有Vite等效版本
- 解决方案:寻找替代方案或自定义插件
- 旧版浏览器支持
- Vite默认目标是现代浏览器
- 解决方案:使用
@vitejs/plugin-legacy
6.2 性能挑战
- 依赖预构建
- 首次启动需要预构建依赖,可能耗时
- 解决方案:使用
optimizeDeps缓存
- 大型项目热更新
- 虽然比Webpack快,但超大项目仍可能有延迟
- 解决方案:代码分割、懒加载
- 内存使用
- 开发服务器可能占用较多内存
- 解决方案:监控和优化
6.3 生态成熟度
- 插件数量
- Vite插件生态快速增长,但仍少于Webpack
- 解决方案:使用Rollup插件(大部分兼容)
- 企业级功能
- 某些企业级功能可能需要额外工作
- 解决方案:组合使用多个工具