一:从输入 URL 到页面渲染
1. URL 的标准组成部分

一个完整的 URL 结构如下:
scheme://host:port/path?query#fragment
前端核心知识体系涵盖 URL 结构、DNS 解析、TCP 连接管理、HTTP 协议细节、浏览器渲染流水线、V8 引擎执行机制及事件循环模型。同时深入剖析 Webpack 与 Vite 构建工具的差异、配置策略及性能优化方案,适用于前端技术面试准备与工程化实践参考。

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

为什么 URL 中有些字符会被转义(如 %20)?
encodeURI 或 encodeURIComponent 进行编码。根据我们刚刚讨论的 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 —— 跨域(端口不同)在面试中,你通常需要给出以下三种最核心的方案:
这是 W3C 标准,由后端通过设置 HTTP 响应头来告诉浏览器:'我允许这个源访问我的资源'。
Access-Control-Allow-Origin: *(或指定的域名)。PUT、DELETE 或自定义 Header),浏览器会先发一个 OPTIONS 方法的请求,称为预检请求。利用了 <script> 标签不受同源策略限制的特性。
原理代码实现:
/**
* 模拟一个简单的 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));
通过'中间人'绕过浏览器限制。浏览器访问同源的代理服务器,代理服务器再去请求目标服务器(服务器之间没有同源策略)。
// vite.config.js 示例
export default {
server: {
proxy: {
'/api': {
target: 'http://backend-api.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
这是一个高频追问点。默认情况下,跨域请求不带 Cookie。
withCredentials = true。Access-Control-Allow-Credentials: true。Access-Control-Allow-Origin不能设为通配符 *,必须指定具体域名。在面试时,你可以这样总结:'跨域是浏览器的安全屏障,解决它的核心思路要么是让服务器明确许可(CORS),要么是利用标签特性(JSONP),要么是避开浏览器环境(Proxy)。'

当你输入
www.example.com时,计算机并不认识这个字符串,它需要通过 DNS(域名系统)找到对应的 IP 地址。
详细步骤(递归 + 迭代):
hosts 文件。.com 在哪吗?'。.com 服务器,LDNS 再去问 .com 服务器:"example.com 在哪?'。example.com 的权威服务器,拿到具体的 IP 地址,返回给浏览器并缓存。在建立连接前,我们要理解数据是怎么通过网络栈传输的。TCP/IP 通常分为四层(或 OSI 七层模型,面试常考四层):

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


拿到 IP 后,客户端需要与服务器建立 TCP 连接。为什么是三次?因为要确保双向通信链路都是通畅的。

SYN (Synchronize) 包,序列号为 x。状态:客户端进入 SYN_SENT。服务器确认了'客户端发送能力正常'。
SYN + ACK (Acknowledgment) 包,序列号 y,确认号 x+1。状态:服务器进入 SYN_RCVD。客户端确认了'服务器接收和发送能力都正常'。
ACK 包,序列号 x+1,确认号 y+1。状态:双方进入 ESTABLISHED。服务器确认了'客户端接收能力正常'。
想象客户端(Client)主动发起断开请求:
FIN (Finish) 报文,用来告诉服务器:'我没有数据要发给你了,我要关闭发送通道'。状态:客户端进入 FIN_WAIT_1 状态。
FIN,回复一个 ACK。意思是:'收到了,但我可能还有数据没发完,你等我一下'。状态:服务器进入 CLOSE_WAIT,客户端进入 FIN_WAIT_2。此时连接处于'半关闭'状态。
FIN。意思是:'好了,我也发完了,我也要关了'。状态:服务器进入 LAST_ACK。
FIN,回复最后一个 ACK。意思是:'收到,祝好'。状态:客户端进入 TIME_WAIT 状态,等待 2MSL 后彻底关闭。服务器收到 ACK 后立即 CLOSED。
TIME_WAIT?这是四次挥手最常被问到的技术细节。客户端在发送完最后一个 ACK 后,并不会立即关掉连接,而是要等待 2MSL(Maximum Segment Lifetime,报文最大生存时间)。
原因有两个:
FIN。客户端只有在 TIME_WAIT 期间才能重发 ACK。CLOSE_WAIT 过多怎么办?作为前端或全栈开发者,如果监控发现服务器出现大量 CLOSE_WAIT 状态,通常是因为程序 Bug。
FIN,但你的代码没有调用 close() 方法关闭 Socket(例如后端连接池没释放,或者长连接逻辑出错)。HTTP 缓存就是:浏览器把请求过的资源存起来,下次用的时候直接从本地拿,不再麻烦服务器。**
当浏览器发起请求时,它会遵循以下逻辑:
特点:不需要发送 HTTP 请求,直接从内存 (
from memory cache) 或磁盘 (from disk cache) 读取。
Expires: Wed, 21 Oct 2025 07:28:00 GMT)。Expires。max-age=3600:缓存 1 小时。no-cache:不使用强缓存,直接进入协商缓存阶段。no-store:完全不缓存,每次都要重新下载。特点:必须发请求到服务器,由服务器决定是否使用缓存。

如果你在写一个 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);
这是很多候选人会忽略的细节:
Cache-Control: max-age=0,跳过强缓存,直接发起协商缓存。在面试中谈论缓存时,你可以顺便带出 '大前端部署实践':
'通常我们会给 HTML 设置
no-cache(走协商缓存),而给静态资源(JS/CSS/图片)设置超长强缓存。为了更新这些资源,我们会给文件名加上 Content Hash。这样只要代码变了,文件名就变,浏览器就会请求新文件,而没变的文件依然能秒开。'

OPTIONS 预检请求或删除操作)。Range Header),服务器成功返回了部分内容。常用于断点续传或大视频分段加载。POST,你用了 GET)。当面试官问你状态码时,不仅要说出数字含义,最好能结合实际业务场景。比如谈到
206聊聊视频大文件下载,谈到304聊聊刚才说的 HTTP 缓存,谈到401/403聊聊权限控制。

HTTPS 并不是一种新的协议,而是 HTTP + SSL/TLS。它主要解决了 HTTP 的三个安全问题:
这是面试官最喜欢问的细节。它结合了对称加密和非对称加密的优点。
为什么这样设计? 非对称加密(公钥/私钥)虽然安全,但速度慢。对称加密(一个密钥)速度快,但密钥传输不安全。结论:用非对称加密来安全地传输对称加密的密钥,然后用对称加密来传数据。
作为前端开发者,你不需要编写加密算法,但你需要知道:
// 在 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');
});
如果黑客伪造了证书,而用户忽略了浏览器的安全警告点击了'继续访问',黑客就可以解密你的数据。这就是为什么证书的有效性验证至关重要。
<div></div>)和一堆 JS 文件。无论数据是怎么来的,一旦浏览器拿到了 HTML、CSS 和 JS,就会进入关键渲染路径。这是面试中最核心的流程:
1. 构建 DOM 树:浏览器将 HTML 字节流解析为一个个令牌(Tokens),然后转换成节点,最后构建成 DOM Tree
当你通过
element.style.height获取高度时,你访问的是 DOM 节点上的属性。特性:它只能获取到写在 HTML 标签style属性中的值。局限性:如果高度是通过外部 CSS 文件或.class定义的,这里返回的是空字符串。阶段:处于 DOM 构建 阶段,尚未经过 CSSOM 的计算
2. 构建 CSSOM 树:解析所有的 CSS(包括外部文件和内联样式),构建出 CSSOM Tree。
当你调用
window.getComputedStyle(element).height时,你访问的是 CSSOM 树 的最终计算结果。特性:无论高度来自 ID、Class 还是继承,它都会返回经过计算的像素值(例如"200.5px")。阶段:处于 合并渲染树 (Render Tree) 之后,但在物理布局完成之前。
浏览器将 DOM 和 CSSOM 合并。
注意:
display: none的节点不会出现在渲染树中,但visibility: hidden的节点会。
计算每个节点在屏幕上的确切位置和大小。想象成在一个白纸上画方块,确定坐标。
这些 API 会触发浏览器的 强制同步布局 (Forced Synchronous Layout),即强制浏览器立即计算 Layout (Reflow) 步骤。
clientHeight: 内容高度 + 内边距 (Padding)。offsetHeight: 内容高度 + 内边距 + 边框 (Border)。**getBoundingClientRect().height**: 元素在屏幕上的物理尺寸(受transform: scale()缩放影响后的最终视觉高度)。阶段:处于 Layout/Reflow 之后。
将节点的像素信息(颜色、边框、阴影等)绘制到屏幕上。
如果页面有复杂的层级(如 3D 转换、Canvas、video),浏览器会将它们分层处理,最后合成到一起。
原理:默认情况下,HTML 解析器遇到
<script>标签时会暂停,去下载并执行 JS。因为 JS 可能会操作 DOM 或修改 CSS(导致前面的工作白费)
解决方案:

从后端返回 HTML 开始,到屏幕显示出图像,浏览器经历了一个非常复杂的流水线。
V8 引擎(Chrome 和 Node.js 的核心)之所以快,是因为它摒弃了传统的'解释执行',采用了 JIT (Just-In-Time) 即时编译技术。
核心执行流程:
面试加分点:为什么 V8 提倡写'类型确定'的代码?因为类型一旦变化,就会触发 Deoptimization,导致性能陡降。这也是为什么 TypeScript 在大型项目中能间接提升性能(通过规范类型减少 V8 的猜疑)。
事件循环是 JS 实现非阻塞 I/O 的核心。由于 JS 是单线程的,它必须通过一个机制来协调同步代码和异步任务。
任务分类:
script (整体代码), setTimeout, setInterval, I/O, UI rendering。Promise.then, MutationObserver, process.nextTick。执行顺序 (Event Loop Tick)
代码实战分析:
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。
如果说 HTTP 缓存是'自动挡',那么 Service Worker 就是'手动挡'。它是运行在浏览器后台的独立线程。Service Worker 的地位:它充当了浏览器与网络之间的代理服务器。它可以拦截网络请求,并根据策略决定是走网络、走缓存,还是直接返回一段自定义内容。
关键特性:
postMessage 与主线程通信。生命周期与缓存结合:
// service-worker.js 示例
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// 如果缓存命中,直接返回;否则走网络请求
return response || fetch(event.request);
})
);
});
总结:三者的联动


2014 年前的前端开发环境:
<script> 标签手动引入,依赖管理混乱

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



require()、import 等语句

babel-loader:ES6+ → ES5css-loader:处理 CSS 中的 @import 和 url()style-loader:将 CSS 注入到 DOMfile-loader:处理文件资源(图片、字体)url-loader:小文件转 Base64,大文件走 file-loaderPlugins 与 Loaders 的区别:


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

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| 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 |

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 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 |
Loader 执行顺序:

详细说明:
| 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 模块示例:



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

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| type: 'asset' | 字符串 | 自动内联/外置 | 'asset' |
| parser.dataUrlCondition | 对象 | 内联条件 | { maxSize: 8192 } |
| generator.filename | 字符串 | 输出文件名 | 'images/[name].[hash:8][ext]' |
| use | 数组 | 额外处理 loader | ['image-webpack-loader'] |
文件大小决策逻辑:

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

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

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 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);
}

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| 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>

详细说明:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| 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' |

详细说明:
| 变量名 | 说明 | 示例值 |
|---|---|---|
| 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();
}

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 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') |
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
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 |

详细说明:
| 配置项 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 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';
}
}
}
}

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


库开发注意事项:
.d.ts 文件





长期缓存策略:
[contenthash] 确保内容变化时文件名变化

基于我们之前的 Webpack 讨论,现在深入探讨 Vite。前端构建工具的范式转变,从'打包'转向'按需编译'
Webpack 在开发环境中的问题:
// Webpack 的开发流程
1. 打包整个应用 → 生成 bundle.js
2. 启动本地服务器
3. 每次修改代码 → 重新打包 → 热更新
4. 项目越大,打包时间越长
性能数据对比:
'按需编译' vs '预先打包':
Webpack:预先打包
所有模块先打包成 bundle.js,浏览器下载并执行
Vite:按需编译
浏览器请求时,Vite 动态编译模块并返回
无需等待整个应用打包完成
关键优势:
传统 Webpack 开发流程:
浏览器请求 → Webpack 打包 → 生成 bundle → 返回 HTML → 执行 JS
↓
每次修改都重复
Vite 开发流程:
浏览器请求 → Vite 按需编译 → 动态返回 ESM 模块
↓
只编译当前需要的模块
详细流程:
/src/main.js 时:
// node_modules 中的依赖通常是 CommonJS 或 UMD 格式
// Vite 需要将它们转换为 ESM
// 转换前(CommonJS)
module.exports = { default: 'value' };
// 转换后(ESM)
export default { default: 'value' };
预构建的好处:
node_modules/.vite 目录为什么使用 Rollup?
// 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/'
]
}
}
});
# .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;
}
// 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;
}
}
]
});
// 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: []
});
| 特性 | Webpack | Vite | 优势方 |
|---|---|---|---|
| 冷启动时间 | 随项目增大而增长 | 几乎恒定(<1 秒) | Vite |
| 热更新速度 | 1-5 秒 | <100ms | Vite |
| 配置复杂度 | 高(需要大量配置) | 低(开箱即用) | Vite |
| TypeScript 支持 | 需要配置 loader | 原生支持 | Vite |
| CSS 处理 | 需要配置 loader | 原生支持 | Vite |
| 代码分割 | 手动配置 | 自动优化 | Vite |
| Tree Shaking | 需要配置 | Rollup 原生支持 | Vite |
| 特性 | Webpack | Vite (Rollup) |
|---|---|---|
| Tree Shaking | 支持,但需要配置 | 支持,更彻底 |
| 代码分割 | 支持,配置复杂 | 支持,自动优化 |
| 输出格式 | 多种 | 多种,更灵活 |
| 构建速度 | 较慢 | 较快(Rollup 效率高) |
| 包大小 | 较大 | 较小(Tree Shaking 更优) |
// 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()]
});
// 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
}
}
}
});
// 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
});
optimizeDeps 预构建@vitejs/plugin-legacyoptimizeDeps 缓存
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online