Vite:什么是bundleless?哪些要打包,哪些不要打包?依赖预构建是什么?依赖预构建如何减少网络请求的?esbuild 又是什么?

文章目录

  • 一、什么是bundleless?哪些要打包,哪些不要打包?
      • [1. 核心理念:什么是 Bundleless?](#1. 核心理念:什么是 Bundleless?)
      • [2. 哪些要打包?哪些不打包?](#2. 哪些要打包?哪些不打包?)
        • [**A. 哪些不要打包(开发阶段)?**](#A. 哪些不要打包(开发阶段)?)
        • [**B. 哪些需要打包?**](#B. 哪些需要打包?)
      • [3. 开发环境 vs. 生产环境](#3. 开发环境 vs. 生产环境)
      • 总结
  • 二、依赖预构建是什么?
      • [1. 为什么要预构建?(解决两大痛点)](#1. 为什么要预构建?(解决两大痛点))
        • [痛点一:格式兼容性(CJS 转换)](#痛点一:格式兼容性(CJS 转换))
        • [痛点二:网络性能(减少 HTTP 请求爆炸)](#痛点二:网络性能(减少 HTTP 请求爆炸))
      • [2. 它是怎么工作的?](#2. 它是怎么工作的?)
      • [3. 什么时候会触发重新构建?](#3. 什么时候会触发重新构建?)
      • 总结
  • [三、依赖预构建如何减少网络请求的?esbuild 又是什么?](#三、依赖预构建如何减少网络请求的?esbuild 又是什么?)
      • [1. 为什么打包后就不需要 600 个请求了?](#1. 为什么打包后就不需要 600 个请求了?)
      • [2. esbuild 是什么?](#2. esbuild 是什么?)
        • [**它为什么能比 Webpack 快 10-100 倍?**](#它为什么能比 Webpack 快 10-100 倍?)
      • [3. esbuild 在 Vite 中的角色](#3. esbuild 在 Vite 中的角色)
      • 总结
  • [四、既然要遍历源代码,那不就是在构建依赖图吗?这和 Webpack 有什么区别?](#四、既然要遍历源代码,那不就是在构建依赖图吗?这和 Webpack 有什么区别?)
      • [1. 扫描 vs 构建:目的不同](#1. 扫描 vs 构建:目的不同)
      • [2. 深度不同:只停留在"入口"级别](#2. 深度不同:只停留在“入口”级别)
      • [3. 为什么说"只关注 node_modules"?](#3. 为什么说“只关注 node_modules”?)
      • [4. 形象的比喻](#4. 形象的比喻)
      • [💡 职业面试深度解析](#💡 职业面试深度解析)
  • [五、 Vite 在中间做了一层"即时翻译](#五、 Vite 在中间做了一层“即时翻译)
      • [1. 核心机制:利用浏览器的 Native ESM](#1. 核心机制:利用浏览器的 Native ESM)
      • [2. 它是如何识别并"翻译"的?](#2. 它是如何识别并“翻译”的?)
      • [3. 为什么业务代码不打包,依赖(node_modules)却要预打包?](#3. 为什么业务代码不打包,依赖(node_modules)却要预打包?)
        • [**原因一:CommonJS 兼容性**](#原因一:CommonJS 兼容性)
        • [**原因二:减少网络请求(HTTP 瀑布流效应)**](#原因二:减少网络请求(HTTP 瀑布流效应))
      • [4. 代码展示:Vite 转换后的长相](#4. 代码展示:Vite 转换后的长相)
      • [5. 总结:Vite 的"不打包"哲学](#5. 总结:Vite 的“不打包”哲学)
  • 六、面试题:vite的构建过程了解吗?说说其实现原理
      • [1. 开发环境原理:基于 Native ESM 的 Bundleless](#1. 开发环境原理:基于 Native ESM 的 Bundleless)
        • [**A. 依赖预构建 (Dependency Pre-bundling)**](#A. 依赖预构建 (Dependency Pre-bundling))
        • [**B. 基于浏览器的按需编译**](#B. 基于浏览器的按需编译)
      • [2. 生产环境原理:基于 Rollup 的高度集成](#2. 生产环境原理:基于 Rollup 的高度集成)
      • [3. Vite 构建流程总结](#3. Vite 构建流程总结)
      • [4. 面试话术建议](#4. 面试话术建议)

一、什么是bundleless?哪些要打包,哪些不要打包?

简单来说,Vite(法语意为"快速")是旨在打破 Webpack 等传统工具"越做越慢"困境的新一代前端构建工具。它不仅是一个构建器,更是一个极速的开发服务器。


1. 核心理念:什么是 Bundleless?

在传统的 Webpack 时代,无论你改动哪行代码,构建工具通常都需要先抓取、编译并打包(Bundle)你的整个应用,然后才能在浏览器中预览。当项目变得巨大时,热更新(HMR)往往需要几秒甚至十几秒,体验非常痛苦。

Bundleless(无打包) 则是 Vite 在开发阶段的核心思路:

  • 利用原生 ESM: 现代浏览器(Chrome、Edge、Safari 等)已经支持原生的 import 语法。
  • 按需加载: 当你在代码中写下 import { func } from './util.js' 时,浏览器会直接向服务器发起一个 HTTP 请求。Vite 只需在收到请求时,实时处理(转换)该文件并返回。
  • 结果: 无论你的项目有 10 个文件还是 10,000 个文件,启动速度几乎是一样快的,因为它根本不需要在启动时进行整包构建。

2. 哪些要打包?哪些不打包?

Vite 的聪明之处在于它并没有完全抛弃"打包",而是通过依赖预构建(Dependency Pre-bundling)区分了对待:

A. 哪些不要打包(开发阶段)?
  • 业务代码(Source Code): 你写的 .vue.tsx.scss 文件。
  • 处理方式: Vite 对其进行"非打包"处理。浏览器请求哪个,Vite 转换哪个。
  • 优势: 修改代码后的热更新(HMR)极快,因为只涉及单个文件的重新请求。
B. 哪些需要打包?

即使在开发阶段,Vite 也会使用极快的 esbuild 对以下内容进行预打包:

  • 第三方依赖(Node Modules): 比如 lodash-esreactvue 等。
  • 原因 1:格式转换。 很多库仍然以 CommonJS (CJS) 格式发布,浏览器认不出来,Vite 必须先把它们转成 ESM。
  • 原因 2:减少请求数。 有些库(如 lodash-es)内部包含几百个小文件,如果不预先打成一个包,浏览器一次性发起几百个 HTTP 请求,网络层会直接崩溃。

3. 开发环境 vs. 生产环境

这是初学者最容易产生误解的地方:Vite 只有在开发环境是 Bundleless 的。

  • 开发环境(Development): 基于原生 ESM,不打包业务代码,追求极致的反馈速度。
  • 生产环境(Production): 使用 Rollup 进行完整打包。
  • 为什么? 尽管 ESM 已经普及,但在生产环境下,由于网络延迟、浏览器并发连接限制等原因,加载成千上万个散碎的小文件依然比加载几个优化过的 Bundle 包要慢得多。为了获得最佳的页面加载性能(如 Tree Shaking、代码分割、压缩),打包依然是目前的最优解。

总结

Vite 的本质是"空间换时间""按需消费"的结合。

特性 传统工具 (Webpack) Vite
启动方式 先打包,再启动 Server 先启动 Server,再按需编译
热更新 (HMR) 随项目规模变大而变慢 恒定速度,与规模无关
开发环境核心 基于 Bundle 基于原生 ESM (Bundleless)
依赖处理 统一打包 预构建 (esbuild)

简单来说,依赖预构建(Dependency Pre-bundling) 是 Vite 在启动开发服务器之前,先对你项目中的 node_modules 依赖进行的一次"提前加工"。

虽然 Vite 的核心卖点是 Bundleless(不打包业务代码),但为了兼容性和性能,它必须对第三方依赖进行特殊的预处理。


二、依赖预构建是什么?

1. 为什么要预构建?(解决两大痛点)

痛点一:格式兼容性(CJS 转换)
  • 背景: 浏览器原生的 ESM 只能识别 export/import 语法。
  • 问题: 许多老牌或流行的 npm 包(如 react)仍然以 CommonJS (CJS) 格式发布(使用 module.exportsrequire)。
  • 方案: Vite 会在启动前使用 esbuild 将这些 CJS 格式的依赖转换为 ESM 格式,确保浏览器能够直接加载它们。
痛点二:网络性能(减少 HTTP 请求爆炸)
  • 背景: 有些 ESM 格式的包内部结构极其复杂。
  • 问题: 比如 lodash-es。当你 import { debounce } from 'lodash-es' 时,如果不处理,浏览器会发现这个文件内部又引用了 600 多个其他小文件。浏览器会瞬间发起 600+ 个 HTTP 请求
  • 方案: Vite 将 lodash-es 预打包成一个单一的模块。这样浏览器只需要发起 1 个 请求即可。

2. 它是怎么工作的?

  1. 扫描: Vite 启动时会扫描你的源代码,找出所有通过 import 引入的第三方依赖。
  2. 构建: 使用 esbuild(用 Go 语言编写,速度比 Webpack 快 10-100 倍)将这些依赖打包成一个个 ESM 模块。
  3. 缓存:
  • 文件系统缓存: 构建好的依赖会存在 node_modules/.vite 目录下。除非你的 package.jsonlock 文件或 vite.config.js 发生变化,否则下次启动直接用缓存,瞬间启动
  • 浏览器缓存: Vite 会通过 HTTP 头(max-age=31536000, immutable)强缓存这些依赖,除非你手动刷新或依赖版本变了,否则浏览器不会再次请求。

3. 什么时候会触发重新构建?

如果发生以下情况,Vite 会自动重新运行预构建:

  • package.json 中的 dependencies 发生变化。
  • 包管理器的 lock 文件(如 package-lock.json, pnpm-lock.yaml)发生变化。
  • vite.config.js 中相关的配置项改变。

小技巧: 如果你发现依赖没生效,或者想手动强制刷新,可以删除 node_modules/.vite 目录,或者启动时加上参数:npx vite --force

总结

依赖预构建 就像是 Vite 的"安检与打包站":它把来自 npm 世界里各种乱七八糟、碎片化的依赖,统一规整为浏览器喜欢的、高性能的单一 ESM 格式,从而保证了后续业务代码"无打包"开发的丝滑体验。


三、依赖预构建如何减少网络请求的?esbuild 又是什么?

1. 为什么打包后就不需要 600 个请求了?

这是一个关于"快递包裹"的类比:

  • 如果不处理(原生 ESM): 浏览器就像一个严谨的办事员。当你 import { debounce } 时,它下载 debounce.js,打开一看发现里面有 import { baseDelay },于是它又去下载 baseDelay.js...... 如此往复。由于 lodash-es 这种库依赖嵌套很深,浏览器必须递归地发起 600 多次"请求-等待-解析-再请求"的过程。即使是 HTTP/2,并发这么多请求也会产生巨大的解析开销。
  • 预打包后(Vite 的做法): esbuild 会提前把这 600 多个小文件的代码合并到一个文件 (例如 node_modules/.vite/deps/lodash-es.js)中。
  • 结果: 浏览器只需要发起 1 次 HTTP 请求,就把原本散落在 600 个文件里的代码一次性拿到了。
  • 关键点: 打包确实要把这 600 个文件的内容"包裹"进去,但它是在服务器端(你的电脑上)瞬间合并完成的。对浏览器来说,它只看到了一个大文件,避开了漫长的网络往返时间(RTT)。

为什么依赖与构建可以减少网络请求?

2. esbuild 是什么?

esbuild 是一个极速的 JavaScript 打包和压缩工具,由 Go 语言编写。它的唯一目标就是:快,快到极致。

它为什么能比 Webpack 快 10-100 倍?
  1. 编译型语言 vs. 解释型语言:
  • Webpack、Rollup 是用 JavaScript 写的。JS 需要经过 JIT 引擎解释执行,有垃圾回收(GC)开销。
  • esbuild 是用 Go 写的。Go 直接编译成机器码运行,且能更底层地控制内存利用,没有频繁的 GC 阻塞。
  1. 多核并行利用:
  • JS 是单线程的(虽然可以用 worker,但数据传输成本高)。
  • Go 天生支持高并发。esbuild 的算法是高度并行化的,解析、转换、生成代码的过程会跑满你 CPU 的所有核心。
  1. 重写一切:
  • Webpack 依赖了大量的第三方插件和库。
  • esbuild 的作者自己重写了所有的功能(从语法解析到代码压缩)。这意味着它可以实现全流程的架构一致性,避免了数据在不同插件之间传递时反复序列化、反序列化的开销。
  1. 高效的内存利用:
  • esbuild 在解析代码时,会尽可能减少对抽象语法树(AST)的多次遍历。很多操作是在一次遍历中完成的,极大地节省了 CPU 和内存。

3. esbuild 在 Vite 中的角色

虽然 esbuild 很快,但它为了速度牺牲了一些灵活性(比如它不支持较旧的 HMR 机制,也不支持某些复杂的插件逻辑)。因此,Vite 采取了"混搭方案":

  • 开发阶段(依赖预构建): 追求速度,用 esbuild 。把 node_modules 里的陈年旧账瞬间理清。
  • 生产阶段(打包发布): 追求体积更小、更稳定、生态更全,用 Rollup。虽然慢一点,但生产环境的安全和优化(如更精细的 Tree-shaking)更重要。

总结

  • 预构建是为了把"一盘散沙"的依赖聚成"一块砖",减少浏览器网络请求的压力。
  • esbuild 则是利用了 Go 语言的并发优势和极致的代码架构,在 Vite 启动的一瞬间,完成了以往 Webpack 需要跑几十秒的工作。

四、既然要遍历源代码,那不就是在构建依赖图吗?这和 Webpack 有什么区别?

这里的核心区别在于 "扫描(Scan)""构建(Bundle)" 的深度与目的完全不同。我们可以通过以下三个层面来拆解:


1. 扫描 vs 构建:目的不同

  • Webpack 的构建:它是为了"出货"。它必须完整地解析每一个文件,处理 Loader(转换 Vue/TS/CSS),建立精细的依赖关系,最后把所有代码揉碎了再拼接成 Bundle。
  • Vite 的扫描 :它是为了"点名"。esbuild 像是一个快速巡逻的哨兵,它扫过你的源码,只为了寻找 import 语句中那些指向 node_modules 的名字(比如 vue, lodash-es)。它不需要理解你的业务逻辑,不需要处理复杂的转换,只要确认"哦,你用了这几个包",然后就立刻转身去处理那几个包。

2. 深度不同:只停留在"入口"级别

Webpack 会深入每一个业务模块的每一个细节。而 Vite 的扫描阶段:

  1. 只找裸模块导入 :它关注的是 from 'vue' 这种非相对路径的导入。
  2. 不处理业务转换 :esbuild 在扫描阶段会跳过对业务代码的深度编译。它利用 Go 语言编写的极速特性,仅通过静态分析提取出依赖列表。
  3. 依赖图的性质 :Vite 也会建立依赖图,但它的业务代码依赖图是交给浏览器 去按需构建的(当浏览器请求某个 .vue 文件时,Vite 才临时编译并返回)。预构建阶段只负责把 node_modules 里的那些"大块头"先打点好。

3. 为什么说"只关注 node_modules"?

这句话的准确含义是:扫描的终点是 node_modules。

  • 当 Vite 扫描到 import App from './App.vue' 时,它发现这是业务代码,记录一下路径就跳过了。
  • 当扫描到 import { ref } from 'vue' 时,它发现这是第三方依赖。这时候,它会深入 node_modules/vue 内部,把它作为一个整体进行预构建。

结论是: Vite 确实像 Webpack 一样遍历了你的源码入口,但它对源码的遍历是"浅尝辄止"的。它把最耗时的"构建依赖图并打包"的工作,针对 业务代码推迟到了浏览器请求时,针对第三方依赖则交给 esbuild 一次性快速预处理。


4. 形象的比喻

  • Webpack (传统构建):像是一个严谨的厨师。他在开店前,必须把土豆削皮、切丝、炒熟,把肉炖烂,最后装成一份份盒饭(Bundle)。客人来的时候,直接拿盒饭。
  • Vite (扫描 + 预构建):像是一个现代超市。
  • 扫描阶段:经理看了一眼进货单(扫描源码),确认需要卖土豆和肉(发现依赖)。
  • 预构建阶段:超市雇了最快的工人(esbuild)把成吨的土豆先洗干净、装成大袋(把 CJS 转成 ESM 并合并请求)。
  • 运行阶段:客人(浏览器)进店说"我要一份青椒炒肉",超市才现场把青椒和已经洗好的肉切了下锅(按需编译业务代码)。

💡 职业面试深度解析

如果面试官追问:"Vite 扫描源码时,如果我的依赖是动态导入(Dynamic Import)怎么办?"

你可以从容回答:

"Vite 的扫描器同样能识别 import() 语法。即使是动态导入的依赖,也会被纳入预构建的范畴。Vite 这样做是为了确保在浏览器真正执行到那行代码、发出请求之前,该依赖已经以 ESM 的格式在 .vite/deps 中准备就绪,从而避免运行时的阻塞。"

五、 Vite 在中间做了一层"即时翻译

1. 核心机制:利用浏览器的 Native ESM

现代浏览器(Chrome, Edge, Safari 等)已经原生支持了 ES 模块。当你写下:

html 复制代码
<script type="module" src="/src/main.ts"></script>

浏览器在解析到这行代码时,会自动向服务器(Vite Dev Server)发送一个网络请求 ,索要 /src/main.ts


2. 它是如何识别并"翻译"的?

虽然浏览器发起了请求,但如果服务器直接把原始的 .vue.ts 文件丢给浏览器,浏览器会报错(MIME 类型不符或语法错误)。

Vite 的角色是一个"高性能透明代理服务器":

  1. 拦截请求 :Vite 拦截浏览器发出的每一个 .vue.ts.scss 请求。
  2. 即时编译 :Vite 调用内置的编译器(如 esbuild 处理 TS,vue/compiler-sfc 处理 Vue):
  • .ts 转换成标准的 .js
  • .vue 文件拆解,将其模板、脚本、样式分别转换成浏览器能识别的 JS 模块。
  1. 返回结果 :Vite 修改响应头(将 Content-Type 设为 application/javascript),然后把转换后的代码发给浏览器。

结论: 在浏览器看来,它请求的是一个 .js 文件;而在你看来,你只是写了一个 .vue 文件。


3. 为什么业务代码不打包,依赖(node_modules)却要预打包?

你可能会问:既然业务代码可以按需编译,为什么 axioslodash 这种第三方依赖还要"预打包"?

原因一:CommonJS 兼容性

很多老牌 npm 包(如 react)使用的是 CommonJS 格式(module.exports),浏览器完全不认识。Vite 必须在启动前把它们统一转成 ESM(export/import) 格式。

原因二:减少网络请求(HTTP 瀑布流效应)

有些库内部极其碎片化。比如 lodash-es 内部有几百个小文件。

  • 如果不预打包 :浏览器请求 lodash 时,会瞬间触发几百个网络请求,浏览器直接卡死。
  • 预打包后 :Vite 把这几百个文件合并成一个大模块。浏览器只需要请求一次,性能提升巨大。

4. 代码展示:Vite 转换后的长相

假设你有一个 App.vue

vue 复制代码
<template>
  <h1>{{ msg }}</h1>
</template>
<script setup>
const msg = 'Hello Vite!'
</script>

当你打开浏览器控制台的网络面板(Network),你会发现浏览器收到的 App.vue?type=script 变成了这样:

javascript 复制代码
// 已经被 Vite 转换成了标准的 JS
const _sfc_main = {
  setup(__props) {
    const msg = 'Hello Vite!'
    return { msg }
  }
}
import { display as _display } from "/node_modules/.vite/deps/vue.js"
// ... 渲染函数等逻辑
export default _sfc_main

5. 总结:Vite 的"不打包"哲学

  • Webpack 是"推"模式:先把所有东西塞进一个大包,推给浏览器。
  • Vite 是"拉"模式:浏览器需要什么,就向服务器拉取什么。Vite 只是在拉取的路中间,顺手把代码"翻译"了一下。

一句话总结: 识别文件的不是浏览器,而是 Vite 拦截器 ;它利用浏览器对 ESM 的原生支持,实现了极其高效的按需动态翻译

这种"即时转换"比 Webpack 这种"预先全量打包"快的原因在于:转换一个文件的时间几乎可以忽略不计。

在面试中,回答 Vite 构建原理的核心在于区分 "开发环境(Development)""生产环境(Production)"。Vite 的高明之处在于它在不同的场景下用了两套完全不同的逻辑。


六、面试题:vite的构建过程了解吗?说说其实现原理

1. 开发环境原理:基于 Native ESM 的 Bundleless

Vite 在开发环境下的核心理念是:不打包(Bundleless)

A. 依赖预构建 (Dependency Pre-bundling)

在服务器启动前,Vite 会先扫描源码中的第三方依赖(如 react, lodash)。

  • 目的:将 CommonJS 格式转为 ESM 格式;将包含数百个模块的库合并成单个模块。
  • 实现 :使用 esbuild(Go 编写),速度比 JS 编写的打包器快 10-100 倍。
  • 结果 :依赖被缓存到 node_modules/.vite 中。
B. 基于浏览器的按需编译
  1. 启动 Server:Vite 启动一个开发服务器,不进行任何打包动作,瞬间完成启动。
  2. 浏览器请求 :当浏览器解析到 import { createApp } from './main.ts' 时,会向服务器发起 HTTP 请求。
  3. 即时转换(On-the-fly) :Vite 拦截请求,发现后缀是 .ts.vue,立即现场调用编译器(如 esbuildvue/compiler-sfc)将其转换成标准的 JavaScript ESM
  4. 返回结果:浏览器拿到转换后的 JS 模块,直接运行。

2. 生产环境原理:基于 Rollup 的高度集成

虽然开发环境下不打包,但生产环境为了减少 HTTP 请求、支持 Tree-shaking 和 压缩,Vite 选择了打包

  • 构建工具 :生产环境使用 Rollup
  • 原因:Rollup 在插件生态和 Tree-shaking 优化上比当时的 esbuild 更成熟、更稳定。
  • 流程
  1. 解析入口 :从 index.html 开始(Vite 以 HTML 为入口)。
  2. 依赖解析:利用 Rollup 的插件机制处理各种文件(CSS 抽离、图片压缩)。
  3. 分包优化:生成针对现代浏览器的 ESM 产物,同时也支持多页应用和异步加载。

3. Vite 构建流程总结

我们可以把 Vite 的构建流程拆解为以下几个关键阶段:

阶段 关键动作 核心技术
1. 配置初始化 合并 vite.config.js、环境变量、插件配置。 defineConfig
2. 依赖预构建 扫描 node_modules,将非 ESM 模块转为 ESM 并合并请求。 esbuild
3. 启动 Dev Server 开启一个本地服务器,监听文件。 Node.js Connect
4. 拦截与转换 拦截浏览器 HTTP 请求,按需将源码翻译成 JS。 Middlewares
5. 热更新 (HMR) 文件变化时,只向浏览器推送变更的消息,浏览器只重新请求改变的模块。 WebSocket
6. 生产打包 扫描全部源码,进行 Tree-shaking 和混淆压缩。 Rollup

4. 面试话术建议

面试官问:"说一下 Vite 的原理?"

回答:

"Vite 的核心原理在于**'动静分离'
开发环境下,它利用了浏览器的原生 ESM 支持。Vite 启动时不需要打包,而是直接启动一个开发服务器。当浏览器请求模块时,Vite 才对源码进行 即时编译**。为了解决第三方库的 CommonJS 兼容性和网络请求过多的问题,它会使用 esbuild 进行预构建。这使得 Vite 的启动速度和 HMR 响应速度基本不随项目规模增大而变慢。

生产环境 下,Vite 采用 Rollup 进行打包。虽然 esbuild 很快,但 Rollup 在代码分割、CSS 处理和插件生态上更成熟。Vite 将 Rollup 封装在内部,并预设了大量的最佳实践配置,从而兼顾了开发体验和生产环境的产物质量。"

追问:为什么生产环境不直接用 esbuild 打包?

回答:

虽然 esbuild 极其快,但它在生产环境所需的 CSS 代码分割(CSS Code Splitting)复杂的代码混淆优化 方面目前还不如 Rollup 完善。Vite 团队为了保证产物的极致优化,选择了 Rollup 作为生产打包引擎。

相关推荐
Rooting++1 小时前
vue2+webpack打包优化的相关问题
前端·webpack·node.js
alxraves1 小时前
超声图像前端信号处理的关键技术
前端·fpga开发·信号处理
问心无愧05131 小时前
ctf show web入门47
前端·笔记
web守墓人1 小时前
【神经网络】js版本的Pytorch,estorch重磅发布
前端·javascript·人工智能·pytorch·深度学习·神经网络
贫民窟的勇敢爷们1 小时前
Vue的渐进式特性,让前端开发更具灵活性
前端·javascript·vue.js
问心无愧05131 小时前
ctf show web入门81
前端·笔记
ZC跨境爬虫1 小时前
跟着MDN学HTML_day_49:(ShadowRoot接口)
前端·javascript·ui·html·ecmascript·媒体
小则又沐风a1 小时前
初步了解进程的概念
java·linux·服务器·前端
审判长烧鸡1 小时前
【前端】npm audit fix 修复漏洞时的具体逻辑
前端·npm