前端知识点梳理,前端面试复习

一:从输入 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 字符集。特殊字符(如空格、中文)必须通过 encodeURIencodeURIComponent 进行编码。

1. 什么是“同源”?

根据我们刚刚讨论的 URL 组成,只有当两个 URL 的 协议 (Protocol)域名 (Domain)端口 (Port) 均相同时,才被称为同源
  • https://a.com/page1https://a.com/page2 —— 同源
  • http://a.comhttps://a.com —— 跨域(协议不同)
  • https://a.comhttps://b.com —— 跨域(域名不同)
  • https://a.comhttps://a.com:8080 —— 跨域(端口不同)

2. 常见的跨域解决方案

在面试中,你通常需要给出以下三种最核心的方案:

A. CORS (Cross-Origin Resource Sharing) - 最主流

这是 W3C 标准,由后端通过设置 HTTP 响应头来告诉浏览器:“我允许这个源访问我的资源”。

  • 核心响应头:Access-Control-Allow-Origin: *(或指定的域名)。
  • 简单请求 vs 预检请求:对于复杂请求(如 PUTDELETE 或自定义 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。

  1. 前端:在 Ajax/Fetch 请求中设置 withCredentials = true
  2. 后端:设置响应头 Access-Control-Allow-Credentials: true
  3. 注意:此时 Access-Control-Allow-Origin不能设为通配符 *,必须指定具体域名。

总结建议

在面试时,你可以这样总结:“跨域是浏览器的安全屏障,解决它的核心思路要么是让服务器明确许可(CORS),要么是利用标签特性(JSONP),要么是避开浏览器环境(Proxy)。”

2. DNS 解析过程

1. DNS 解析过程:将域名变为 IP 地址

当你输入 www.example.com 时,计算机并不认识这个字符串,它需要通过 DNS(域名系统)找到对应的 IP 地址。

详细步骤(递归 + 迭代):

  1. 浏览器缓存/操作系统缓存:首先检查浏览器自身是否有该域名的解析记录,如果没有,再检查操作系统的 hosts 文件。
  2. 本地 DNS 服务器(LDNS):通常是你接入的网络服务商(ISP)。
  3. 根域名服务器(Root Nameserver):LDNS 如果没有缓存,会去问根服务器:“我知道 .com 在哪吗?”。
  4. 顶级域名服务器(TLD Nameserver):根服务器指向 .com 服务器,LDNS 再去问 .com 服务器:“example.com 在哪?”。
  5. 权威域名服务器(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,报文最大生存时间)。

原因有两个:

  1. 确保最后的 ACK 能够到达服务器:如果最后一个 ACK 丢了,服务器会超时重传第三次的 FIN。客户端只有在 TIME_WAIT 期间才能重发 ACK。
  2. 防止“已失效的请求”干扰:等待足够长的时间,让本次连接产生的所有报文都在网络中消失。这样下次建立相同 IP 和端口的连接时,就不会收到上一次连接残留的旧数据。

 代码与性能层面:CLOSE_WAIT 过多怎么办?

作为前端或全栈开发者,如果监控发现服务器出现大量 CLOSE_WAIT 状态,通常是因为程序 Bug。

  • 原因:对方发了 FIN,但你的代码没有调用 close() 方法关闭 Socket(例如后端连接池没释放,或者长连接逻辑出错)。
  • 后果:会占用大量文件描述符,导致新连接无法建立。

5.HTTP 缓存策略

HTTP 缓存就是:浏览器把请求过的资源存起来,下次用的时候直接从本地拿,不再麻烦服务器

1. 缓存的整体流程

当浏览器发起请求时,它会遵循以下逻辑:

  1. 先看强缓存:如果命中了,直接用,不发请求到服务器。
  2. 再看协商缓存:如果没有强缓存或已过期,就带着“凭证”去问服务器:“我这资源还能用吗?”。
  3. 返回结果
    • 服务器说“没变”:返回 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:客户端发送了范围请求(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 聊聊权限控制。

7.HTTP和 HTTPS的区别

1.核心区别汇总

2. HTTPS 的“安全”是如何实现的?

HTTPS 并不是一种新的协议,而是 HTTP + SSL/TLS。它主要解决了 HTTP 的三个安全问题:

  1. 机密性(Encryption):防止数据被中间人窃听。
  2. 完整性(Integrity):防止数据在传输过程中被篡改。
  3. 身份认证(Authentication):确认你访问的网站确实是“官宣”的那个,而不是钓鱼网站。

3. HTTPS 的握手过程(核心考点)

这是面试官最喜欢问的细节。它结合了对称加密和非对称加密的优点。

  1. 客户端请求:客户端(浏览器)向服务器发起 HTTPS 请求,连接到 443 端口,发送支持的加密算法列表。
  2. 服务器响应:服务器选择一套加密算法,并发送自己的数字证书(包含服务器公钥)。
  3. 客户端验证
    • 浏览器检查证书是否过期、颁发机构是否可信。
    • 如果验证通过,客户端生成一个随机数(预主密钥)
  4. 非对称加密传输密钥:客户端用服务器的公钥加密这个随机数,发给服务器。
  5. 服务器解密:服务器用自己的私钥解密,得到这个随机数。
  6. 对称加密传输数据:现在双方都有了这个随机数,它将作为对称加密的密钥。此后的所有数据传输都使用这个随机数进行加密。
为什么这样设计?非对称加密(公钥/私钥)虽然安全,但速度慢对称加密(一个密钥)速度快,但密钥传输不安全。结论:用非对称加密来安全地传输对称加密的密钥,然后用对称加密来传数据。

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) 即时编译技术。

核心执行流程:

  1. 解析(Parser):将源代码转为 抽象语法树(AST)
    • 词法分析:把代码拆成一个个不可再分的词(Tokens)。
    • 语法分析:根据语法规则把 Tokens 组成树状结构。
  2. 解释(Ignition):解释器将 AST 转为字节码(Bytecode)并开始执行。
    • 字节码比机器码轻量,可以跨平台运行。
  3. 优化(TurboFan):编译器会标记“热点代码”(执行次数很多的函数),将其直接编译为高效的机器码
  4. 去优化(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)

  1. 执行一个宏任务(最开始是同步代码)。
  2. 执行过程中如果遇到微任务,放入微任务队列
  3. 当前宏任务执行完后,立即清空所有的微任务队列
  4. (关键点):更新渲染(Update Rendering)。
  5. 检查是否有 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 的地位:它充当了浏览器与网络之间的代理服务器。它可以拦截网络请求,并根据策略决定是走网络、走缓存,还是直接返回一段自定义内容。

关键特性:

  1. 离线能力:即使没网,也能通过缓存加载页面(PWA 的核心)。
  2. 推送通知:可以在后台接收服务器消息。
  3. 不能直接操作 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年)

核心概念:模块打包器

构建过程

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

模块加载-Loaders的工作原理

常见Loaders

  • babel-loader:ES6+ → ES5
  • css-loader:处理CSS中的@importurl()
  • style-loader:将CSS注入到DOM
  • file-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(输出配置)

详细说明:

配置项类型说明示例值
pathstring输出目录的绝对路径path.resolve(__dirname, 'dist')
filenamestring/function入口文件名模板[name].[contenthash:8].js
chunkFilenamestring非入口 chunk 文件名模板[name].[contenthash:8].chunk.js
publicPathstring资源公共路径'/''https://cdn.example.com/'
assetModuleFilenamestring静态资源文件名模板'assets/[hash][ext][query]'
cleanboolean/object是否清理输出目录true{ dry: true }
libraryobject库配置(打包库时使用){ name: 'MyLibrary', type: 'umd' }
globalObjectstring全局对象(UMD 模式)'this''window'

占位符说明:

占位符说明示例
[name]chunk 名称main
[id]chunk ID0
[hash]基于模块内容的哈希a1b2c3d4
[contenthash]基于 chunk 内容的哈希e5f6g7h8
[chunkhash]基于 chunk 的哈希i9j0k1l2
[ext]文件扩展名(不带点)js
[query]URL 查询字符串?v=1

2、模块解析规则(Module Rules)
2.1 JS/TS 处理

详细说明:

配置项类型说明示例
testRegExp匹配文件扩展名/\.(js|jsx)$/
excludeRegExp/Array排除的目录/node_modules/
includeRegExp/Array包含的目录[path.resolve(__dirname, 'src')]
useString/Object/Array使用的 Loader'babel-loader'{ loader: 'babel-loader', options: {...} }
parserObject解析器配置{ amd: false }
generatorObject生成器配置{ dataUrl: (content) => ... }
typeString模块类型'javascript/auto'
sideEffectsBoolean/Array副作用标记false

Loader 执行顺序:

  • pre:所有 Loader 之前执行
  • normal:普通 Loader(默认)
  • post:所有 Loader 之后执行
2.2 CSS 处理(生产环境)

详细说明:

Loader作用生产环境配置开发环境配置
MiniCssExtractPlugin.loader提取 CSS 到文件MiniCssExtractPlugin.loaderstyle-loader
css-loader处理 @import 和 url()css-loadercss-loader
postcss-loader自动添加前缀、压缩postcss-loader + cssnanopostcss-loader + autoprefixer

CSS 模块示例:

2.3 SASS/SCSS 处理

详细说明:

Loader顺序作用配置要点
style-loader/MiniCssExtractPlugin.loader1注入或提取 CSS生产环境用 MiniCssExtractPlugin
css-loader2处理 CSS 导入importLoaders 需要包含后续 loader 数量
postcss-loader3自动添加前缀配置 autoprefixer
sass-loader4编译 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 支持注释
YAMLyaml-loader需要安装 js-yaml
XMLxml-loader需要安装 xml2js
TOMLtoml-loader需要安装 toml

3、解析配置(Resolve)

详细说明:

配置项类型说明示例
extensionsArray自动解析的扩展名['.js', '.jsx', '.json']
aliasObject路径别名{'@': path.resolve(__dirname, 'src')}
modulesArray模块搜索目录['node_modules', 'src']
mainFieldsArraypackage.json 字段顺序['module', 'main', 'browser']
symlinksBoolean是否解析符号链接true
unsafeCacheBoolean/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)

详细说明:

配置项类型说明示例值
templatestringHTML 模板路径'./src/index.html'
filenamestring输出文件名'index.html'
injectboolean/string资源注入位置'body'
chunksArray注入的 chunk['main', 'vendor']
minifyObjectHTML 压缩配置{ removeComments: true }
metaObjectmeta 标签配置{ description: '...' }
scriptLoadingstring脚本加载方式'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)

详细说明:

配置项类型说明示例值
filenamestring入口 CSS 文件名'[name].[contenthash:8].css'
chunkFilenamestring非入口 chunk CSS 文件名'[name].[contenthash:8].chunk.css'
ignoreOrderboolean忽略顺序警告true
hmrboolean是否启用 HMRprocess.env.NODE_ENV === 'development'
4.3 DefinePlugin(定义全局变量)

详细说明:

变量名说明示例值
process.env.NODE_ENVNode 环境'production'
process.env.API_BASE_URLAPI 地址'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(复制静态文件)

详细说明:

配置项类型说明示例
fromstring源路径'public/'
tostring目标路径'./'
toTypestring目标类型'dir'
globOptionsObjectglob 配置{ ignore: ['**/*.tmp'] }
filterfunction过滤函数(path) => !path.endsWith('.map')
transformfunction内容转换(content) => compress(content)
transformPathfunction路径转换(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 } }; 

详细说明:

配置项类型说明示例
minimizeboolean是否压缩process.env.NODE_ENV === 'production'
minimizerArray压缩器配置[new TerserPlugin(), new CssMinimizerPlugin()]
splitChunksObject代码分割配置{ chunks: 'all', cacheGroups: {...} }
runtimeChunkboolean/string运行时代码提取'single'
moduleIdsstring模块 ID 策略'deterministic'
usedExportsboolean使用分析(Tree Shaking)true
sideEffectsboolean副作用分析(Tree Shaking)true
5、开发服务器配置(DevServer)

详细说明:

配置项类型说明示例
staticObject静态文件配置{ directory: path.join(__dirname, 'public') }
hoststring主机地址'localhost'
portnumber/string端口号3000
openboolean/string自动打开浏览器true
hotboolean热模块替换true
compressbooleanGzip 压缩true
historyApiFallbackboolean/ObjectSPA 路由支持true
proxyObject代理配置{ '/api': { target: '...' } }
headersObject响应头{ 'X-Custom-Header': 'value' }
clientObject客户端配置{ 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、不同场景的配置示例

库开发注意事项:

  1. 使用 UMD 格式:兼容 CommonJS、AMD 和全局变量
  2. 外部依赖:将 React 等作为外部依赖,避免重复打包
  3. 压缩代码:生产环境压缩,减少库体积
  4. 类型声明:使用 TypeScript 或生成 .d.ts 文件
  5. Tree Shaking:确保代码无副作用,支持 Tree Shaking
8、Webpack 5 新特性配置
8.1 资源模块(Asset Modules)
8.2 持久化缓存
8.3 未来兼容性配置
9、性能优化最佳实践
9.1 构建性能优化
9.2 运行时性能优化
9.3 长期缓存优化

长期缓存策略:

  1. 文件名哈希:使用 [contenthash] 确保内容变化时文件名变化
  2. 分离公共代码:提取第三方库,长期缓存
  3. 运行时代码分离:避免业务代码变化影响缓存
  4. CDN 部署:使用 CDN 加速静态资源加载
  5. 缓存控制:设置合适的 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动态编译模块并返回 无需等待整个应用打包完成 

关键优势

  1. 冷启动极快:无论项目多大,启动时间几乎恒定
  2. 热更新极快:毫秒级HMR
  3. 开箱即用:零配置或极简配置
  4. 生产优化:使用Rollup进行生产构建

2、Vite的工作原理

2.1 开发环境:ESM驱动的开发服务器

传统Webpack开发流程

浏览器请求 → Webpack打包 → 生成bundle → 返回HTML → 执行JS ↓ 每次修改都重复 

Vite开发流程

浏览器请求 → Vite按需编译 → 动态返回ESM模块 ↓ 只编译当前需要的模块 

详细流程

  1. 服务器启动:Vite启动ESM开发服务器,不进行打包
  2. 依赖预构建:将CommonJS/UMD依赖转换为ESM并缓存
  3. 按需编译:当浏览器请求/src/main.js时:
    • 解析导入语句
    • 转换TypeScript/JSX等
    • 返回ESM模块
  4. 动态导入:浏览器继续请求依赖的模块,Vite动态编译

2.2 依赖预构建

// node_modules中的依赖通常是CommonJS或UMD格式 // Vite需要将它们转换为ESM // 转换前(CommonJS) module.exports = { default: 'value' }; // 转换后(ESM) export default { default: 'value' }; 

预构建的好处

  1. 1.性能:避免每次请求都转换
  2. 2.兼容性:确保依赖能正确在浏览器中运行
  3. 3.缓存:转换结果缓存在node_modules/.vite目录

2.3 生产构建:Rollup优化

为什么使用Rollup?

  1. Tree Shaking:Rollup的Tree Shaking更彻底
  2. 代码分割:Rollup的代码分割算法更优秀
  3. 模块格式:Rollup支持多种输出格式
  4. 插件系统: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 开发体验对比

特性WebpackVite优势方
冷启动时间随项目增大而增长几乎恒定(<1秒)Vite
热更新速度1-5秒<100msVite
配置复杂度高(需要大量配置)低(开箱即用)Vite
TypeScript支持需要配置loader原生支持Vite
CSS处理需要配置loader原生支持Vite
代码分割手动配置自动优化Vite
Tree Shaking需要配置Rollup原生支持Vite

4.2 生产构建对比

特性WebpackVite (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 不适合的场景

  1. 大量CommonJS依赖的项目
    • Vite需要转换CommonJS到ESM,可能有性能开销
    • 解决方案:使用optimizeDeps预构建
  2. 需要复杂自定义配置的项目
    • Vite配置相对简单,但灵活性可能不足
    • 解决方案:编写自定义插件
  3. 需要特定Webpack插件的项目
    • 某些Webpack插件可能没有Vite等效版本
    • 解决方案:寻找替代方案或自定义插件
  4. 旧版浏览器支持
    • Vite默认目标是现代浏览器
    • 解决方案:使用@vitejs/plugin-legacy

6.2 性能挑战

  1. 依赖预构建
    • 首次启动需要预构建依赖,可能耗时
    • 解决方案:使用optimizeDeps缓存
  2. 大型项目热更新
    • 虽然比Webpack快,但超大项目仍可能有延迟
    • 解决方案:代码分割、懒加载
  3. 内存使用
    • 开发服务器可能占用较多内存
    • 解决方案:监控和优化

6.3 生态成熟度

  1. 插件数量
    • Vite插件生态快速增长,但仍少于Webpack
    • 解决方案:使用Rollup插件(大部分兼容)
  2. 企业级功能
    • 某些企业级功能可能需要额外工作
    • 解决方案:组合使用多个工具

Read more

Flutter 三方库 shelf_swagger_ui 鸿蒙微服务侧交互式接口图谱引擎适配:搭建开箱即用的自动化联调文档中心重现 API 高自由度联调可视沙盘-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 shelf_swagger_ui 鸿蒙微服务侧交互式接口图谱引擎适配:搭建开箱即用的自动化联调文档中心重现 API 高自由度联调可视沙盘-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 shelf_swagger_ui 鸿蒙微服务侧交互式接口资产图谱引擎适配:搭建开箱即用的自动化联调文档组件中心重现 API 高自由度联调可视沙盘 前言 在构建 OpenHarmony 端的全栈应用或复杂的后端微服务时,接口文档的可视化与实时联调是研发效率的瓶颈。对于使用 shelf 框架的 Dart 后端开发者而言,shelf_swagger_ui 提供了极简的集成方案,能够直接将 Swagger UI 注入到服务端路由中。本文将深入探讨并演示如何在鸿蒙开发环境中集成这一组件,实现开发即文档的现代化联调流程。 一、原理解析 / 概念介绍 1.1 基础原理/概念介绍 shelf_swagger_ui 本质上是一个 Static Content Handler(静态资源处理器)。它将

By Ne0inhk
OpenClaw Mac本地部署保姆级教程:手把手教你“养龙虾”

OpenClaw Mac本地部署保姆级教程:手把手教你“养龙虾”

目录 一、部署前必读:你的Mac够格“养虾”吗? 1.1 硬件要求(别担心,要求不高) 1.2 你需要准备的东西 二、Step 1:安装Homebrew(Mac的“应用商店”) 三、Step 2:安装Node.js(OpenClaw的运行环境) 可选但推荐:安装Redis 四、Step 3:安装OpenClaw(核心步骤) 4.1 一键安装脚本(最简单,推荐) 编辑 4.2 如果一键脚本失败(备用方案:手动安装) 4.3 解决“command not found”

By Ne0inhk
Flutter 组件 sheety_localization 的适配 鸿蒙Harmony 实战 - 驾驭在线协作式多语言管理、实现鸿蒙端动态词条下发与全球化敏捷发布方案

Flutter 组件 sheety_localization 的适配 鸿蒙Harmony 实战 - 驾驭在线协作式多语言管理、实现鸿蒙端动态词条下发与全球化敏捷发布方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 sheety_localization 的适配 鸿蒙Harmony 实战 - 驾驭在线协作式多语言管理、实现鸿蒙端动态词条下发与全球化敏捷发布方案 前言 在鸿蒙(OpenHarmony)应用的国际化(I18n)进程中,开发者面临的最大痛点往往不是代码逻辑,而是“文案的频繁变动”。产品经理、翻译团队与开发团队之间通过 Excel 或是 IM 工具反复传递文案 json,不仅效率低下,且极易引入语境遗漏和乱码风险导致鸿蒙 App 上线后的“翻译灾难”。 我们需要一种更有“生命力”的文案流转方式。 sheety_localization 提供了一套极其巧妙的方案:将 Google Sheets 作为文案的在线配置中心。翻译团队直接在云端修改表格,鸿蒙应用通过 API 即可实时拉取最新的语言资产。

By Ne0inhk
OpenHarmony Flutter 三方库 flusmic 的适配鸿蒙调研 - 玩转跨平台 CMS 内容管理、实现动态数据流式获取与 UI 高速同步

OpenHarmony Flutter 三方库 flusmic 的适配鸿蒙调研 - 玩转跨平台 CMS 内容管理、实现动态数据流式获取与 UI 高速同步

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net OpenHarmony Flutter 三方库 flusmic 的适配鸿蒙调研 - 玩转跨平台 CMS 内容管理、实现动态数据流式获取与 UI 高速同步 前言 在现代化的 App 开发中,“内容与展现分离”已成为主流趋势。开发者不希望为了修改一段文案或更新一张活动图而被迫发版本审核。Headless CMS(无头内容管理系统)如 Prismic 正是为了解决这一痛点。 flusmic 是 Flutter 生态中对接 Prismic CMS 的优秀客户端。它以轻量、类型安全且支持丰富查询语法而著称。 伴随着鸿蒙系统(OpenHarmony)在全场景终端的爆发式增长,如何在鸿蒙手机、平板甚至折叠屏上,实现 CMS 数据的秒级同步和完美呈现?本文将带你从零开始,在鸿蒙环境下玩转 flusmic,

By Ne0inhk