结合 Vite 和 Webpack 说一说打包工具都做了什么

1) 入口与模块图:把项目“地图”画出来 功能: 告诉工具“从哪儿开始找代码”,再顺着每个文件里的 import/require() 把所有会用到的文件串成一张网(依赖图)。 直觉类比: 从首页(入口)出发,顺着“超链接”(import)把整站爬一遍,得到网站地图。 Vite: Dev:浏览器请求哪个模块,就现查现用地把它和它的依赖挂到图上。 Build:交给 Rollup 从入口把整张图一次走完。 Webpack: 不分 Dev/Prod,都是先把整张图建好再工作。 常用配置: Vite:resolve.alias、build.rollupOptions.input Webpack:entry、resolve.alias、resolve.extensions、resolve.mainFields 2) 模块解析与格式兼容:不同“口味”的文件都能吃 功能: 让 ESM、CommonJS、CSS、图片、JSON… 这些不同格式都能作为“模块”被导入。 直觉类比: 餐厅后厨收菜,有中餐配料、西餐配料、饮料、甜点——都要能接收并分门别类。 Vite: Dev 期用原生 ESM;遇到 CJS 用 esbuild 转成 ESM;CSS/图片会被转成“虚拟模块”。 Webpack: 用 loader 把各种格式变成 JS 能理解的模块;静态资源用 asset/* 类型统一管理。 配置抓手: Vite:多数开箱即用;特殊包用 optimizeDeps.include/exclude。 Webpack:module.rules(css-loader/file-loader 等已被 asset 取代)、type:'asset/resource|inline|source'。 3) 源码转换(TS/JSX/ESNext):把“新语法”变成“大家都懂的” 功能: 把 TS、JSX、以及浏览器还不完全支持的新 JS 语法,变成兼容目标环境的普通 JS。 直觉类比: 把方言翻译成普通话;再按不同城市口音做点适配。 Vite: Dev 用 esbuild 快速转;Build 用 Rollup 插件链 + esbuild;必要时再上 Babel。 Webpack: 常用 swc-loader / esbuild-loader(快)或 ts-loader + babel-loader(灵活)。 配置抓手: ...

十一月 5, 2025

jsconfig 配置及注意事项

jsconfig 配置的作用 在某个目录放一个 jsconfig.json,VS Code 就把它当作JavaScript 项目根。文件里定义项目包含的源文件与语言服务选项,从而影响智能提示、自动导入、转到定义、错误检查等编辑器体验。(Visual Studio Code) VS Code 没有 jsconfig 也能工作,但当你的工作区里并非所有 JS 文件都属于同一个项目,或需要路径别名/包含范围等配置时,就应该创建它。(Visual Studio Code) 它本质上是 tsconfig.json 的“JS 版变体”,只作用于编辑器的 JS/JSX 语言服务(不参与打包)。(Visual Studio Code) 为什么/什么时候需要 VS Code JS 支持有两种模式: File Scope(无 jsconfig):每个文件独立,没有统一的项目上下文。 Explicit Project(有 jsconfig):用 jsconfig.json 明确项目边界与解析规则。 当你需要更准确的 IntelliSense、跨文件跳转、非相对导入/别名,建议使用 jsconfig。(GitHub) 常用配置项(高频) include / exclude:工程边界。只把 src/ 等需要的目录纳入项目。(Visual Studio Code) compilerOptions.baseUrl + paths:配置路径别名(如 @/),让 VS Code 能解析到定义并提供自动导入。(Visual Studio Code) checkJs:对 .js/.jsx 做 TS 级别类型检查(配合 JSDoc 与 .d.ts)以发现更多类型错误。(Visual Studio Code) jsx:设置 JSX 处理方式(如 React 17+ 的 react-jsx)。(Visual Studio Code) 其他常见:module、moduleResolution、resolveJsonModule、types 等,影响编辑器如何解析模块与可见的全局类型(例如 Node/React)。(Visual Studio Code) 小技巧:用命令面板 JavaScript: Go to Project Configuration 可检查当前文件是否被某个 jsconfig.json 管理。(Visual Studio Code) ...

十一月 3, 2025

Laravel IDE Helper 使用笔记

📦 一、包简介 项目地址: https://github.com/barryvdh/laravel-ide-helper 作者:Barry vd. Heuvel 最新版本:v3.x支持 Laravel 10+ ✅ 作用 laravel-ide-helper 是一个为 Laravel 项目生成 PHPDocs 的开发辅助工具。它可以让你的 IDE (PhpStorm / VS Code )能够: 智能补全 Laravel Facade 方法 识别 Eloquent 模型的属性与关系 添加 IoC 容器解析类的类型提示 自动生成 macro / mixin / Fluent 方法文档 🔍 说白了:让 IDE 更懂 Laravel。 ⚙️ 二、安装与配置 1. 安装 composer require --dev barryvdh/laravel-ide-helper 🛠️ 三、常用命令 1. Facade PHPDocs php artisan ide-helper:generate 🔹 生成 _ide_helper.php,使 IDE 识别如 DB, Auth, Cache 等 Facade。 ...

十月 28, 2025

不同系统中的卷(Volume)与分区管理对比

一、基本概念 “卷(Volume)”是存储空间的逻辑单位。不同操作系统对“卷”的定义和管理方式不同: Windows 以固定分区为主; macOS (APFS) 采用共享空间的容器模型; Linux 使用 LVM(逻辑卷管理)实现灵活可扩展的结构。 二、三大系统卷管理方式对比 系统 底层技术 结构层次 空间分配机制 是否自动共享空间 是否支持跨硬盘 是否可加密 调整难度 Windows NTFS 分区 硬盘 → 分区 每个分区固定大小 ❌ 否 ❌ 否 BitLocker 高(需重新分区) macOS APFS 容器 + 卷 硬盘 → 容器 → 卷 容器内卷共享空间 ✅ 是 ❌ 否 ✅ 原生支持 (APFS Encrypted) 极低(自动) Linux LVM(PV → VG → LV) 硬盘 → 分区 → 物理卷 → 卷组 → 逻辑卷 卷组内卷可手动调整大小 ⚙️ 半自动(需命令调整) ✅ 可将多盘合并 ✅ LUKS / dm-crypt 中等(需命令行) 三、形象理解 系统 类比场景 特点 Windows 固定大小的房间 空间一旦划定就不能自动调整。 macOS (APFS) 多人共享仓库 所有卷共享一个空间池,自动伸缩。 Linux (LVM) 模块化仓库 管理员可自由扩展、合并或缩小卷组。 四、文件系统与大小写敏感性 系统 常见文件系统 是否区分大小写 说明 Windows NTFS / exFAT / FAT32 默认不区分 对大小写敏感软件不友好。 macOS APFS / HFS+ 可选(Case-sensitive 或不敏感) 默认不区分;开发环境可选敏感模式。 Linux ext4 / XFS / Btrfs / ZFS 默认区分 文件系统天然区分大小写。 五、加密机制对比 系统 加密方式 特点 Windows BitLocker 简单易用,系统集成。 macOS APFS (Encrypted) / FileVault 系统级集成,安全方便。 Linux LUKS + LVM 灵活可组合,适合进阶使用者。 六、操作方式示例 1️⃣ macOS 新建卷(Case-insensitive) 打开 磁盘工具 (Disk Utility) 选中 APFS 容器 → 点击 “+” 格式选择 APFS 或 APFS (Encrypted)(不要选 Case-sensitive) 完成后复制数据过去即可使用 → 所有卷自动共享同一空间。 ...

十月 24, 2025

为什么不应该在 Sidebar 组件中直接使用 useSessions Hook

引言 在 React 开发中,我们经常面临一个重要的架构决策:是否应该让组件直接使用业务逻辑 hooks,还是通过 props 传递数据和回调函数?本文将通过一个实际的聊天应用案例,分析为什么建议保持组件的数据流清晰,而不是在组件中直接使用业务逻辑 hooks。 问题背景 在我们的聊天应用中,有一个 Sidebar 组件负责显示聊天会话列表。最初的设计是: // 当前架构:通过 props 传递数据 <Sidebar sessions={sessions} currentSessionId={currentSessionId} onSelectSession={selectSession} onNewSession={createNewSession} onDeleteSession={deleteSession} isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} /> 有人可能会问:为什么不直接在 Sidebar 组件中使用 useSessions hook 呢? // 不推荐的架构:直接在组件中使用 hook export function Sidebar() { const { sessions, currentSessionId, selectSession, createNewSession, deleteSession } = useSessions() // ... } 为什么不建议在组件中直接使用业务逻辑 Hooks? 1. 关注点分离 (Separation of Concerns) 组件应该专注于 UI 渲染,而不是业务逻辑 // ✅ 好的做法:纯展示组件 export function Sidebar({ sessions, onSelectSession, onNewSession, onDeleteSession }) { return ( <div> {sessions.map(session => ( <button key={session.id} onClick={() => onSelectSession(session.id)}> {session.title} </button> ))} </div> ) } // ❌ 不好的做法:组件混合了业务逻辑 export function Sidebar() { const { sessions, selectSession } = useSessions() // 业务逻辑混入组件 // ... } 2. 可测试性 (Testability) 纯组件更容易进行单元测试 ...

八月 21, 2025

Zustand Store 状态同步问题分析与解决方案

问题背景 在使用 Zustand 构建复杂状态管理时,经常会遇到需要组合多个子 store 的场景。本文通过一个实际案例,分析了在组合 store 时遇到的状态同步问题,并提供了相应的解决方案。 问题描述 原始代码结构 const useDeviceStore = create((set, get) => ({ // 问题:这种方式只获取初始状态,不会响应子store的变化 ...useDeviceIdentityStore.getState(), ...useNetworkStatusStore.getState(), ...useOperationStatusStore.getState(), ...useDeviceInfoStore.getState(), _initialized: false, init: async () => { // 初始化逻辑 await useDeviceIdentityStore.getState().init(); set({ _initialized: true }); } })); // src/pages/mainTab/index.jsx - 组件使用 const MainTab = () => { const deviceId = useDeviceStore((state) => state.deviceId); // 始终为空 const accessCode = useDeviceStore((state) => state.accessCode); // 始终为空 // ... }; 问题现象 设备初始化成功(控制台显示 deviceId: 'E00E40', accessCode: 'CJRA88') 但组件中获取到的 deviceId 和 accessCode 始终为空字符串 子 store 状态正常,主 store 状态不同步 问题根源 状态组合方式错误:使用 ...useDeviceIdentityStore.getState() 只会在 store 创建时获取一次初始状态,不会建立响应式连接。当子 store 状态更新时,主 store 不会自动同步。 ...

八月 2, 2025

Socket.io 中间件机制与错误事件传递详解

中间件机制(Middleware) 什么是 Socket.io 中间件 Socket.io 中间件是在客户端连接建立之前执行的函数,用于验证、授权或预处理连接请求。 中间件的执行时机 客户端发起连接 → 中间件验证 → 连接建立/拒绝 → 触发相应事件 基本语法结构 io.use((socket, next) => { // 验证逻辑 if (验证通过) { next(); // 继续连接流程 } else { next(new Error("错误信息")); // 拒绝连接并传递错误 } }); 中间件参数详解 socket: 即将建立的 socket 连接对象,包含 handshake 信息 next: 回调函数,控制中间件流程的继续或中断 错误事件传递机制 服务端错误传递 // ✅ 正确方式:在中间件中传递错误 io.use((socket, next) => { try { const token = socket.handshake.auth.token; if (!token) { // 通过 next 传递错误到客户端 return next(new Error("Authentication error: No token provided")); } jwt.verify(token, SECRET_KEY); next(); // 验证通过,继续连接 } catch (err) { // JWT验证失败 next(new Error("Authentication error: Invalid token")); } }); 客户端错误接收 const socket = io({ auth: { token: "your_token_here" } }); // 连接成功事件 socket.on('connect', () => { console.log('✅ 连接成功'); }); // 连接错误事件 - 接收中间件传递的错误 socket.on('connect_error', (error) => { console.log('❌ 连接失败:', error.message); // error.message 包含服务端 next(new Error()) 中的错误信息 }); 对比:正确 vs 错误的实现方式 ❌ 错误方式:连接后断开 // 问题:连接已建立,disconnect 不会触发 connect_error io.on('connection', (socket) => { const token = socket.handshake.auth.token; if (!token) { socket.disconnect(true); // 客户端不会收到 connect_error return; } }); 问题分析: ...

七月 17, 2025

Intro Trickle Ice

Trickle ICE(增量式 ICE)是一种优化WebRTC连接建立速度的技术,属于 Interactive Connectivity Establishment (ICE) 机制的一种扩展方式。 一、基本概念与原理: 传统的 ICE 流程是: 候选地址收集:获取所有网络接口的本地候选地址(如本地IP、STUN服务器反射地址、TURN服务器中继地址)。 候选地址交换:候选地址完全收集完成后,一次性发送给对端(通常通过SDP)。 连接检查:双方获取地址后尝试相互连接,找到可用的路径并建立媒体通信。 但这个过程存在一个问题: 收集地址可能需要一定时间,特别是STUN、TURN服务器响应可能较慢。 因此,在传统模式下,ICE需要等待候选地址完全收集完毕后再交换,延迟较高。 为优化连接建立速度,Trickle ICE 被提出: 增量式发送候选地址,而非一次性发送全部。 只要有新的候选地址出现,就立即发送给对方。 对方收到后也会立即开始连接检查,不再等待完整候选地址列表。 二、Trickle ICE的工作流程: 步骤: 发起Offer Caller (Offerer)创建一个初始Offer,通常携带已有的少量候选地址(或为空),标明使用a=ice-options:trickle。 快速响应Answer 被呼叫方(Answerer)尽快响应Answer(也可能是空或少量地址),也标注a=ice-options:trickle。 增量发送候选(Trickling) 双方后续每次获得新候选地址时,都通过信令通道单独传送给对方,不再等待所有候选地址完全收集完毕。 连接检查同步进行 每收到一个候选地址,双方立即开始对该地址进行连通性检查(connectivity check)。 连接建立 一旦某条路径通过连接检查成功,就建立连接并开始媒体通信。 Trickle ICE 完成标记 当候选地址全部发送完毕后,会发送一个特殊标记表示候选地址收集完成,例如end-of-candidates。 三、Trickle ICE的优势: 更快的连接建立 候选地址收集和连接检查并行进行,连接延迟大幅降低(从几秒降低到几百毫秒)。 灵活性提升 特别适用于移动或不稳定的网络环境中,可以快速响应网络状况变化。 四、典型应用场景: 实时音视频通信(如WebRTC通话、视频会议、语音通话)。 实时游戏或协作应用,对连接建立时延敏感的场景。 五、Trickle ICE示例SDP片段: Offer示例: v=0 ... a=ice-options:trickle a=candidate:1 1 UDP 2130706431 192.0.2.1 3478 typ host 之后,通过信令通道逐步发送新候选: { "candidate": "candidate:2 1 UDP 1694498815 198.51.100.1 8998 typ srflx raddr 192.0.2.1 rport 3478", "sdpMid": "audio", "sdpMLineIndex": 0 } 候选地址发送完毕: ...

六月 25, 2025

Node.js 和 Go:哪个更适合写爬虫?

在实现站点爬取和内容分析时,Node.js 和 Go 是两种常见的选择。它们各有优劣,适合不同的场景。本文将从性能、开发效率、并发处理和复杂网页支持等方面对两者进行详细对比,帮助你根据项目需求做出选择。 1. 性能对比 Go:作为编译型语言,Go 的性能非常优越,尤其适合处理高并发任务。Go 内置的 goroutines 使得并发爬取变得简单高效,在处理大量网络请求时表现尤为突出。 Node.js:Node.js 基于事件驱动的异步非阻塞模型,在 I/O 密集型任务中表现良好。然而,由于其是解释型语言,性能稍逊于 Go。在处理大量任务时,可能会遇到内存和性能瓶颈。 适用场景:如果项目对高并发和高性能有较高要求,Go 是更好的选择。 2. 开发效率与生态 Go:Go 的语法简洁,开发效率高,错误处理清晰,适合编写健壮的爬虫程序。然而,Go 的网页解析库相对较少,处理 HTML 等操作时需要更多手动工作。 Node.js:Node.js 拥有丰富的第三方库和工具,例如 axios、cheerio 和 puppeteer,可以快速实现爬虫功能。这些库极大地降低了开发复杂度,尤其是在处理 HTML 解析和动态页面时。 适用场景:如果优先考虑开发速度和生态丰富性,Node.js 更适合。 3. 并发与内存管理 Go:Go 的 goroutines 非常轻量,能够高效处理大量并发任务。同时,Go 的内存管理机制更加高效,适合大规模爬取任务。 Node.js:Node.js 的事件循环机制在一定规模的并发任务中表现良好,但在处理过多并发请求时,可能会面临内存压力和事件循环阻塞的问题。 适用场景:对于高并发和内存密集型任务,Go 更具优势。 4. 动态网页处理能力 Go:Go 在处理动态页面(如 JavaScript 渲染内容)时并不占优势。虽然可以使用 chromedp 等库实现浏览器自动化,但开发复杂度较高。 Node.js:Node.js 借助 puppeteer 或 playwright 等工具,可以轻松控制浏览器,处理动态内容和模拟用户行为。 适用场景:如果需要处理 JavaScript 渲染的动态内容,Node.js 是更好的选择。 总结 对比维度 Go Node.js 性能 高性能,适合高并发任务 性能稍逊,适合 I/O 密集型任务 开发效率 语法简单,但生态不如 Node.js 丰富 丰富的库和工具,开发速度快 并发与内存管理 goroutines 高效,适合大规模爬取任务 事件循环机制,适合中小规模并发任务 动态网页处理 支持有限,开发复杂度高 借助工具轻松处理动态内容 选择 Go:适合高并发、大规模爬取、对动态内容处理需求较少的项目。 选择 Node.js:适合快速开发、处理动态网页、依赖丰富第三方库的项目。 根据项目需求权衡选择,才能更高效地完成爬虫开发任务 ...

十月 23, 2024

减少 Tailwind 中重复和冗长的 className

有几种方法可以减少 Tailwind 中重复和冗长的 className: 1. 使用 @apply 语法 在自定义的 CSS 文件中,使用 Tailwind 的 @apply 语法将常用的样式组合成一个自定义的类。例如: /* styles.css */ .btn-primary { @apply bg-blue-500 text-white py-2 px-4 rounded; } 然后在组件中使用: <button className="btn-primary">Click me</button> 这样可以减少 JSX 文件中的 className 冗余。 2. 封装组件 将常用的样式封装成可复用的组件。例如,将按钮样式封装为一个 Button 组件: const Button = ({ children, className, ...props }) => ( <button className={`bg-blue-500 text-white py-2 px-4 rounded ${className}`} {...props} > {children} </button> ); // 使用 <Button className="my-2">Click me</Button>; 通过这种方式,可以保持代码的简洁并提高复用性。 3. 使用 clsx 或 classnames 库 这些库可以帮助你有条件地组合多个 className,减少重复性。例如: ...

十月 23, 2024