跳到主要内容JavaScriptNode.js大前端
前端核心知识点梳理与面试复习指南
综述由AI生成系统梳理了前端开发的核心知识点,涵盖网络基础(URL、DNS、TCP/IP、HTTP/HTTPS)、浏览器渲染原理(DOM、CSSOM、重排重绘)、JavaScript 引擎机制(V8、事件循环)以及构建工具(Webpack、Vite)的配置与优化。文章结合面试场景,详细解析了跨域解决方案、缓存策略、状态码含义及性能优化实践,适合前端开发者复习与进阶。
黑客23 浏览 一:从输入 URL 到页面渲染
1. URL 的标准组成部分

一个完整的 URL 结构如下:scheme://host:port/path?query#fragment
URI 用字符串标识某一互联网资源,而 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):
function jsonp({ url, params, callbackName }) {
return new Promise((resolve) => {
const script = document.createElement('script');
window[callbackName] = function(data) {
resolve(data);
document.body.removeChild(script);
delete window[callbackName];
};
const query = { ...params, callback: callbackName };
const queryString = Object.keys(query).map(key => `${key}=${query[key]}`).join('&');
script.src = `${url}?${queryString}`;
document.body.appendChild(script);
});
}
C. Proxy (代理) - 开发环境最常用
通过'中间人'绕过浏览器限制。浏览器访问同源的代理服务器,代理服务器再去请求目标服务器(服务器之间没有同源策略)。
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 地址,返回给浏览器并缓存。
3. 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, 网络适配器,即网卡),及光纤等物理可见部分 (还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。
图解
4. TCP 三次握手:确保双方都有收发能力
拿到 IP 后,客户端需要与服务器建立 TCP 连接。为什么是三次?因为要确保双向通信链路都是通畅的。
- 第一次握手:客户端发送
SYN (Synchronize) 包,序列号为 x。
状态:客户端进入 SYN_SENT。服务器确认了'客户端发送能力正常'。
- 第二次握手:服务器返回
SYN + ACK (Acknowledgment) 包,序列号 y,确认号 x+1。
状态:服务器进入 SYN_RCVD。客户端确认了'服务器接收和发送能力都正常'。
- 第三次握手:客户端发送
ACK 包,序列号 x+1,确认号 y+1。
状态:双方进入 ESTABLISHED。服务器确认了'客户端接收能力正常'。
5. 四次挥手的详细过程
- 第一次挥手 (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(例如后端连接池没释放,或者长连接逻辑出错)。
- 后果:会占用大量文件描述符,导致新连接无法建立。
6. 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) => {
res.setHeader('Cache-Control', 'public, max-age=31536000');
res.sendFile(__dirname + '/logo.png');
});
app.listen(3000);
5. 面试官杀手锏:用户操作对缓存的影响
- 地址栏回车 / 链接跳转:有效,强缓存和协商缓存都正常工作。
- F5 刷新:失效。浏览器会在请求头带上
Cache-Control: max-age=0,跳过强缓存,直接发起协商缓存。
- Ctrl + F5 (强制刷新):全部失效。跳过所有缓存,直接从服务器拉取最新的。
总结建议
在面试中谈论缓存时,你可以顺便带出 '大前端部署实践':
'通常我们会给 HTML 设置 no-cache(走协商缓存),而给静态资源(JS/CSS/图片)设置超长强缓存。为了更新这些资源,我们会给文件名加上 Content Hash。这样只要代码变了,文件名就变,浏览器就会请求新文件,而没变的文件依然能秒开。'
7. 状态码
1. 状态码分类概览
2xx - 成功
- 200 OK:请求成功,有返回内容。
- 204 No Content:请求成功,但没有响应主体(常用于
OPTIONS 预检请求或删除操作)。
- 206 Partial Content:客户端发送了范围请求(
Range Header),服务器成功返回了部分内容。常用于断点续传或大视频分段加载。
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 聊聊权限控制。
8. 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 与服务器通信。
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) 之后,但在物理布局完成之前。
注意: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。
- 执行一个宏任务(最开始是同步代码)。
- 执行过程中如果遇到微任务,放入微任务队列。
- 当前宏任务执行完后,立即清空所有的微任务队列。
- (关键点):更新渲染(Update Rendering)。
- 检查是否有 Web Worker 任务,开始下一个宏任务。
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
解释:1 和 4 先入栈执行。执行完后,检查微任务队列发现 3,执行 3。最后开启下一个循环,执行宏任务 2。
五:浏览器缓存与渲染结合:Service Worker
如果说 HTTP 缓存是'自动挡',那么 Service Worker 就是'手动挡'。它是运行在浏览器后台的独立线程。Service Worker 的地位:它充当了浏览器与网络之间的代理服务器。它可以拦截网络请求,并根据策略决定是走网络、走缓存,还是直接返回一段自定义内容。
- 离线能力:即使没网,也能通过缓存加载页面(PWA 的核心)。
- 推送通知:可以在后台接收服务器消息。
- 不能直接操作 DOM:必须通过
postMessage 与主线程通信。
- Install 阶段:通常用来预缓存静态资源(App Shell)。
- Activate 阶段:清理旧缓存。
- Fetch 阶段:拦截请求,实现缓存优先或网络优先策略。
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 出现前)
- 无模块化:所有 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+ → ES5
css-loader:处理 CSS 中的 @import 和 url()
style-loader:将 CSS 注入到 DOM
file-loader:处理文件资源(图片、字体)
url-loader:小文件转 Base64,大文件走 file-loader
插件系统(Plugins)
- 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 |
| 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 |
- 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 |
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';
.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">
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
<div></div>
<%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</html>
| 配置项 | 类型 | 说明 | 示例值 |
|---|
| 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: {
minimize: process.env.NODE_ENV === 'production',
minimizer: [
new TerserPlugin({
parallel: true,
cache: true,
sourceMap: process.env.NODE_ENV === 'development',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info'],
dead_code: true,
conditionals: true,
if_return: true,
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: true,
async: true,
yield: true,
classes: true,
let_vars: true,
const_vars: true,
for_of: true,
for_in: true,
export: true,
import: true,
module: true,
strict: true,
this: true,
arguments: true,
eval: false,
hoist_vars: false,
hoist_funs: false,
hoist_props: false,
hoist_func_decls: false,
pure_getters: 'strict',
keep_fargs: false,
keep_fnames: false,
keep_classnames: false,
keep_infinity: false,
side_effects: true,
unused: true,
warnings: false
},
mangle: {
keep_classnames: false,
keep_fnames: false,
safari10: true,
properties: {
reserved: ['__esModule', 'default'],
regex: null,
debug: false,
reserved_names: null
}
},
output: {
comments: false,
preserve_line: false,
beautify: false,
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,
wrap_switches: false,
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,
wrap_switch_literal_expressions: false,
wrap_try_catch_literal_expressions: false
},
sourceMap: {
includeSources: false,
file: null,
url: null
}
}
}),
new CssMinimizerPlugin({
parallel: true,
cache: true,
sourceMap: process.env.NODE_ENV === 'development',
minimizerOptions: {
preset: [
'default',
{
discardComments: {
removeAll: true
},
discardEmpty: true,
mergeLonghand: true,
mergeRules: true,
calc: {
precision: 5
},
colormin: true,
reduceTransforms: true,
svgo: false
}
]
}
})
],
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 250000,
minChunks: 2,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: (module, chunks, cacheGroupKey) => {
return cacheGroupKey;
},
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors',
reuseExistingChunk: true,
enforce: true
},
commons: {
test: /[\\/]src[\\/]common[\\/]/,
minChunks: 2,
priority: -20,
name: 'commons',
reuseExistingChunk: true
},
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 30,
reuseExistingChunk: true
},
styles: {
test: /\.css$/,
chunks: 'all',
enforce: true,
name: 'styles',
priority: -5,
reuseExistingChunk: true
},
dynamic: {
test: /[\\/]src[\\/]pages[\\/]/,
chunks: 'async',
name: (module, chunks, cacheGroupKey) => {
return `${cacheGroupKey}-${chunks[0].name}`;
},
priority: 10,
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'runtime'
},
moduleIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
chunkIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
nodeEnv: process.env.NODE_ENV || 'development',
providedExports: true,
usedExports: true,
sideEffects: true,
concatenateModules: process.env.NODE_ENV === 'production',
mangleExports: process.env.NODE_ENV === 'production',
portableRecords: true,
realContentHash: true,
emitOnErrors: false,
checkWasmTypes: true,
minimize: true,
removeAvailableModules: true,
removeEmptyChunks: true,
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,
pathRewrite: {
'^/api': ''
},
secure: false,
ws: true,
bypass: (req, res) => {
if (req.headers.accept.includes('html')) {
return '/index.html';
}
}
}
}
6、调试与监控配置
6.1 Source Maps 配置
| 模式 | 速度 | 质量 | 适用场景 |
|---|
| 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 的瓶颈
1. 打包整个应用 → 生成 bundle.js
2. 启动本地服务器
3. 每次修改代码 → 重新打包 → 热更新
4. 项目越大,打包时间越长
- 小型项目(< 100 个模块):Webpack 启动时间约 3-5 秒
- 中型项目(100-500 个模块):Webpack 启动时间约 5-15 秒
- 大型项目(> 500 个模块):Webpack 启动时间可能超过 30 秒
1.2 Vite 的核心理念
Webpack:预先打包
所有模块先打包成 bundle.js,浏览器下载并执行
Vite:按需编译
浏览器请求时,Vite 动态编译模块并返回
无需等待整个应用打包完成
- 冷启动极快:无论项目多大,启动时间几乎恒定
- 热更新极快:毫秒级 HMR
- 开箱即用:零配置或极简配置
- 生产优化:使用 Rollup 进行生产构建
2、Vite 的工作原理
2.1 开发环境:ESM 驱动的开发服务器
浏览器请求 → Webpack 打包 → 生成 bundle → 返回 HTML → 执行 JS
↓
每次修改都重复
浏览器请求 → Vite 按需编译 → 动态返回 ESM 模块
↓
只编译当前需要的模块
- 服务器启动:Vite 启动 ESM 开发服务器,不进行打包
- 依赖预构建:将 CommonJS/UMD 依赖转换为 ESM 并缓存
- 按需编译:当浏览器请求
/src/main.js 时:
- 解析导入语句
- 转换 TypeScript/JSX 等
- 返回 ESM 模块
- 动态导入:浏览器继续请求依赖的模块,Vite 动态编译
2.2 依赖预构建
module.exports = {
default: 'value'
};
export default {
default: 'value'
};
- 性能:避免每次请求都转换
- 兼容性:确保依赖能正确在浏览器中运行
- 缓存:转换结果缓存在
node_modules/.vite 目录
2.3 生产构建:Rollup 优化
- Tree Shaking:Rollup 的 Tree Shaking 更彻底
- 代码分割:Rollup 的代码分割算法更优秀
- 模块格式:Rollup 支持多种输出格式
- 插件系统:Rollup 插件更专注,性能更好
3、Vite 配置详解
3.1 基础配置
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({
base: '/',
server: {
host: 'localhost',
port: 3000,
open: true,
https: false,
hmr: {
overlay: true,
clientPort: 3000
},
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/ws': {
target: 'ws://localhost:8080',
ws: true,
changeOrigin: true
}
},
preview: {
port: 4173,
open: true
}
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
input: {
main: './index.html',
},
output: {
manualChunks: (id) => {
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,
reportCompressedSize: true
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
},
less: {
modifyVars: {
'primary-color': '#1DA57A'
}
}
},
extract: true,
modules: {
generateScopedName: '[name]__[local]--[hash:base64:5]',
localsConvention: 'camelCaseOnly'
},
devSourcemap: true
},
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']
},
envPrefix: 'VITE_',
define: {
'__APP_VERSION__': JSON.stringify(process.env.npm_package_version),
'__BUILD_TIME__': Date.now()
},
plugins: [
react({
babel: {
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }]
]
},
fastRefresh: true,
jsxRuntime: 'automatic'
}),
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']
}),
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setup.js',
coverage: {
reporter: ['text', 'json', 'html'],
exclude: ['node_modules/', 'tests/']
}
}
});
3.2 环境变量管理
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My Application
VITE_ENABLE_ANALYTICS=true
VITE_API_URL=http://localhost:8080
VITE_DEBUG=true
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';
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_NAME: string;
readonly VITE_ENABLE_ANALYTICS: boolean;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
3.3 多页面应用配置
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: [
{
name: 'multi-page-plugin',
transformIndexHtml(html) {
return html;
}
}
]
});
3.4 库开发配置
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 自定义插件开发
import { defineConfig } from 'vite';
function myPlugin() {
return {
name: 'my-custom-plugin',
transform(code, id) {
if (id.endsWith('.js')) {
return {
code: code + '\n// My custom plugin added this',
map: null
};
}
},
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 增强配置
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";
`,
javascriptEnabled: true
}
}
}
});
5.3 性能优化配置
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
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']
}
},
cssCodeSplit: true,
assetsInlineLimit: 4096,
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 插件(大部分兼容)
- 企业级功能
- 某些企业级功能可能需要额外工作
- 解决方案:组合使用多个工具
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online