会 Vue / React,上手 Electron 真没那么难:前端开发者需要补齐的核心知识

分享我最近做的一个 AI Mind 项目。

GitHub:github.com/HWYD/ai-min...

线上体验:ai.hwyblog.cloud/instant-min...

AI Mind 是一个持续迭代中的 Next.js AI Chat 项目。从最基础的本地聊天开始,逐步加入流式协议、工具调用、MCP、Skill 和 Agent 能力。

如果你对这个项目感兴趣,或者这篇文章对你有一点帮助,也欢迎顺手到 GitHub 帮 AI Mind 点个 Star⭐,这会是对我继续更新很大的鼓励。

如果已经做过一段时间的 Vue 或 React,第一次接触 Electron 时,通常会有两种完全相反的感觉。

一种是:

Electron 不就是给网页套一个桌面窗口吗?

另一种是:

桌面开发是不是意味着又要重新学一整套技术?

实际做下来,我觉得这两个判断都不太准确。

Electron 对前端开发者最友好的地方在于:页面开发这一层,依然是熟悉的 Web 技术体系。

React、Vue、CSS、状态管理、路由、网络请求、组件库,这些经验都可以继续使用。真正需要补的,是过去做浏览器应用时很少主动处理的另一部分:

text 复制代码
Main Process
BrowserWindow
Preload
IPC
Node.js / OS 能力
应用与窗口生命周期
桌面安全边界
打包与安装
Windows / macOS 平台差异

所以,如果已经会 Vue / React,学习 Electron 并不是从零开始。

更准确地说:

原来的前端能力负责"页面怎么写",Electron 要补的是"这个页面如何安全地作为一个真正的桌面应用运行起来"。

这篇文章不会把 Electron API 从头到尾过一遍,而是站在 Web 前端开发者的视角,把真正值得优先掌握的知识点串起来。


1. Electron 和 React / Vue 到底是什么关系

先把最容易混淆的一件事讲清楚:

Electron 和 React / Vue 并不是同一层的技术。

React 和 Vue 主要解决 UI 层的问题:

text 复制代码
组件
状态
事件
路由
页面渲染
前端交互

Electron 解决的则是桌面应用运行环境:

text 复制代码
创建桌面窗口
管理应用生命周期
调用操作系统能力
进程间通信
访问本地文件
系统菜单
通知
剪贴板
打包与安装

普通 Web 应用可以简单理解成:

text 复制代码
Chrome
└── React / Vue
    ├── Components
    ├── Router
    ├── State
    └── Fetch

换成 Electron 后,大致变成:

text 复制代码
Electron Desktop Runtime
├── Main Process
│
├── Preload
│
└── Renderer Process
    └── React / Vue
        ├── Components
        ├── Router
        ├── State
        └── Fetch

所以对于前端开发者来说,Renderer 这一层几乎是熟悉的。

Electron 真正新增的是外面的桌面运行时。它内部集成了 Chromium 和 Node.js,可以粗略理解为:

text 复制代码
Chromium
  → 负责 Web 页面渲染

Node.js + Electron APIs
  → 提供桌面运行时和操作系统能力

于是原本运行在浏览器里的前端页面,可以被放进真正的桌面应用,并运行在 Windows、macOS、Linux 等平台。

这也是为什么 Web 前端转 Electron,学习成本通常没有想象中那么高。


2. Main、Renderer、Preload:先搞懂三个核心角色

学习 Electron,我认为最先应该搞懂的不是 API,而是三个角色:

text 复制代码
Main
Renderer
Preload

这里需要说明一下:

Main 和 Renderer 属于 Electron 的多进程模型,而 Preload 本身并不是一个独立进程,它是在 Renderer 页面加载前执行的一段特殊脚本。

但从开发职责上,把它们作为三个角色理解最直观。

2.1 Main Process:桌面应用的管理者

Main Process,也就是主进程。

它负责管理整个 Electron 应用,常见职责包括:

text 复制代码
应用启动 / 退出
创建和销毁窗口
系统菜单
Tray
原生 Dialog
全局快捷键
窗口生命周期
IPC 处理
部分本机能力调用

最简单的 Electron 窗口代码大概是这样:

ts 复制代码
import { app, BrowserWindow } from 'electron'

const createWindow = () => {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
  })

  win.loadURL('http://localhost:5173')
}

app.whenReady().then(createWindow)

这里的 BrowserWindow 可以理解成:

由 Electron 主进程管理的 Chromium 页面窗口。

以前做普通 Web 项目时,浏览器窗口由 Chrome 帮我们创建和管理;到了 Electron,这件事开始由自己的应用负责。

2.2 Renderer Process:依然是熟悉的前端页面

Renderer 就比较容易理解了。

它本质上仍然是浏览器渲染环境。

例如 React:

tsx 复制代码
function App() {
  return (
    <main>
      <h1>Hello Electron</h1>
      <button>打开文件</button>
    </main>
  )
}

Vue 也是一样。

原来的技术基本都可以继续使用:

text 复制代码
React / Vue
Tailwind CSS
Pinia / Zustand
React Router / Vue Router
TanStack Query
组件库
Markdown Renderer
WebSocket / SSE / Fetch

所以,如果已经熟悉 Vue / React,Electron 的 UI 开发并不会突然变成一个陌生领域。

真正需要适应的是:

Renderer 不再只是一个浏览器页面,它运行在桌面应用的进程模型里。

2.3 Preload:Web 页面和本机能力之间的桥梁

Preload 是 Electron 初学阶段非常值得重点理解的一层。

它运行在 Renderer 页面加载之前,可以在受控环境中访问部分 Electron / Node 能力,再通过 contextBridge 暴露有限接口给页面。

比如 React 页面想打开系统文件选择器,比较合理的调用链是:

text 复制代码
React Renderer
      ↓
window.desktop.openFile()
      ↓
Preload
      ↓
IPC
      ↓
Main Process
      ↓
dialog.showOpenDialog()

也就是说,Renderer 不直接操作 Electron Main,而是通过一层明确的桥接接口调用。

三个角色放到一起:

text 复制代码
┌─────────────────────────────┐
│        Main Process         │
│                             │
│ Window / File / Dialog / OS │
└─────────────┬───────────────┘
              │
             IPC
              │
       ┌──────┴──────┐
       │   Preload   │
       │ 受控能力桥梁 │
       └──────┬──────┘
              │
┌─────────────┴───────────────┐
│       Renderer Process      │
│                             │
│      React / Vue / CSS      │
│ Router / State / Fetch ...  │
└─────────────────────────────┘

如果这张图能理解,Electron 的核心心智模型其实已经建立了一半。


3. BrowserWindow:Web 页面怎么变成桌面窗口

普通前端项目里,我们很少关心浏览器窗口是谁创建的,Chrome 已经替我们处理好了。

Electron 不一样,应用需要自己创建窗口。

最核心的 API 就是:

ts 复制代码
new BrowserWindow()

例如:

ts 复制代码
const mainWindow = new BrowserWindow({
  width: 1280,
  height: 800,
})

mainWindow.loadURL('http://localhost:5173')

开发阶段通常会加载本地开发服务器;生产环境则可能加载打包后的 HTML、固定线上 URL,或者本地自定义协议页面。具体采用哪种方式,要看项目架构。

BrowserWindow 并不只是一个"网页容器"。

项目真正做下去以后,会逐渐碰到:

text 复制代码
窗口大小
最小宽高
最大化 / 最小化
全屏
多窗口
父子窗口
窗口位置记忆
无边框窗口
自定义标题栏
关闭确认
Windows / macOS 窗口控制差异

这也是从 Web 开发进入桌面开发后,第一个明显的思维变化。

过去主要关注:

text 复制代码
页面生命周期

现在还需要关心:

text 复制代码
应用生命周期
窗口生命周期
Renderer 生命周期

例如:

text 复制代码
用户关闭窗口时,应用是否退出?
macOS 点击关闭按钮后是否保留应用?
第二次启动时创建新窗口还是聚焦旧窗口?
Renderer 崩溃后如何恢复?

这些问题在普通 Web 开发里通常不需要自己处理,但在桌面应用里会变得非常实际。


4. IPC:Renderer 怎么调用本机能力

IPC,全称 Inter-Process Communication,也就是进程间通信。

这是 Electron 开发必须掌握的一部分。

普通 Web 应用最常见的数据调用关系是:

text 复制代码
React / Vue
    ↓
HTTP / Fetch
    ↓
Backend

Electron 里会多一条本地调用链:

text 复制代码
Renderer
   ↓
Preload
   ↓
IPC
   ↓
Main
   ↓
Operating System

例如,我们想实现一个很简单的功能:

点击按钮,弹出系统文件选择器。

Renderer:

ts 复制代码
const result = await window.desktop.openFile()

Preload:

ts 复制代码
contextBridge.exposeInMainWorld('desktop', {
  openFile: () => ipcRenderer.invoke('file:open'),
})

Main:

ts 复制代码
ipcMain.handle('file:open', async () => {
  return dialog.showOpenDialog({
    properties: ['openFile'],
  })
})

于是完整过程就是:

text 复制代码
用户点击按钮
    ↓
React / Vue
    ↓
window.desktop.openFile()
    ↓
Preload
    ↓
ipcRenderer.invoke()
    ↓
ipcMain.handle()
    ↓
dialog.showOpenDialog()
    ↓
结果返回 Renderer

如果做过前后端分离开发,可以把它类比成:

text 复制代码
HTTP API

Frontend
  ↓
Backend

Electron 里则变成:

text 复制代码
IPC API

Renderer
  ↓
Preload
  ↓
Main

当然,两者底层机制并不一样,但架构思路有一个共通点:

不要跨层直接调用,而是通过明确接口通信。

这个理解很重要。

因为 Electron 项目规模一旦变大,IPC 本质上也会形成一套"内部 API"。如果 IPC channel 到处散落、参数随便传、Renderer 可以调用任意 Main 能力,很快就会失控。


5. Preload + contextBridge:不要让页面直接操作 Node.js

这里是很多 Electron 入门 Demo 容易留下坏习惯的地方。

最省事的方式当然是让 Renderer 直接访问 Node.js:

ts 复制代码
fs.readFile(...)

这样写 Demo 非常快。

但真正做桌面应用时,一般不应该让 Renderer 随意拿到 Node.js 和 Electron 的完整能力。

更合理的方式是:

text 复制代码
Renderer

window.desktop.openFile()
window.desktop.saveFile()
window.desktop.getAppVersion()

         ↓

      Preload

         ↓

        IPC

         ↓

       Main

Renderer 只知道自己允许使用的业务能力,它不需要知道:

text 复制代码
fs
path
ipcRenderer
dialog
shell
child_process
Electron internal APIs

例如:

ts 复制代码
contextBridge.exposeInMainWorld('desktop', {
  openFile: () => ipcRenderer.invoke('file:open'),

  saveFile: (content: string) =>
    ipcRenderer.invoke('file:save', content),
})

不建议直接暴露:

ts 复制代码
window.ipcRenderer = ipcRenderer

因为那相当于把通信能力整体交给 Renderer,再让页面自己决定可以调用什么。

更好的设计应该反过来:

先确定页面真正需要哪些能力,再逐项暴露。

例如一个 Markdown 编辑器可能只需要:

text 复制代码
openTextFile()
saveTextFile()
getAppVersion()

那 Preload 就只暴露这三个接口。

这也是 Electron 和普通 Web 开发思维上一个很值得尽早建立的习惯:

重点不是"怎么让页面拥有更多权限",而是"怎么把权限限制到刚好够用"。


6. Electron 比普通 Web 多了哪些能力

Electron 到底能解决什么普通 Web 不方便解决的问题?

最直接的区别,就是应用开始能够调用更多桌面系统能力。

文件与目录

text 复制代码
读取本地文件
写入文件
选择文件
选择目录
保存文件
文件拖放
监听部分本地资源

系统能力

text 复制代码
Clipboard
Notification
Global Shortcut
Tray
Menu
系统信息

窗口能力

text 复制代码
多窗口
窗口置顶
透明窗口
无边框窗口
全屏
窗口位置
自定义标题栏

本地运行能力

text 复制代码
SQLite
本地文件索引
启动子进程
调用 CLI
Python Sidecar
本地 HTTP 服务

再往 AI 应用延伸,就会出现很多有意思的组合。

例如:

text 复制代码
React UI
   ↓
Electron
   ↓
Python / FastAPI
   ↓
ComfyUI
   ↓
Local Image Model

或者:

text 复制代码
React UI
   ↓
Electron
   ↓
Local File System
   ↓
Agent Runtime

甚至:

text 复制代码
Electron
   ├── Remote LLM
   ├── Local Model
   ├── Local Files
   └── Local Tool / MCP

这也是 Electron 在 AI 应用场景里很有价值的原因之一。

Web 技术继续负责 UI,Electron 则让应用可以连接更多本机资源和本地服务。

不过有一点需要始终记住:

Electron 能调用本机能力,不代表这些能力应该全部暴露给 Renderer。

能力越强,边界越要收紧。


7. 前端开发者必须掌握的 Electron 安全边界

如果只是写一个自己使用的小 Demo,Electron 的安全问题可能不会马上暴露出来。

但只要准备真正发布,就应该尽早建立安全意识。

前端开发者可以先记住几个核心原则:

text 复制代码
nodeIntegration = false
contextIsolation = true
按场景启用 sandbox
Preload 只暴露最小 API
IPC 输入必须校验
限制页面 Navigation
限制 Popup
谨慎处理外部链接
Permission 默认拒绝
不要信任远程内容

为什么 Electron 比普通 Web 更需要注意这些?

假设普通网页出现 XSS:

text 复制代码
Web XSS
  ↓
攻击页面环境
  ↓
窃取页面数据 / 冒充用户发请求

已经非常严重。

但如果 Electron Renderer 同时拥有:

text 复制代码
fs
child_process
shell
ipcRenderer

那么风险可能进一步扩大:

text 复制代码
Renderer XSS
     +
Node / OS 权限
     ↓
影响用户本机

所以 Electron 的安全问题,本质上是:

Web 内容和操作系统能力之间必须存在清晰边界。

BrowserWindow 常见的安全方向类似:

ts 复制代码
const win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: false,
    contextIsolation: true,
    sandbox: true,
    preload: PRELOAD_PATH,
  },
})

然后通过 Preload 暴露极少量接口。

IPC 也应该做输入校验。

例如 Renderer 传递文件路径:

ts 复制代码
window.desktop.readFile(path)

Main 不能简单认为 Renderer 传来的内容一定可信,至少需要考虑:

text 复制代码
路径是否合法?
文件类型是否允许?
是否允许访问任意目录?
payload 是否符合约定?
调用来源是否合法?

做 Electron 安全时,一个比较实用的思路是:

把 Renderer 当成不可信的 Web 客户端,把 Main 当成拥有高权限的服务端。

这样很多设计就会自然很多。


8. 从 dev 到安装包:Electron 的构建与发布

前端开发者第一次做 Electron 时,经常会有一个明显的认知变化:

pnpm dev 能打开窗口,离真正把应用发给用户还有很远。

Web 项目的典型流程一般是:

text 复制代码
Source
  ↓
Build
  ↓
Deploy
  ↓
URL

Electron 则更像:

text 复制代码
Source
  ↓
Build
  ↓
Package
  ↓
Platform Artifact
  ↓
Installer
  ↓
Signing
  ↓
Distribution
  ↓
Update

如果使用 Electron Forge,经常会接触几个概念。

Package

将 Electron 应用整理成对应操作系统可以运行的应用目录。

例如:

text 复制代码
Windows executable
macOS .app

Make

基于 Package 结果生成面向用户分发的制品,例如:

text 复制代码
Windows Installer
DMG
ZIP

具体格式取决于 Maker 配置。

Publish

把构建后的制品发布到对应分发渠道。

除此之外,还有一些实际项目迟早会碰到的概念:

text 复制代码
ASAR
App Icon
x64 / arm64
Code Signing
macOS Notarization
Auto Update
CI Build Matrix
Artifact Hash

其中比较容易被低估的是签名和跨平台构建

例如:

text 复制代码
Windows
  → exe / installer
  → code signing

macOS
  → .app / DMG
  → Developer ID signing
  → notarization
  → Gatekeeper

所以桌面应用真正拉开工程复杂度的地方,很多时候并不是 React 页面,而是:

text 复制代码
安装
签名
系统安全机制
版本升级
CI
跨平台兼容

也很容易出现这种情况:

text 复制代码
页面功能:半天
IPC:半天
Windows 打包:继续排查
macOS 签名:再查一晚上资料

这才是真实 Electron 项目后半程经常遇到的问题。


9. 从 Web 思维切换到 Electron 思维

如果已经是 React / Vue 开发者,真正需要适应的变化可以整理成这样:

Web 开发 Electron 开发
Browser Tab App + Window Lifecycle
HTTP API HTTP + IPC
浏览器 Sandbox Electron Process / Security Boundary
localStorage Browser Storage + File / Local DB
Browser Permission Browser + OS Permission
页面路由 页面路由 + Window Navigation
前端 Build Build + Package + Installer
DevTools Renderer DevTools + Main Logs
浏览器负责 Chromium 更新 Electron 版本决定 Chromium
页面刷新 Renderer Reload / Window Recreate
Web 部署 Desktop Distribution

其中我认为最重要的变化可以归纳成四个。

第一,开始关心"进程"

以前更多是在想:

text 复制代码
组件之间怎么通信?

现在还要考虑:

text 复制代码
Renderer 和 Main 怎么通信?

第二,开始关心"权限"

以前更多是:

text 复制代码
浏览器允许这个 API 吗?

现在还要考虑:

text 复制代码
这个能力该不该开放给 Renderer?

第三,开始关心"生命周期"

以前主要是:

text 复制代码
页面 mount / unmount

现在还包括:

text 复制代码
App start
Window create
Window close
App quit
Second instance
Renderer crash

第四,开始关心"交付"

以前关心:

text 复制代码
网站部署成功了吗?

现在还要考虑:

text 复制代码
Windows 能安装吗?
macOS 能打开吗?
架构匹配吗?
签名正确吗?
怎么升级?

所以 Electron 并没有让前端开发失效。

它只是把前端开发的边界继续向外扩了一层。


10. 用一个最小 Demo 串起整个知识体系

学习 Electron,我不太推荐一开始做 Todo List。

因为 Todo List 基本展示不出 Electron 和普通 React / Vue 项目的区别。

更适合入门的是:

桌面 Markdown / TXT 查看器

功能非常简单:

text 复制代码
点击"打开文件"
     ↓
系统文件选择器
     ↓
选择 .md / .txt
     ↓
Main 读取文件
     ↓
IPC 返回
     ↓
React / Vue 展示内容

一个功能就能串起 Electron 最核心的几个知识点。

Renderer

tsx 复制代码
function OpenFileButton() {
  const handleOpen = async () => {
    const result = await window.desktop.openTextFile()

    if (!result) return

    console.log(result.content)
  }

  return <button onClick={handleOpen}>打开文件</button>
}

Renderer 只负责 UI。

它不知道文件系统怎么读取,也不知道 Electron Dialog 怎么调用。

Preload

ts 复制代码
contextBridge.exposeInMainWorld('desktop', {
  openTextFile: () =>
    ipcRenderer.invoke('file:open-text'),
})

Preload 只暴露一个能力:

text 复制代码
openTextFile()

而不是整个 Electron IPC。

Main

ts 复制代码
ipcMain.handle('file:open-text', async () => {
  // 1. 打开系统文件选择器
  // 2. 判断用户是否取消
  // 3. 校验扩展名
  // 4. 读取文件
  // 5. 返回受控 DTO
})

完整结构:

text 复制代码
┌──────────────────┐
│ React / Vue      │
│ 打开文件按钮      │
└────────┬─────────┘
         │
         ↓
┌──────────────────┐
│ Preload          │
│ openTextFile()   │
└────────┬─────────┘
         │ IPC
         ↓
┌──────────────────┐
│ Main Process     │
│ Dialog + fs      │
└────────┬─────────┘
         │
         ↓
     Local File

做完这个 Demo,至少已经实际接触:

text 复制代码
BrowserWindow
Main Process
Renderer Process
Preload
contextBridge
ipcRenderer
ipcMain
Dialog
File System
IPC 边界

然后可以一步一步加功能。

例如:

第二步:保存文件

学习:

text 复制代码
save dialog
fs.writeFile
Renderer → Main 参数传递

第三步:最近文件

学习:

text 复制代码
本地状态
应用数据目录
菜单

第四步:自定义菜单

学习:

text 复制代码
Menu
Main → Renderer 通信

第五步:打包

学习:

text 复制代码
Package
Maker
Icon
ASAR
Windows / macOS artifact

这样学习,会比从 Electron API 文档第一页开始逐项记忆自然很多。


11. 前端开发者学习 Electron 的推荐路线

如果已经掌握 Vue / React,我不建议先花大量时间看完整 Electron API。

可以按照下面的顺序学习。

第一阶段:先跑起来

text 复制代码
React / Vue Renderer
       ↓
BrowserWindow

目标只有一个:

把熟悉的 Web 页面运行到桌面窗口里。

第二阶段:搞懂进程模型

重点掌握:

text 复制代码
Main
Renderer
Preload

知道代码应该放在哪一层。

第三阶段:跑通 IPC

实现一个真实能力,例如:

text 复制代码
打开文件
保存文件
获取 App Version

完整跑通:

text 复制代码
Renderer
   ↓
Preload
   ↓
IPC
   ↓
Main

第四阶段:学习安全边界

重点理解:

text 复制代码
Context Isolation
Sandbox
Node Integration
contextBridge
IPC Validation
Navigation Policy
Permission Policy

这一步值得在入门阶段就做,而不是等项目上线前再补。

第五阶段:理解生命周期

学习:

text 复制代码
app.whenReady()
window-all-closed
activate
before-quit
BrowserWindow lifecycle
single instance

开始真正从"页面"进入"桌面应用"。

第六阶段:真正打一个安装包

不要一直停留在开发模式。

至少做一次:

text 复制代码
Package
   ↓
Make
   ↓
Install
   ↓
Launch

很多 Electron 问题只有打包之后才会出现。

第七阶段:根据项目继续扩展

再往后根据实际项目学习:

text 复制代码
Tray
Global Shortcut
Auto Update
Code Signing
Notarization
SQLite
Native Module
Child Process
Deep Link
Local Server
Python Sidecar
Local AI Runtime

这些都属于按需学习的进阶能力,不需要在刚入门时一次掌握。


12. 总结:难的不是 React / Vue,而是桌面运行时边界

如果已经会 Vue 或 React,Electron 的 UI 开发并不会突然变成一个陌生领域。

原来掌握的:

text 复制代码
组件
CSS
状态管理
路由
请求
工程化
性能优化

依然全部有价值。

真正需要补的是:

text 复制代码
Main Process
BrowserWindow
Preload
IPC
本机能力
安全边界
生命周期
打包发布
跨平台差异

所以我更愿意把 Electron 的学习过程理解成:

text 复制代码
Web Frontend
     +
Desktop Runtime Knowledge
     =
Electron Development

入门阶段最重要的,也不是记住多少 Electron API。

先真正理解下面这条链路:

text 复制代码
React / Vue Renderer
        ↓
      Preload
        ↓
       IPC
        ↓
   Main Process
        ↓
 Operating System

再理解为什么这些层之间需要隔离,Electron 的主体知识其实就已经串起来了。

如果本身就是 React / Vue 开发者,那么学习 Electron 并不是重新学一次前端,更不是重新学习一套完全陌生的 UI 技术。

更像是在已有 Web 能力之外,再补上一层:

应用如何安全地作为一个真正的桌面程序运行起来。

当 Main、Renderer、Preload、IPC、安全边界和打包发布这些概念都建立起来之后,再去看 Tray、Auto Update、本地数据库、本地 AI、Python Sidecar 这些功能,就会顺很多。

这也是我认为前端开发者上手 Electron 最合适的路线。

相关推荐
huabuyu1 小时前
CLS 总是修不好?因为你只盯着分数,从没拆开看过它
前端·javascript
kisshyshy1 小时前
从多页面到SPA:React Router 路由进阶完全指南
前端·javascript·react.js
JakeJiang1 小时前
抓到接口还不够:用 AIProxy 改返回、Mock 数据、切测试环境
前端·后端
倾颜1 小时前
从 Web 到桌面:AI Mind Electron Desktop Host 的安全边界设计
前端
Csvn1 小时前
📡 前端错误监控从零搭建:window.onerror 与 unhandledrejection 的完整实践
前端
jarvisuni1 小时前
翻车了!GPT5.6接手Opus4.8的项目之后!
前端·人工智能·ai编程
雨田言炎2 小时前
十、QThread多线程
linux·服务器·开发语言·前端·qt
IT_陈寒2 小时前
Vite热更新失效?我的几个犯傻操作害我debug两小时
前端·人工智能·后端
晓得迷路了2 小时前
栗子前端技术周刊第 141 期 - Next.js 16.3、npm 安全事件、2026 CSS 现状调查报告结果...
前端·javascript·npm