阅读视图

发现新文章,点击刷新页面。

「寻找年味」 沸点活动|获奖名单公示🎊

image.png

🎉2026年 「寻找年味」 沸点活动正式落幕啦!

这个春节,我们在沸点里看见了无数动人瞬间:有故乡的烟火,有团圆的温柔,也有坚守岗位的专注与光芒。

每一条沸点,都是最真实、最可爱、最有温度的新年风景。

感谢每一位技术er,用文字、照片与 AI 创作,记录独属于程序员的春节时光,让技术与年味温暖相融。

由于获奖用户较多,详细的名单公示如下:[2026年 寻找年味沸点活动_获奖名单]

如何快速找到自己:进入飞书表格后,使用 Ctr+F 搜索自己的用户名或 用户id,选择“所有工作表”( 用户 id 即掘金主页 juejin.cn/XXXXXX 最后的一串数字)。之前打开过本表同学请刷新表单,奖项或有增补,以最新的表格为准。

image.png

活动奖品

沸点将根据评审团综合打分互动量得分加权计算。

✅ 常规内容赛道:TOP10 赢 SZCOOL 无线蓝牙音箱

✅ AI 内容赛道: TOP10 拿下「窝在一起」瓦楞猫窝

✅阳光普照奖:凡是符合#寻找年味主题的且有互动(排除作者自己点赞和评论)的沸点内容皆可获得10w 矿石

领奖方式

  • 获得上述奖项的掘友近期请注意 [系统消息] (预计2026年2月26日23:00前下发),请于 2026 年 3 月 4 日 23 点 之前在相关问卷中填写信息,过期将视为放弃奖品。
  • 常规内容赛道/AI 内容赛道 奖品将于问卷截止日期后的 30 个工作日内完成发放。
  • 阳光普照奖矿石将于2026年3月5日(申述时间截止后)发放。

若对获奖名单有疑问,请先自查沸点,确认无以下原因后,可点击联系 爱专研的安东尼 进行申诉。 申诉处理时间2026年2月26日-2026年3月4日,过时维持原结果。

  • 参赛沸点互动量按「点赞数 + 评论数」总和核算;

  • 同一用户发布多条内容,仅取数据最优一条参与排名获奖;

  • 无意义水评论、刷赞等违规行为,剔除获奖资格;

  • 数据统计截止至 2026 年 2 月 23 日 23:59,逾期新增互动不计入;

  • 禁止引战/制造冲突/谩骂内容,或者发现引战内容剔除获奖资格;

  • 评审团会依据沸点内容质量进行打分排序;

  • 非AI相关内容都归类为常规内容赛道,两个赛道不重复获奖,阳光普照奖可叠加赛道获奖;

再次感谢所有掘友的热情参与,愿大家新的一年代码无 Bug、技术节节高、万事皆顺遂!

常见的内存泄漏有哪些?

在 JavaScript 中,内存泄漏指的是应用程序不再需要某块内存,但由于某种原因,垃圾回收机制(GC, Garbage Collection)无法将其回收,导致内存占用持续升高,最终可能引发性能下降或崩溃。

以下是 JavaScript 中导致内存泄漏的最常见情况及示例:

1. 意外的全局变量

在 JavaScript 中,如果未声明的变量被赋值,它会自动成为全局对象的属性(浏览器中是 window,Node.js 中是 global)。全局变量在页面关闭前永远不会被垃圾回收。

function leak() {
  // 忘记了使用 let/const/var
  secretData = "这是一段敏感数据"; // 变成了 window.secretData
}
leak();

解决方案:

  • 使用严格模式 ('use strict') 来避免意外的全局变量。
  • 使用完后手动设置为 null

2. 被遗忘的定时器或回调函数

如果代码中设置了 setIntervalsetTimeout,但忘记清除(clear),且定时器内部引用了外部变量,那么这些变量无法被释放。

const someResource = hugeData(); // 很大的数据

setInterval(function() {
  // 这个回调引用了 someResource
  console.log(someResource);
}, 1000);

// 如果没有调用 clearInterval,someResource 会一直留在内存中

解决方案:

  • 在组件卸载或页面关闭时,清除定时器:clearInterval(id)

3. 闭包(Closures)的不当使用

闭包是 JavaScript 的强大特性,但如果闭包长期持有父函数的变量,而这些变量又很大,就会造成泄漏。

function outer() {
  const largeArray = new Array(1000000).fill('data');

  return function inner() {
    // inner 函数引用了 outer 作用域的 largeArray
    // 只要 inner 函数还存在,largeArray 就无法被回收
    console.log(largeArray.length);
  };
}

const innerFunc = outer(); // largeArray 被保留
// 如果后续没有释放 innerFunc,内存就会泄漏

解决方案:

  • 确保不再需要的函数被释放(innerFunc = null)。
  • 在闭包外尽量避免引用大对象。

4. DOM 引用未被清理

当把 DOM 元素存储为 JavaScript 对象或数据结构时,即使该元素已从 DOM 树中移除,只要 JS 中还有引用,该 DOM 元素连同其事件监听器就不会被释放。

const elements = {
  button: document.getElementById('button')
};

function removeButton() {
  document.body.removeChild(document.getElementById('button'));
  // 注意:elements.button 仍然指向那个 DOM 对象,所以它无法被回收
}

解决方案:

  • 移除 DOM 节点后,同时将变量设置为 null

5. 事件监听器未移除

向 DOM 元素添加了事件监听器,但在移除该元素前没有移除监听器。现代浏览器(尤其是针对原生 DOM 的监听器)处理得比以前好,但在单页应用(SPA, Single Page Application)中,如果频繁添加和移除元素,累积的监听器仍会导致泄漏。

const element = document.getElementById('button');
element.addEventListener('click', onClick);

// 如果后来 element 被移除了,但没有 removeEventListener
// 并且 onClick 函数引用了外部变量,就会造成泄漏

解决方案:

  • 在移除元素前调用 removeEventListener
  • 使用框架(如 React、Vue)时,框架的生命周期通常会自动处理,但要注意在 useEffect 的清理函数中移除原生监听器。

6. 脱离 DOM 树的引用(DOM 树内部引用)

这通常发生在给 DOM 元素添加自定义属性时。如果两个 DOM 元素相互引用,即使从文档流中移除,也可能因为循环引用导致泄漏(在老版本 IE 中常见,现代浏览器有所改进,但仍需注意)。

7. Map 或 Set 的不当使用

使用对象作为 MapSet 的 key,如果只把 key 置为 null,而没有从 Map 中删除它,key 依然被 Map 引用着,无法被回收。

let obj = {};
const map = new Map();
map.set(obj, 'some value');

obj = null; // 这里 obj 被置为 null
// 但 map 里仍然有对原对象的引用,所以原对象无法被回收

解决方案:

  • 使用 WeakMapWeakSet。它们的 key 是弱引用,不会阻止垃圾回收。

8. console.log 的影响

在开发环境调试时打印对象,如果线上环境忘记删除 console.log,控制台会一直持有对象的引用(特别是打印复杂对象时),导致对象无法被回收。现代浏览器在处理 console.log 时有所优化,但仍需注意。

建议:

  • 生产环境打包时移除所有 console.log

总结:如何避免内存泄漏?

  1. 使用 WeakMapWeakSet 存储对象引用。
  2. 及时清理:清除定时器、取消订阅、解绑事件。
  3. 避免全局变量,使用 let/const 和严格模式。
  4. 合理使用闭包,避免在闭包中持有大量数据的引用。
  5. 善用工具
    • 使用 Chrome DevTools 的 Memory 面板拍摄堆快照(Heap Snapshot),分析内存占用。
    • 使用 Performance 面板监控内存变化。

JavaScript 基础入门

如果把网页比作一个人:

  • HTML 是骨架:决定哪里是头、哪里是手。
  • CSS 是皮肤和衣服:决定长得好不好看。
  • JavaScript(简称 JS)是肌肉和神经:决定网页能不能“动”起来。

没有 JS,网页就是一张静态海报;有了 JS,网页就变成了可以互动的游戏、能验证密码的表单、能滑动加载的瀑布流。

趣味科普:1995 年,网景公司的程序员仅用 10天 就写出了 JS 的初版。为了蹭当时 Java 语言的热度,起名叫 JavaScript,但实际上它俩毫无关系(就像“雷锋”和“雷峰塔”)。如今,JS 已经成为能写网页、做手机 APP、写后端服务的“全栈霸主”。


核心前置知识:小白必懂的 3 个概念

在动手写代码前,必须先搞懂这 3 个“绕不开”的基础概念。

1. 变量:装数据的“带标签盒子”

变量的作用是给数据起个名字,方便反复使用。

声明关键字 特点 适用场景 示例
const 不可变(常量) 声明后不会再改变的数据 const name = "张三";
let 可变(变量) 声明后可能会被修改的数据 let age = 20; age = 21;

2. DOM:网页的“结构图纸”

DOM(文档对象模型) 是浏览器把 HTML 转换成的一棵“对象树”。 JS 看不懂 HTML 代码,但它能看懂 DOM 树。JS 拿着这张图纸,就能精准找到网页上的任何元素并修改它。

graph TD
    HTML((html)) --> HEAD((head))
    HTML --> BODY((body))
    HEAD --> TITLE[title: 网页标题]
    BODY --> H1[h1: 标题文字]
    BODY --> IMG[img: 图片]
    BODY --> BUTTON[button: 按钮]
    
    style HTML fill:#f9f2f4,stroke:#d04437,stroke-width:2px
    style BODY fill:#e6f2ff,stroke:#007bff,stroke-width:2px
    style H1 fill:#d9ead3,stroke:#93c47d
    style IMG fill:#d9ead3,stroke:#93c47d

3. 事件监听:给网页安上“传感器”

让 JS 盯着用户的动作(点击、滑动、输入),一旦触发,就执行对应的代码。

  • 核心语法addEventListener("事件类型", 触发后执行的代码)

综合实战:打造你的第一个交互网页

我们将通过一个综合案例,一次性掌握 改文字、换图片、存数据 三大核心技能。

步骤 1:搭建 HTML 骨架

新建 index.html,注意 <script> 标签里的 defer 属性,这是新手避坑的关键!

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>我的交互网页</title>
  <!-- 引入 JS。defer 表示“后台加载,等 HTML 渲染完再执行”,防止 JS 找不到元素 -->
  <script defer src="main.js"></script>
</head>
<body>
  <h1>原来的标题</h1>
  <img src="images/firefox-icon.png" alt="火狐图标" id="myImg">
  <button id="changeUserBtn">切换用户</button>
</body>
</html>

步骤 2:编写 JS 代码

新建 main.js,把下面的代码复制进去。我们分三步来实现交互:

魔法 1:自动修改标题(DOM 操作)

// 1. 找元素:用 querySelector 找到 <h1>
const myHeading = document.querySelector("h1");

// 2. 改内容:修改它的文字属性
myHeading.textContent = "Hello World!";

魔法 2:点击切换图片(事件监听 + 属性修改)

// 1. 找元素:找到图片
const myImage = document.querySelector("#myImg");

// 2. 听事件:监听点击动作
myImage.addEventListener("click", () => {
  // 3. 做处理:获取当前图片路径
  const currentSrc = myImage.getAttribute("src");
  
  // 4. 更页面:判断并切换路径
  if (currentSrc === "images/firefox-icon.png") {
    myImage.setAttribute("src", "images/firefox2.png"); // 换新图
  } else {
    myImage.setAttribute("src", "images/firefox-icon.png"); // 换回原图
  }
});

魔法 3:记住用户的名字(本地存储 + 弹窗)

这里我们会用到浏览器的“小仓库”——localStorage。它能把数据存在用户电脑上,刷新网页也不会丢。

const myButton = document.querySelector("#changeUserBtn");

// 定义一个设置名字的函数
function setUserName() {
  // prompt 会弹出一个输入框
  const userName = prompt("请输入你的名字:");
  
  if (!userName) { 
    setUserName(); // 如果没输入,就重新弹窗要求输入
  } else {
    // 存数据:把名字存进本地仓库,贴上标签叫 "savedName"
    localStorage.setItem("savedName", userName);
    // 模板字符串:用反引号 ` 和 ${} 把变量塞进句子里
    myHeading.textContent = `JS 太酷了,${userName}!`;
  }
}

// 页面刚打开时的判断逻辑
if (!localStorage.getItem("savedName")) {
  setUserName(); // 没存过,弹窗让用户输入
} else {
  const savedName = localStorage.getItem("savedName"); // 存过,直接取出来
  myHeading.textContent = `JS 太酷了,${savedName}!`;
}

// 点击按钮时,重新设置名字
myButton.addEventListener("click", () => {
  setUserName();
});

总结:JS 交互的“万能 4 步曲”

不管是做简单的按钮点击,还是复杂的网页游戏,JS 的核心交互逻辑永远逃不开这 4 步。把这张图印在脑子里,你就算真正入门了!

graph TD
    A[1. 找元素<br>querySelector] -->|找到目标| B[2. 听事件<br>addEventListener]
    B -->|用户触发| C[3. 做处理<br>逻辑判断/存取数据]
    C -->|计算完毕| D[4. 更页面<br>修改 DOM/样式]
    
    style A fill:#ffe6cc,stroke:#ff9900
    style B fill:#e6f2ff,stroke:#007bff
    style C fill:#e6ffe6,stroke:#28a745
    style D fill:#f9f2f4,stroke:#d04437

新手避坑指南

  1. 报错 Cannot read properties of null(找不到元素)
    • 原因:JS 执行时,HTML 还没加载完。
    • 解决:检查 <script> 标签有没有加 defer 属性,或者把 <script> 放到 </body> 标签的前面。
  2. 名字存了但刷新后不显示
    • 原因:存数据(setItem)和取数据(getItem)时,用的“标签名(key)”拼写不一致。
    • 解决:仔细检查字符串,比如是不是把 savedName 错拼成了 saveName

call、apply、bind 原理与实现

📍 第一章:先搞清楚 this 是什么

在讲 call/apply/bind 之前,必须理解 this 的指向规则:

// 1. 默认绑定:独立函数调用,this 指向全局(非严格模式)
function foo() { console.log(this); }
foo(); // window(浏览器)或 global(Node)

// 2. 隐式绑定:作为对象方法调用,this 指向该对象
const obj = { foo };
obj.foo(); // obj

// 3. 显式绑定:call/apply/bind 强制指定 this
foo.call(obj); // obj

// 4. new 绑定:构造函数,this 指向新创建的对象
new foo(); // foo 的实例

call/apply/bind 的作用:强行指定函数的 this,让函数执行时指向你给的对象。

🔧 第二章:call 方法深度拆解

2.1 call 的语法

fn.call(thisArg, arg1, arg2, ...)

2.2 call 的原理

一句话原理:把函数临时挂载到目标对象上执行,执行完再删除。

// 假设我们想让 fn 的 this 指向 obj
const obj = { name: 'obj' };
function fn() { console.log(this.name); }

// 1. 正常的 this 指向
fn(); // undefined(this 指向全局)

// 2. call 的魔法:obj.fn = fn; obj.fn(); delete obj.fn;
// 这就是 call 的底层原理!

2.3 手写实现

Function.prototype.myCall = function(context = window) {
    // ----- 第1步:处理 context -----
    // 为什么要用 Object()?
    // 如果 context 是原始值(如 123、'abc'、null、undefined),需要转成对象
    // null/undefined 会被转成空对象,原始值会被包装成对象
    context = Object(context);
    // 示例:
    // myCall(123)  → context = Number {123}
    // myCall(null) → context = {}
    
    // ----- 第2步:创建唯一属性名 -----
    // 为什么要用 Symbol?
    // 防止覆盖 context 上已有的属性
    // 比如 context 本来就有 fn 属性,直接用 'fn' 会覆盖
    const fnSymbol = Symbol('fn');
    
    // ----- 第3步:把函数挂到对象上 -----
    // 这里的 this 是谁?是调用 myCall 的那个函数!
    // fn.myCall(obj)  → this = fn
    context[fnSymbol] = this;
    
    // ----- 第4步:处理参数 -----
    // arguments 是所有参数的类数组对象
    // fn.myCall(obj, 1, 2, 3) → arguments = [obj, 1, 2, 3]
    const args = Array.from(arguments).slice(1);
    // slice(1) 去掉第一个参数(context)
    
    // ----- 第5步:执行函数 -----
    // 判断是否有参数,用扩展运算符传参
    const result = args.length 
        ? context[fnSymbol](...args)   // 有参数
        : context[fnSymbol]();          // 无参数
    
    // ----- 第6步:清理临时属性 -----
    // 用完就删,保持对象原样
    delete context[fnSymbol];
    
    // ----- 第7步:返回结果 -----
    return result;
};

2.4 执行过程可视化

function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
    return 'done';
}

const person = { name: '张三' };

// 调用
greet.myCall(person, 'Hello');

// 执行过程:
// 第1步:context = Object(person) → { name: '张三' }
// 第2步:fnSymbol = Symbol('fn')
// 第3步:context[fnSymbol] = greet
//       person 变成了:{ name: '张三', [Symbol(fn)]: greet }
// 第4步:args = ['Hello']
// 第5步:执行 context[fnSymbol]('Hello') → this 指向 person
// 第6步:delete context[fnSymbol] → person 恢复原样:{ name: '张三' }
// 第7步:返回 'done'

2.5 边界情况处理

// 情况1:context 是原始值
function test() { console.log(this); }
test.myCall(123); // Number {123}  ✅

// 情况2:context 是 null/undefined
test.myCall(null); // {}  ✅(非严格模式下转成全局对象)

// 情况3:函数有返回值
function add(a, b) { return a + b; }
console.log(add.myCall(null, 1, 2)); // 3 ✅

// 情况4:无参数
function logThis() { console.log(this); }
logThis.myCall(); // window ✅

📊 第三章:apply 方法深度拆解

3.1 apply 与 call 的唯一区别

// call:参数列表
fn.call(obj, 1, 2, 3);

// apply:参数数组
fn.apply(obj, [1, 2, 3]);

3.2 手写实现

Function.prototype.myApply = function(context = window, args = []) {
    // ----- 第1步:处理 context -----
    context = Object(context);
    
    // ----- 第2步:创建唯一属性名 -----
    const fnSymbol = Symbol('fn');
    context[fnSymbol] = this;
    
    // ----- 第3步:执行函数(唯一区别在这里)-----
    // args 是数组,直接用扩展运算符展开
    const result = args.length 
        ? context[fnSymbol](...args) 
        : context[fnSymbol]();
    
    // ----- 第4步:清理 -----
    delete context[fnSymbol];
    
    return result;
};

3.3 为什么要有 apply?

// 场景1:处理类数组对象
function sum() {
    // arguments 是类数组,不能直接用数组方法
    // 用 apply 传参
    const nums = Array.prototype.slice.apply(arguments);
    return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6

// 场景2:配合 Math.max/min
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers); // 7
// ES6 可以用扩展运算符:Math.max(...numbers)

// 场景3:合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4]

🔗 第四章:bind 方法深度拆解

4.1 bind 的核心特性

fn.bind(thisArg, arg1, arg2, ...)

三大特性

  1. 不立即执行:返回一个新函数
  2. 永久绑定 this:bind 后的函数 this 不能再用 call/apply 改变
  3. 支持柯里化:可以预置参数

4.2 基础版实现

Function.prototype.myBind = function(context = window, ...boundArgs) {
    // 保存原函数
    const originalFn = this;
    
    // 返回新函数
    return function(...callArgs) {
        // 合并参数:bind 时传的 + 调用时传的
        const allArgs = [...boundArgs, ...callArgs];
        
        // 用 apply 改变 this
        return originalFn.apply(context, allArgs);
    };
};

// 测试
function introduce(hobby, age) {
    console.log(`我是${this.name},喜欢${hobby},今年${age}岁`);
    return 'done';
}

const person = { name: '李四' };
const boundIntroduce = introduce.myBind(person, '编程');
const result = boundIntroduce(18); 
// 输出: 我是李四,喜欢编程,今年18岁
console.log(result); // done

4.3 进阶:考虑 new 的情况(完整版)

如果 bind 返回的函数被 new 调用,this 应该指向新创建的对象

Function.prototype.myBind = function(context = window, ...boundArgs) {
    const originalFn = this;
    
    // 返回的函数
    function boundFunction(...callArgs) {
        const allArgs = [...boundArgs, ...callArgs];
        
        // 关键判断:是否通过 new 调用
        // this instanceof boundFunction 为 true 说明用了 new
        const isNewCall = this instanceof boundFunction;
        
        // 如果是 new 调用,this 指向新对象;否则指向绑定的 context
        return originalFn.apply(
            isNewCall ? this : context,
            allArgs
        );
    }
    
    // 维护原型链:让返回的函数继承原函数的原型
    // 这样 new boundFunction() 创建的对象才能继承 originalFn.prototype
    boundFunction.prototype = Object.create(originalFn.prototype);
    
    return boundFunction;
};

4.4 new 场景测试

function Person(name, age) {
    this.name = name;
    this.age = age;
    console.log('构造函数执行了');
}

Person.prototype.sayHi = function() {
    console.log(`Hi, I'm ${this.name}`);
};

// bind 预置 name
const BoundPerson = Person.myBind(null, '王五');

// 用 new 调用
const p = new BoundPerson(25);
console.log(p); // Person { name: '王五', age: 25 } ✅
p.sayHi(); // Hi, I'm 王五 ✅(原型链也保留了)

// 如果不处理 new 的情况:
// p 会是 {},name/age 都挂到了 BoundPerson 上,原型链也断了 ❌

4.5 bind 的特性验证

// 特性1:永久绑定(一旦 bind,不能再用 call/apply 改变)
function fn() { console.log(this.name); }
const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };

const boundFn = fn.bind(obj1);
boundFn(); // obj1 ✅

// 尝试用 call 改变
boundFn.call(obj2); // 还是 obj1 ✅(bind 优先级最高)

// 特性2:支持柯里化(预置参数)
function add(a, b, c) {
    return a + b + c;
}

const add5 = add.bind(null, 5);    // 预置 a = 5
const add5And10 = add5.bind(null, 10); // 预置 b = 10
console.log(add5And10(15)); // 5 + 10 + 15 = 30

// 特性3:this 优先级
// new > bind > call/apply > 隐式绑定 > 默认绑定

🎯 第五章:三者的优先级关系

// this 绑定优先级(从高到低)
// 1. new 绑定
// 2. bind 绑定
// 3. call/apply 绑定
// 4. 隐式绑定(对象.方法)
// 5. 默认绑定(独立调用)

function test() { console.log(this.name); }

const obj = { name: 'obj' };
const obj2 = { name: 'obj2' };

// bind 优先级 > call/apply
const bound = test.bind(obj);
bound.call(obj2); // obj(不是 obj2) ✅

// new 优先级 > bind
function Person(name) { this.name = name; }
const BoundPerson = Person.bind({ name: 'bindObj' });
const p = new BoundPerson('newObj');
console.log(p.name); // newObj(不是 bindObj)✅

💡 第六章:常见题解析

实现一个可以链式调用的 call

// 题目:让 fn.call.call(obj) 这种写法生效
function fn() { console.log(this); }

// 解析:fn.call.call(obj) 等价于
// (fn.call).call(obj)
// 即 Function.prototype.call 作为函数被调用

// 理解:
// fn.call 本身是一个函数(Function.prototype.call)
// .call(obj) 把 fn.call 的 this 指向 obj
// 所以执行的是 obj 上的 call 方法

bind 之后的函数 length 属性

function fn(a, b, c) {}
console.log(fn.length); // 3

const bound = fn.bind(null, 1);
console.log(bound.length); // 2(预置了一个参数,剩余 2 个)

// 原理:bind 返回的函数的 length = 原函数 length - 预置参数个数

实现函数的柯里化

// 用 bind 实现
function curry(fn, ...args) {
    return fn.length <= args.length
        ? fn(...args)
        : curry.bind(null, fn, ...args);
}

function sum(a, b, c) {
    return a + b + c;
}

const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6

📝 第七章:完整代码汇总

// call 完整实现
Function.prototype.myCall = function(context = window) {
    context = Object(context);
    const fnSymbol = Symbol();
    context[fnSymbol] = this;
    const args = Array.from(arguments).slice(1);
    const result = args.length ? context[fnSymbol](...args) : context[fnSymbol]();
    delete context[fnSymbol];
    return result;
};

// apply 完整实现
Function.prototype.myApply = function(context = window, args = []) {
    context = Object(context);
    const fnSymbol = Symbol();
    context[fnSymbol] = this;
    const result = args.length ? context[fnSymbol](...args) : context[fnSymbol]();
    delete context[fnSymbol];
    return result;
};

// bind 完整实现(含 new 处理)
Function.prototype.myBind = function(context = window, ...boundArgs) {
    const originalFn = this;
    
    function boundFunction(...callArgs) {
        const allArgs = [...boundArgs, ...callArgs];
        return originalFn.apply(
            this instanceof boundFunction ? this : context,
            allArgs
        );
    }
    
    boundFunction.prototype = Object.create(originalFn.prototype);
    return boundFunction;
};

🎓 第八章:总结对比

特性 call apply bind
执行时机 立即 立即 延迟
参数形式 列表 数组 列表(可分批)
返回值 函数结果 函数结果 新函数
this 永久性 一次性 一次性 永久
柯里化 不支持 不支持 支持
new 调用 无效 无效 有效(原函数可被 new)
实现难点 Symbol 防冲突 参数数组处理 new 判断 + 原型链

call 是立即执行+参数列表,apply 是立即执行+参数数组,bind 是返回新函数+永久绑定+支持柯里化

什么是事件循环?调用堆栈和任务队列之间有什么区别?

事件循环 (Event Loop)

事件循环是 JavaScript 运行时处理异步操作的核心机制,它使得 JavaScript 虽然是单线程的,但能够非阻塞地处理 I/O 操作和其他异步任务。

主要组成部分

  1. 调用堆栈 (Call Stack)

    • 一个后进先出(LIFO)的数据结构
    • 用于跟踪当前正在执行的函数
    • 当函数被调用时,会被推入堆栈;执行完毕后弹出
  2. 任务队列 (Task Queue)

    • 一个先进先出(FIFO)的数据结构
    • 存储待处理的消息(异步操作的回调)
    • 包括宏任务队列和微任务队列

调用堆栈 vs 任务队列

特性 调用堆栈 (Call Stack) 任务队列 (Task Queue)
结构 LIFO (后进先出) FIFO (先进先出)
内容 同步函数调用 异步回调函数
执行时机 立即执行 等待调用堆栈为空时才执行
优先级
溢出 可能导致"栈溢出"错误 不会溢出,但可能导致内存问题

事件循环的工作流程

  1. 执行调用堆栈中的同步代码
  2. 当调用堆栈为空时,事件循环检查任务队列
  3. 如果有待处理的任务,将第一个任务移到调用堆栈执行
  4. 重复这个过程

微任务队列 (Microtask Queue)

  • 比普通任务队列优先级更高
  • 包含 Promise 回调、MutationObserver 等
  • 在当前任务完成后、下一个任务开始前执行
  • 会一直执行直到微任务队列为空
console.log('1'); // 同步代码,直接执行

setTimeout(() => console.log('2'), 0); // 宏任务,放入任务队列

Promise.resolve().then(() => console.log('3')); // 微任务,放入微任务队列

console.log('4'); // 同步代码,直接执行

// 输出顺序: 1, 4, 3, 2

理解事件循环和这些队列的区别对于编写高效、无阻塞的 JavaScript 代码至关重要。

处理 I/O 操作的含义

I/O(Input/Output,输入/输出)操作是指程序与外部资源进行数据交换的过程。在JavaScript中,处理I/O操作特别重要,因为JavaScript是单线程的,而I/O操作通常是阻塞的(需要等待响应)。

常见的I/O操作类型

  1. 文件系统操作

    • 读写文件
    • 例如Node.js中的fs.readFile()
  2. 网络请求

    • HTTP/HTTPS请求
    • WebSocket通信
    • 例如fetch()XMLHttpRequest
  3. 数据库操作

    • 查询或更新数据库
    • 例如MongoDB、MySQL等数据库操作
  4. 用户输入

    • 键盘输入
    • 鼠标点击等交互事件

JavaScript如何处理I/O操作

JavaScript通过异步非阻塞方式处理I/O:

  1. 非阻塞特性

    • 发起I/O请求后,不等待结果立即继续执行后续代码
    • 避免线程被阻塞
  2. 回调机制

    • I/O完成后通过回调函数处理结果
    • 例如:
      fs.readFile('file.txt', (err, data) => {
        if (err) throw err;
        console.log(data);
      });
      
  3. Promise/async-await

    • 更现代的异步处理方式
    • 例如:
      async function fetchData() {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
      }
      

为什么需要特殊处理I/O

  1. 性能考虑:I/O操作通常比CPU操作慢得多

    • 磁盘读取:毫秒级(10^-3秒)
    • 网络请求:可能达到秒级
  2. 单线程限制:JavaScript只有一个主线程

    • 如果同步等待I/O,整个程序会卡住
  3. 用户体验:在浏览器中,阻塞会导致页面无响应

事件循环中的I/O处理

当I/O操作完成时:

  1. 相应的回调函数被放入任务队列
  2. 事件循环在调用栈为空时从队列中取出回调执行
  3. 这使得JavaScript能够高效处理大量并发I/O
console.log('开始请求'); // 同步代码

// 异步I/O操作
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log('收到数据:', data)); // 回调

console.log('请求已发起,继续执行其他代码'); // 立即执行

// 可能的输出顺序:
// 开始请求
// 请求已发起,继续执行其他代码
// 收到数据: {...}

这种机制使得JavaScript特别适合I/O密集型应用(如Web服务器),能够高效处理大量并发请求而不需要创建多个线程。

防抖和节流:解决高频事件性能

在前端开发中,经常会遇到高频触发的事件,搜索框输入、页面滚动、按钮点击等,如果不对这些事件进行处理,频繁执行回调函数,可能会导致页面卡顿、请求次数过多,影响用户体验和系统性能。

防抖(Debounce)和节流(Throttle) 就是解决高频事件的秘密武器。它们的实现原理是闭包,用法相似,使用场景不同。应该根据实际场景,选择合适的方法进行处理。

为什么需要防抖节流

先从一个实际的例子入手,百度搜索,当你在搜索框输入关键字时,每次输入,都会触发键盘事件,若直接绑定网络请求,就会出现高频请求的问题。

如果不做任何处理,就会出现两个核心问题:

  • 执行次数过多:如果用户输入速度快,会在短时间内多次触发网络请求,网络次数过多,不仅服务器压力大,也会浪费前端性能。
  • 用户体验失衡:请求过快,频繁发送请求可能会导致响应混乱、页面卡顿;请求过慢,会导致联想建议延迟,影响使用体验

类似的场景还有很多,页面滚动加载、按钮重复提交,这些高频触发事件,都要通过防抖或节流来优化,避免“性能浪费”

这一切的实现,都离不开闭包的支持,利用闭包保留定时器ID,上一次执行时间等状态,让函数能够记住之前的执行状态,从而实现精准的触发控制,这就是防抖节流的核心底层逻辑。

防抖(Debounce): 多次触发,只执行最后一次

什么是防抖

防抖的核心逻辑:**在规定时间内,无论事件触发多少次,都只执行最后一次回调。**触发高频事件之后,函数不会立即执行,而是等待一段时间(延迟时间)后再执行;若在这段时间内事件再次被触发,则重新计时,只有当事件停止触发并且超过要延迟时间,函数才会执行一次。

防抖实现关键

/**
 * 防抖函数
 * @param {Function} fn 函数
 * @param {Number} delay 间隔
 */
function debounce(fn, delay) {
  var timer = null; // 变量(闭包核心):保存定时器ID
  return function(args) {
    if(timer) clearTimeout(timer); // 每次触发事件,先清除之前的定时器,重置倒计时
    var that = this; // 保存当前this指向,避免定时器内this丢失
    timer = setTimeout(function(){
      fn.call(that, args); // 推迟执行:延迟delay毫秒后,执行目标函数(最后一次触发的回调)
    }, delay);
  }
}

防抖应用场景

  • 搜索框输入联想:用户不断输入,等待用户停止输入后才触发联想请求;
  • 输入框实时校验:手机号、邮箱格式等,等待用户输入完成之后再校验,避免输入过程中频繁提示;
  • 按钮防止重复提交:例如表单提交按钮,避免用户连续点击发送多次请求;
  • 滚动条滚动检测:无需滚动过程中频繁检测,等待滚动停止后,判断滚动条位置。

节流(Throttle):每隔一段时间,只执行一次

什么是节流

节流的核心逻辑:**在规定时间内,无论事件触发多少次,都只执行一次回调。**触发高频事件,限制函数在指定时间间隔内只执行一次,无论事件触发多少次,都会按照固定的频率执行函数。

节流实现关键

/**
 * 节流函数
 * @param {Function} fn 函数
 * @param {Number} interval 间隔
 */
function throttle(fn, interval) {
    var enterTime = Date.now(); //触发时间
    return function() {
        var that = this, currentTime = Date.now(); // 当前时间
        if (currentTime - enterTime >= interval) { //判断是否到了指定间隔
            fn.apply(that, args);
            enterTime = Date.now(); //更新触发时间
        }
    }
}

节流应用场景

  • 按钮点击:点赞、刷新按钮,限制用户操作频率,防止恶意刷赞;
  • 滚动加载更多:用户滚动要页面,设置间隔,检测滚动位置,判断是否需要加载更多数据;
  • 鼠标移动:拖拽元素,固定频率更新元素位置,避免更新过于频繁,造成页面卡顿。

防抖和节流的区别

特性 防抖(Debounce) 节流(Throttle)
核心逻辑 等待最后一次触发后,延迟执行一次 固定时间间隔内,只执行一次
执行时机 事件停止触发后(延迟结束) 事件触发过程中(按固定频率)
触发频率 取决于事件停止触发的时间,可能很久执行一次 固定频率执行,不受事件触发频率影响
核心目的 过滤无效触发,保留最后一次结果 控制执行频率,避免过度执行
通俗比喻 电梯关门(有人进就重新计时) 水龙头滴水(固定时间滴一滴)

总结

防抖和节流解决问题的核心目的是一致:优化高频事件的性能,避免复杂任务频繁执行,两者的核心区别在于防抖只执行最后一次,节流是固定时间间隔内只执行一次。在实际运用过程中,需要根据不同场景合理选择使用防抖或节流,提升页面性能,优化用户体验。

【uniapp】小程序端解决分包的uni_modules打包后产物进入主包中的问题

配置

分包优化

需要在 mainfest.json 指定小程序节点下添加如下配置,例如:

{
  "mp-weixin": {
         "optimization": {
            "subPackages": true
          },
        "usingComponents": true
  }
}

主包分包的 uni_modules

首先,主包的 uni_moudles 要放在主包的根目录下,分包的 uni_moudles 要放在分包的根目录下

sub.jpg

然后,在 pages.json 中配置组件 easycom 引入规则,这一步是为了避免同一个组件库被主包分包都使用,出现识别错误的问题,例如,我在 uniappx 项目中使用了 rice-ui 组件库,可以这样配置

{
  "easycom": {
        "autoscan": true,
        "custom": {
            "^rice-(.*)": "uni_modules/rice-ui/components/rice-$1/rice-$1.uvue",
            "^sub-rice-(.*)": "sub/uni_modules/rice-ui/components/rice-$1/rice-$1.uvue"
        }
    }
}

这样,分包用组件就写 sub-rice-avatar,主包就是 rice-button

main.jpg

示例项目

测试项目在这个帖子末尾的附件 ask.dcloud.net.cn/article/423…

从 8 个实战场景深度拆解:为什么资深前端都爱柯里化?

你一定见过无数臃肿的 if-else 和重复嵌套的逻辑。在追求 AI-Native 开发的今天,代码的“原子化”程度直接决定了 AI 辅助重构的效率。

柯里化(Currying) 绝不仅仅是面试时的八股文,它是实现逻辑复用、配置解耦的工业级利器。通俗地说,它把一个多参数函数拆解成一系列单参数函数:f(a,b,c)f(a)(b)(c)f(a, b, c) \rightarrow f(a)(b)(c)

以下是 8 个直击前端实战痛点的柯里化应用案例。


1. 差异化日志系统:环境与等级的解耦

在web系统中,我们经常需要根据不同环境输出不同等级的日志。

JavaScript

const logger = (env) => (level) => (msg) => {
  console.log(`[${env.toUpperCase()}][${level}] ${msg} - ${new Date().toLocaleTimeString()}`);
};

const prodError = logger('prod')('ERROR');
const devDebug = logger('dev')('DEBUG');

prodError('支付接口超时'); // [PROD][ERROR] 支付接口超时 - 10:20:00

2. API 请求构造器:预设 BaseURL 与 Header

不用每次请求都传 Token 或域名,通过柯里化提前“锁死”配置。

JavaScript

const request = (baseUrl) => (headers) => (endpoint) => (params) => {
  return fetch(`${baseUrl}${endpoint}?${new URLSearchParams(params)}`, { headers });
};

const apiWithAuth = request('https://api.finance.com')({ 'Authorization': 'Bearer xxx' });
const getUser = apiWithAuth('/user');

getUser({ id: '888' }); 

3. DOM 事件监听:优雅传递额外参数

在 Vue 或 React 模板中,我们常为了传参写出 () => handleClick(id)。柯里化可以保持模板整洁并提高性能。

JavaScript

const handleMenuClick = (menuId) => (event) => {
  console.log(`点击了菜单: ${menuId}`, event.target);
};

// 模板中直接绑定:@click="handleMenuClick('settings')"

4. 复合校验逻辑:原子化验证规则

将复杂的表单校验拆解为可组合的原子。

JavaScript

const validate = (reg) => (tip) => (value) => {
  return reg.test(value) ? { pass: true } : { pass: false, tip };
};

const isMobile = validate(/^1[3-9]\d{9}$/)('手机号格式错误');
const isEmail = validate(/^\w+@\w+.\w+$/)('邮箱格式错误');

console.log(isMobile('13800138000')); // { pass: true }

5. 金融汇率换算:固定基准率

在处理多币种对账时,柯里化能帮你固定变动较慢的参数。

JavaScript

const convertCurrency = (rate) => (amount) => (amount * rate).toFixed(2);

const usdToCny = convertCurrency(7.24);
const eurToCny = convertCurrency(7.85);

console.log(usdToCny(100)); // 724.00

6. 动态 CSS 类名生成器:样式逻辑解耦

配合 CSS Modules 或 Tailwind 时,通过柯里化快速生成带状态的类名。

JavaScript

const createCls = (prefix) => (state) => (baseCls) => {
  return `${prefix}-${baseCls} ${state ? 'is-active' : ''}`;
};

const navCls = createCls('nav')(isActive);
const btnCls = navCls('button'); // "nav-button is-active"

7. 数据过滤管道:可组合的 Array 操作

在处理海量 AI Prompt 列表时,将过滤逻辑函数化,方便链式调用。

JavaScript

const filterBy = (key) => (value) => (item) => item[key].includes(value);

const filterByTag = filterBy('tag');
const prompts = [{ title: 'AI助手', tag: 'Finance' }, { title: '翻译机', tag: 'Tool' }];

const financePrompts = prompts.filter(filterByTag('Finance'));

8. AI Prompt 模板工厂:多层上下文注入

为你正在开发的 AI Prompt Manager 设计一个分层注入器:先注入角色,再注入上下文,最后注入用户输入。

JavaScript

const promptFactory = (role) => (context) => (input) => {
  return `Role: ${role}\nContext: ${context}\nUser says: ${input}`;
};

const financialExpert = promptFactory('Senior Financial Analyst')('Analyzing 2026 Q1 Report');
const finalPrompt = financialExpert('请总结该季报风险点');

Bun 全景指南:下一代 All-in-One 运行时详解与实战

1. 什么是 Bun?真正的 All-in-One

对于习惯了 Node.js 生态的开发者来说,Bun 不仅仅是另一个 JavaScript 运行时,它是一次对现代前端工程化的重构整合

Bun 是一个为速度而生的 All-in-one JavaScript 工具箱。它使用 Zig 语言从头构建。与 Node.js 不同,Bun 不仅仅是一个运行时(Runtime),它将前端开发所需的核心工具链全部内置到了一个二进制文件中:

  1. 运行时 (Runtime): 替代 Node.js,支持 TS/JS,基于 JavaScriptCore。
  2. 包管理器 (Package Manager): 替代 npm/yarn/pnpm,安装速度极快。
  3. 测试运行器 (Test Runner): 替代 Jest/Vitest,开箱即用,无需配置。
  4. 打包器 (Bundler): 替代 Webpack/Rollup/Esbuild,用于构建生产环境代码。

这种设计哲学旨在消除碎片化。你不再需要为了一个简单的项目去配置 package.json 中的十几个 devDependencies,也不用担心 ts-nodebabeljestwebpack 之间的版本冲突。Bun 给你提供了一个统一、标准且极速的开发环境。

2. 核心特性与使用实战

2.1 原生 TypeScript 支持

在 Node.js 中运行 TS 通常需要 ts-node 或构建步骤。在 Bun 中,TypeScript 是一等公民

# 直接运行 TS 文件,无需配置 tsconfig.json (虽然它也支持读取配置)
bun run index.ts

Bun 会在运行时直接转译 TypeScript(速度极快),无需预编译。注意:Bun 仅移除类型注解,不进行类型检查。生产构建时建议配合 tsc --noEmit 进行静态检查。

2.2 极速包管理器 (bun install)

Bun 的包管理器旨在替代 npm/yarn/pnpm。它利用全局缓存和硬链接(类似 pnpm),安装速度通常快 10-100 倍

bun install
bun add react
bun remove lodash

2.3 内置测试运行器 (bun test)

你不再需要 Jest 或 Vitest。Bun 内置了一个与 Jest 兼容的测试运行器。

// test.ts
import { expect, test } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

运行测试:

bun test

2.4 Web 标准 API 与内置服务器

Bun 实现了标准的 Web API,如 fetch, Request, Response, WebSocket。这意味着你在浏览器中学到的知识可以直接在服务端使用。

启动一个高性能 HTTP 服务器极其简单:

// server.ts
Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello from Bun!");
  },
});

2.5 内置打包器 (bun build)

Bun 还是一个打包工具,旨在替代 Webpack/Rollup/Esbuild。

bun build ./index.tsx --outdir ./out --target browser

3. 为什么 Bun 这么快?

Bun 的“快”是全方位的,主要体现在三个维度:

3.1 启动速度 (Startup Time)

这是 Bun 最显著的优势。

  • 现象: 运行 bun run script.ts 几乎是瞬时的。
  • 原因:
    • JavaScriptCore (JSC): Bun 使用了 Safari 的 JS 引擎。JSC 针对移动端设备进行了极致优化,优先保证快速启动低内存占用。相比之下,V8 (Node.js) 更侧重于长时间运行后的 JIT 优化。
    • Zig 语言: Bun 的底层代码是用 Zig 编写的。Zig 允许手动管理内存,没有垃圾回收(GC)的暂停,且编译器能生成高度优化的机器码。

3.2 运行时性能 (Runtime Performance)

虽然 V8 在极致的 JIT 优化上非常强大,但 Bun 在很多 I/O 密集型任务上依然超越了 Node.js。

  • HTTP 服务器: Bun.serve() 基于 uWebSockets 库(C++编写),处理并发请求的能力远超 Node.js 的 http 模块。
  • SQLite: bun:sqlite 是直接嵌入在运行时中的,比 Node.js 通过桥接调用的 SQLite 快数倍。

3.3 工具链速度 (Tooling Speed)

  • 安装依赖: bun install 使用了二进制层面的优化,利用系统调用(System Calls)快速读写文件,并采用全局缓存 + 硬链接策略。
  • 转译/打包: Bun 的转译器是用 Zig 手写的,不依赖 Babel 或其他 JS 实现的 AST 解析器,因此速度极快。

3.4 理性看待:Bun 的局限性与 V8 的反击

回答一个常见疑问:Bun 真的在所有场景下都完胜 Node.js 吗? 答案是否定的。

  1. 启动快 vs 跑得快 (Startup vs Peak Performance)

    • Bun (JSC): 赢在起跑线。由于 JavaScriptCore 针对移动端(Safari)优化,它的冷启动速度极快,内存占用极低。这使得 Bun 非常适合CLI 工具Serverless 函数脚本等短生命周期任务。
    • Node.js (V8): 赢在长跑。V8 引擎拥有强大的 TurboFan 优化编译器。在长时间运行的计算密集型服务中,V8 的 JIT 优化往往能产生比 JSC 更高效的机器码,峰值吞吐量可能更高。
  2. 兼容性的“恐怖谷”

    • Bun 虽然实现了 90% 的 Node.js API,但在剩下的 10% 中隐藏着魔鬼。特别是涉及到C++ Addons、一些冷门的 fschild_process 边缘行为时,Bun 可能会表现出与 Node.js 不一致,导致现有项目迁移受阻。
  3. 生态系统的护城河

    • Node.js 经过 10+ 年的打磨,其稳定性是企业级的。Bun 虽然迭代快,但作为新生代,Bug 率相对较高。对于金融级或核心业务系统,Node.js 依然是更稳妥的选择。

4. 路径之争:前端工程化的未来

Node.js 生态目前正在经历一场“性能革命”,主要有两种演进路径:

4.1 路径 A:渐进式改良 (Node.js + Rust/Go)

这是目前的主流趋势,即在保留 Node.js 运行时的基础上,用高性能语言重写上层工具链。

  • 代表: Vite (使用 esbuild/Rollup), SWC (Rust版 Babel), Biome (Rust版 Prettier/ESLint)。
  • 优势: 兼容性好。用户依然运行在 Node.js 上,迁移成本低,生态惯性大。
  • 劣势: 碎片化依旧。底层 Node.js 的启动速度瓶颈依然存在,且工具链之间的数据交换(JS <-> Rust/Go)存在序列化开销。

4.2 路径 B:革命式重构 (Bun)

这是 Bun 选择的道路,从底层 Runtime 到上层 Tooling 全部推倒重来。

  • 代表: Bun。
  • 优势: 极致性能与体验。消除了所有中间环节的开销,统一了标准,实现了真正的 All-in-One。
  • 劣势: 兼容性挑战。虽然 Bun 努力兼容 Node.js API,但在 C++ Addons 和一些边缘 API 上仍需时间完善。

4.3 另一个挑战者:Deno

提到新的运行时,不得不提 Deno。它由 Node.js 之父 Ryan Dahl 创立,旨在“修复 Node.js 的设计遗憾”。

特性 Node.js Deno Bun
核心哲学 稳定、生态丰富 安全、Web 标准、修正设计 极致性能、无缝兼容
JS 引擎 V8 V8 JavaScriptCore (启动更快)
编写语言 C++ Rust Zig
兼容性 基准 逐步兼容 npm 优先兼容 Node API
优势 庞大生态 安全性 (默认无权限)、TS 开箱即用 速度 (启动/安装/运行)、All-in-One

核心区别总结

  • Deno (理想主义): 强调安全性Web 标准。它试图纠正 Node.js 的历史包袱(如 node_modules),推崇 URL 导入和显式权限管理。虽然现在也支持 npm,但其初衷是建立一个更“正确”的运行时。
  • Bun (实用主义): 强调速度兼容性。它不试图改变开发习惯,而是致力于让你现有的 npm 项目跑得飞快。对于大多数希望“无痛提速”的开发者来说,Bun 的迁移阻力更小。

4.4 结论

Bun 是一条鲶鱼。它的出现已经迫使 Node.js 社区加速进化(Node 最近也在添加内置测试运行器、.env 支持等)。

对于开发者,现在是学习 Bun 的最佳时机。它代表了前端工程化向“高性能、统一化”发展的未来方向。即使你不立即在生产环境使用它,它的设计理念和对 Web 标准的拥抱也能让你对现代 JavaScript 运行时有更深的理解。

5. 市场现状与数据 (2024)

虽然 Bun 在技术圈的讨论度极高,但客观地看,它仍处于早期快速增长阶段

5.1 开发者调研数据

根据 State of JS 2024 (最新发布) 的数据:

  • 使用率: Bun 的关注度和使用率持续上升。在“运行时”类别中,虽然 Node.js 依然占据统治地位,但 Bun 已成为增长最快的挑战者。
  • 关注度: Bun 连续两年在开发者调查中获得极高的“感兴趣” (Interest) 评分,这表明它在开发者群体中拥有极高的 Mindshare(心智占有率)。
  • Stack Overflow 2024: 在更广泛的开发者调查中,Bun 被列为增长最快的运行时之一,特别是在追求高性能的 Web 框架(如 ElysiaJS, Hono)社区中采用率极高。

5.2 实际落地场景

目前 Bun 的核心用户群并非直接替换大型企业的核心业务后端,而是集中在以下领域:

  1. CI/CD 流水线: 利用 bun install 极速安装依赖,显著缩短构建时间。
  2. Serverless/Edge: 利用其毫秒级的冷启动速度(比 Node.js 快 3-5 倍),在 AWS Lambda、Cloudflare Workers 等场景下降低延迟和成本。
  3. 新一代框架: 像 ElysiaJS 这样的框架专门为 Bun 优化,性能霸榜,吸引了大量追求极致性能的开发者。
  4. PaaS 支持: 主流云平台如 Railway, Vercel, Netlify 均已原生支持 Bun,降低了部署门槛。

总结: Bun 目前的市场份额虽然无法与 Node.js 抗衡,但其20%+ 的早期采用率证明了它不仅仅是个玩具。它正在通过“农村包围城市”的策略(先占领工具链和边缘计算,再渗透核心运行时)逐步扩大版图。

6. 实战案例:Opencode 如何使用 Bun

6.1 关于 Opencode

在深入技术细节之前,先简单介绍一下案例背景。Opencode 是一个开源的现代 AI 编程助手。与传统的 IDE 插件不同,Opencode 采用 Client-Server 架构,旨在通过 Agent(智能体)自主完成复杂的编程任务。

  • 核心能力: 它可以理解自然语言指令,通过工具(Tools)自主执行终端命令、读写文件、运行测试,甚至进行复杂的代码重构。
  • 技术栈: 项目完全基于 TypeScript 构建,采用 Monorepo 结构,而 Bun 正是其底层的运行时基石。

Opencode 选择 Bun 并非偶然,而是为了满足 AI 编程助手对低延迟高吞吐环境一致性的苛刻要求。

6.2 精确的依赖管理 (Lockfile Policy)

在 [bunfig.toml] 中,Opencode 配置了 save-exact = true

[install]
exact = true
  • 作用: 这意味着当你运行 bun add react 时,Bun 会在 package.json 中写入 "react": "18.2.0" 而不是 "^18.2.0"
  • 收益: 这在大型工程中至关重要,它锁死了所有依赖的确切版本,消除了“在我机器上能跑,在你机器上报错”的幽灵依赖问题,确保了团队开发环境的一致性。

6.2 高性能 HTTP Server (Hono + Bun)

Opencode 的核心是一个 HTTP Server,用于处理 CLI 命令和 AI 会话。它使用了 Hono 框架,并直接运行在 Bun.serve 上。

参考代码 [server.ts]:

import { Hono } from "hono"
import { websocket } from "hono/bun" // 直接使用 Bun 原生 WebSocket

const app = new Hono()

// Hono 会自动检测并使用 Bun.serve 启动
export default {
  port: 4096,
  fetch: app.fetch,
  websocket // 利用 Bun 的高性能 WebSocket 实现
}
  • 收益: 相比于 Node.js + Express,这种组合的吞吐量通常高出 3-5 倍,且内存占用极低。这对于需要常驻后台的 Agent 服务来说非常重要。

6.3 极速测试 (Bun Test)

Opencode 抛弃了 Jest,直接使用 bun test。在 [package.json] 中可以看到:

"scripts": {
  "test": "bun test --timeout 30000"
}
  • 收益: 测试启动几乎是瞬时的。对于包含大量单元测试的 Monorepo,这意味着 CI 流水线的时间可以从几分钟缩短到几十秒。

6.4 Monorepo 工作空间

Opencode 是一个多包项目 (packages/opencode, packages/sdk, packages/util)。Bun 原生支持 Workspace 协议:

"dependencies": {
  "@opencode-ai/sdk": "workspace:*",
  "@opencode-ai/util": "workspace:*"
}
  • 收益: Bun 会自动将本地包链接在一起,修改 sdk 的代码后,opencode 能立即感知,无需重新构建或 npm link

附录:技术选型深度解析

1. 为什么选择 Zig?

Zig 是一门现代系统编程语言,旨在替代 C。

  • 手动内存管理,但更安全: Zig 没有垃圾回收(GC),这让 Bun 能精确控制内存行为,避免了 GC 带来的不可预测的暂停。同时它提供了比 C 更好的内存安全检查。
  • Comptime (编译时执行): Zig 允许在编译阶段执行代码。Bun 利用这一点在编译时预计算了很多数据结构,减少了运行时的开销。
  • C 互操作性: Zig 可以直接引用 C 头文件并链接 C 库。这对于 Bun 集成 JavaScriptCore(它是 C++ 写的,但在 Zig 中调用非常方便)至关重要。

2. 为什么选择 JavaScriptCore (JSC) 而非 V8?

这是 Bun 最具争议也最核心的决策之一。

特性 V8 (Chrome/Node.js) JavaScriptCore (Safari/Bun)
主要优化目标 桌面端/服务器的高性能计算 移动端的快速启动省电
JIT 策略 激进的 TurboFan,预热后代码运行极快 分层编译,优先保证启动速度,后续再优化热点代码
启动时间 较慢(需加载庞大的优化编译器) 极快
内存占用 较高 较低

Bun 的选择逻辑: 现代前端开发(以及 Serverless/Edge 场景)最大的痛点往往是 "启动慢"(运行一个简单的 CLI 命令都要等几秒)。JSC 的架构天然适合解决这个问题。虽然在长时间运行的纯计算任务上 V8 可能略胜一筹,但在 99% 的 Web 场景(I/O 密集型、短生命周期脚本)中,JSC 的快速启动和低内存占用带来的体验提升更为明显。

100s 带你了解 Bun 为什么这么火

1995 年, JavaScript 诞生,主要用于广告弹窗。

2009 年,Node.js 诞生,JS 可以写后端了。

然而这是罪恶的开始,之后 JS 发展出了世界上最复杂的工具链。

于是写一个 Web 项目,你需要 Node.js 作为运行环境,Npm 作为包管理器,Webpack 作为打包工具,Jest 作为测试,还要用 Babel 转译,还要写一大堆没人看懂的配置文件。

这样的痛苦想必你已经体会到了。

2021 年,Bun 说:“为什么不能在运行时就完成所有得事情呢?”

于是它火了。

Bun 是什么?

Bun 本质上是一个 JavaScript 运行时,类似于 Node.js,但极其注重性能。

为了实现高性能,Bun 的核心策略是将:

  1. Node.js 的 C++ 替换成 Zig

  2. Node.js 的 V8 引擎替换成 Safari 使用的 JavaScript Core

这确实让 Bun 取得了不错的性能测试成绩。

image.png

但 Bun 真正革命性的地方在于它不仅仅是一个运行时。

它取代了你的打包工具,于是你可以直接写 TypeScript 或 JavaScript,而不用做任何配置。

它取代了你的测试框架和包管理器,甚至内置数据库驱动程序,同时又保持了与 Node.js 生态的兼容性。

从此以后,你只用一个工具就可以完成所有任务。

当然直接说还是有些抽象,我们直接看代码吧。

Bun 的使用

安装 Bun:

curl -fsSL https://bun.sh/install | bash

创建新项目:

bun init

现在你已经可以编写 TypeScript 代码了。

现在我们搭建一个 Web 服务器,不需要 express,只需要:

const server = Bun.serve({
  port: 3000,
  routes: {
    "/": () => new Response("Bun!"),
  },
});

console.log(`Listening on ${server.url}`);

运行 bun run index.ts 你就可以直接看到效果。

如果你想操作数据库,直接写:

import { Database } from "bun:sqlite";
const db = new Database("./app.sqlite");

如果你想使用 Redis,直接写:

import { redis } from "bun";

// 设置 Key
await redis.set("greeting", "Hello from Bun!");

// 读取数据
const cachedDate = await redis.exists("greeting");

如果你需要安装包,直接运行:

# 安装速度比 npm 快 25 倍
bun install

如果你想写测试,直接写:

// 内置测试工具
import { test, expect } from "bun:test";

test("2 + 2 = 4", () => {
  expect(2 + 2).toBe(4);
});

为什么要关注 Bun?

Bun 本身其实已经很火了。

2025 年底,Anthropic 收购 Bun,更是为 Bun 的发展添了一把柴。

Bun 现在已经普遍被用于 Claude Code 等工具、云平台上的 Serverless Functions 等,这预示着它正在成为 JavaScript 生态系统中的重要力量。

所以如果你正在学 JavaScript,或者想尝试新工具,Bun 值得一看。

即使现在不用,了解这个“未来趋势”也会让你对前端生态有更深的理解。

我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。

欢迎围观我的“网页版朋友圈”,关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。

2026 春晚魔术大揭秘:作为程序员,分分钟复刻一个 | 掘金一周 2.26

本文字数1300+ ,阅读时间大约需要 4分钟。

【掘金一周】本期亮点:

「上榜规则」:文章发布时间在本期「掘金一周」发布时间的前一周内;且符合各个栏目的内容定位和要求。 如发现文章有抄袭、洗稿等违反社区规则的行为,将取消当期及后续上榜资格。

一周“金”选

掘金一周 文章头图 1303x734.jpg

内容评审们会在过去的一周内对社区深度技术好文进行挖掘和筛选,优质的技术文章有机会出现在下方榜单中,排名不分先后。

前端

2026 春晚魔术大揭秘:作为程序员,分分钟复刻一个(附源码) @程序员Sunday

在这个 App 进入“魔术模式”后,键盘事件已经被 e.preventDefault() 拦截了。无论你按哪个数字键,屏幕上只会依次显示程序预设好的那个 差值字符串

我写了个 code-review 的 Agent Skill, 没想到火了 @神三元

四份 checklist 的内容加起来好几千字,如果全塞进 SKILL.md,一上来就会吃掉大量上下文窗口。所以我把它们放在 references/ 里,SKILL.md 里只在需要的步骤写 Load references/xxx.md

构建工具的第三次革命:从 Rollup 到 Rust Bundler,我是如何设计 robuild 的 @sunny_

robuild 就是为解决这些问题而设计的。它基于 Rolldown(Rust 实现的 Rollup 替代品)和 Oxc(Rust 实现的 JavaScript 工具链),专注于库构建场景。

后端

Spring 源码分析 事务管理的实现原理(下) @暮色妖娆丶

这里我以 SpringBoot 源码入口为起点,画了一个相关的流程图,包含了 SpringBoot、Spring 事务、Spring AOP、Spring 事件、BeanFactoryPostProcessor、BeanPostProcessor 等所有 Spring 知识,以及相关模块之间的交互联系

一站式了解Agent Skills @想用offer打牌

因为skills只会暴露name和description,所以agent会自己判断什么场景使用这个skill,就像我们玩游戏一样,脑子已经潜移默化这种场景使用这种skills或者按这样的顺序将多种skills结合使用。

Android

丰田正在使用 Flutter 开发游戏引擎 Fluorite @恋猫de小郭

对于 Fluorite ,目前主要的集成方式就是用 FluoriteView 在 Flutter App 中添加多个 3D 视图,所以可以直接用 Flutter 生态,而 C++ 核心确保在低端硬件(如车载屏)实现主机级效果,避免 Godot 等开源引擎的启动慢/资源重问题。

Flutter 设计包解耦新进展,material_ui 和 cupertino_ui 发布预告 @恋猫de小郭

未来 Flutter 在 Framework 内将不带任何 material 和 cupertino 样式,你可以根据需要选择样式库,甚至觉得使用哪个样式库版本,最重要的是:不升级 Flutter 版本也可以更新最新的设计样式,同时控件 Bug 也可以得到更快的修复和发布

把离线AI代理装进口袋里 @稀有猿诉

正如你的输入是结构化的一样,模型的输出也是结构化的。模型不会仅仅返回一个最终的文本块。相反,它会返回一个 ModelResponse 对象流,其中每个对象代表一种不同的输出类型。模型可能生成纯文本,也可能决定调用你的某个函数。

人工智能

你知道不,你现在给 AI 用的 Agent Skills 可能毫无作用,甚至还拖后腿? @恋猫de小郭

高质量技能 = 搜索空间压缩器,它可以限定决策路径、减少无效探索、提供验证锚点和显式化领域隐性流程,这才是 Skills 能推高 Pareto frontier 的原因,所以,你需要避免百科式技能,它可能带来的更多的噪音。

Rust 编写的 40MB 大小 MicroVM 运行时,完美替代 Docker 作为 AI Agent Sandbox @RoyLin

在 AI Agent 时代,安全的代码执行环境不再是可选项,而是基础设施。A3S Box 正是为这个时代而生的运行时——它让每一行不可信的代码都运行在硬件隔离的沙箱中,让每一字节敏感数据都受到硬件加密的保护,同时让开发者感觉就像在使用 Docker 一样简单。

Skills 实战:让 AI 成为你的领域专家 @冬奇Lab

description 是 Skill 触发的关键。Claude 根据用户请求与 description 的匹配度决定是否加载该 Skill。

📖 投稿专区

大家可以在评论区推荐认为不错的文章,并附上链接和推荐理由,有机会呈现在下一期。文章创建日期必须在下期掘金一周发布前一周以内;可以推荐自己的文章、也可以推荐他人的文章。

AI 写代码总是半途而废?试试这个免费的工作流工具

作为一个用过多种 IDE 的开发者,我想分享一个让我效率 up up 的小工具。

你有没有遇到过这种情况?

  • 跟 AI 聊了半天需求,代码写了一半,上下文满了,AI "失忆"了
  • 项目做到一半搁置,一周后回来完全忘了做到哪了
  • 想加一个功能,结果 AI 把之前的代码改坏了

这些问题都有一个共同原因:上下文衰减(Context Rot)

简单来说,AI 的"记忆"是有限的。当对话太长时,它会慢慢忘掉之前说过的话,导致代码质量下降。

GSD 是什么?

GSD = Get Shit Done(把事做完)

它是一个开源的 AI 编程工作流框架,核心思路很简单:

把项目信息存到文件里,而不是全部塞给 AI。

就像你写代码会用 Git 做版本控制一样,GSD 帮你做"AI 对话的版本控制"。

GSD for Trae

原版 GSD 是为 Claude Code 设计的。因为我日常用 Trae,所以做了这个适配版本。

安装只需一行命令:

npx gsd-trae

或者:

bash <(curl -s https://raw.githubusercontent.com/Lionad-Morotar/get-shit-done-trae/main/install.sh)

它能帮你做什么?

1. 新项目规划

输入 /gsd:new-project,它会:

  • 问你一系列问题,搞清楚你要做什么
  • 自动研究技术方案(可选)
  • 生成项目路线图

2. 阶段式开发

大项目拆成小阶段:

  • /gsd:plan-phase 1 - 规划第一阶段
  • /gsd:execute-phase 1 - 执行第一阶段
  • /gsd:verify-work - 验证做得对不对

每完成一个阶段,进度都会被记录,随时可以接着做。

3. “断点续传”

关掉电脑、明天再来,输入 /gsd:progress,AI 马上知道:

  • 项目做到哪了
  • 接下来该做什么
  • 之前的决策是什么

实际使用感受

我用了一个月,相比 Trae 的 Plan Build 模式最明显的变化:

以前:一个功能聊到一半,AI 开始"胡言乱语",只能新开对话重来

现在:每个阶段都有清晰的目标和验收标准,AI 一直保持在正确的方向上

以前:同时开多个功能,代码互相冲突

现在:按阶段来,做完一个再做下一个,井井有条(进阶用户也可以选择 Worktree 模式)

以前:Plan 文档随意仍在 .trae 的文档目录,没有管理,很难查找

现在:结构化的目录,GSD 和开发者都能轻松阅读

适合谁用?

  • 用 Trae/Gemini/Claude 写代码的开发者
  • 做独立项目、 side project 的人
  • 觉得 AI 编程"聊不动"的新手

相比其他工具的优势

市面上有不少 AI 编程工作流工具,比如 GitHub 的 Spec Kit、OpenSpec、BMAD 等。GSD 的定位不太一样:

工具 特点 GSD 的区别
Spec Kit 企业级、严格阶段门控、30分钟启动 GSD 更轻量,5分钟上手,没有繁琐的仪式
OpenSpec 灵活快速、Node.js 运行 GSD 额外解决了 Context Rot 问题,支持断点续传
BMAD 21个 AI Agent、完整敏捷流程 GSD 不模拟团队,而是聚焦"让开发者高效完成项目"

简单说:如果你期待快速而结构化的流程,又不想被复杂的企业开发规范束缚的同时,确保 AI 编程能稳定输出,GSD 可能是目前最合适的选择。

它是免费的

开源项目,GitHub 地址: github.com/Lionad-Moro…

MIT 协议,可以随便用、随便改。

最后说一句

AI 编程工具越来越强大,但工具只是工具。

好的工作流能让你事半功倍,而 GSD 就是这样一套经过验证的工作流。

不需要改变你现有的开发习惯,安装后输入 /gsd:new-project 试试看。


如果你试过觉得好用,欢迎点个 Star ⭐

如果发现问题,也欢迎提 Issue

React 状态管理:Easy-Peasy 入门指南

大家好!今天我们来聊聊 React 状态管理中的一个非常简单且高效的库——Easy-Peasy。它可以帮助我们轻松管理应用中的状态,让开发更加高效和清晰。

相关链接

官网 Github仓库 npm

为什么选择 Easy-Peasy?

Easy-Peasy 是对 Redux 的一种抽象。它提供了一个重新构思的 API,专注于开发者体验,使你能够快速轻松地管理状态,同时利用 Redux 强大的架构保障,并与其广泛的生态系统进行集成。

所有功能均已内置,无需配置即可支持一个健壮且可扩展的状态管理解决方案,包括派生状态、API 调用、开发者工具等高级特性,并通过 TypeScript 提供完整的类型支持体验。

它社区活跃、更新稳定,跟随社区的 redux 版本同步更新。

  • Zero configuration 零配置

  • No boilerplate 无冗余代码

  • React hooks based API 基于React Hook的API

  • Extensive TypeScript support 广泛的TypeScript支持

  • Encapsulate data fetching 封装数据获取

  • Computed properties 计算属性

  • Reactive actions 响应式动作

  • Redux middleware support 支持 Redux 中间件

  • State persistence 状态持久性

  • Redux Dev Tools 支持Redux开发工具

  • Global, context, or local stores 全局、上下文或本地存储

  • Built-in testing utils 内置测试工具

  • React Native supported 支持 React Native

  • Hot reloading supported 支持热重载

所有这些功能只需安装一个依赖项即可实现。

创建nextjs项目

(base) pe7er@pe7erdeMacBook-Pro github % npx create-next-app@latest
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app 1151ms (cache updated)
Need to install the following packages:
create-next-app@14.2.13
Ok to proceed? (y)

npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app 1071ms (cache miss)
npm http fetch POST 200 https://mirrors.cloud.tencent.com/npm/-/npm/v1/security/advisories/bulk 621ms
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app/-/create-next-app-14.2.13.tgz 1034ms (cache miss)
✔ What is your project named? … nextjs-easy-peasy-template
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /Users/pe7er/Developer/github/nextjs-easy-peasy-template.
控制台完整输出
            (base) pe7er@pe7erdeMacBook-Pro github % npx create-next-app@latest
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app 1151ms (cache updated)
Need to install the following packages:
create-next-app@14.2.13
Ok to proceed? (y)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app 1071ms (cache miss)
npm http fetch POST 200 https://mirrors.cloud.tencent.com/npm/-/npm/v1/security/advisories/bulk 621ms
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/create-next-app/-/create-next-app-14.2.13.tgz 1034ms (cache miss)
✔ What is your project named? … nextjs-easy-peasy-template
✔ Would you like to use TypeScript? … No / YesWould you like to use ESLint? … No / YesWould you like to use Tailwind CSS? … No / YesWould you like to use `src/` directory? … No / YesWould you like to use App Router? (recommended) … No / YesWould you like to customize the default import alias (@/*)? … No / YesWhat import alias would you like configured? … @/*
Creating a new Next.js app in /Users/pe7er/Developer/github/nextjs-easy-peasy-template.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- postcss
- tailwindcss
- eslint
- eslint-config-next
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/react 720ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/react-dom 1250ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/next 2449ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@opentelemetry%2fapi 591ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@playwright%2ftest 1250ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/sass 422ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typescript 2082ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2fnode 1414ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2freact 742ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2freact-dom 566ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss 309ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/tailwindcss 1018ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint 479ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-config-next 1234ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@eslint-community%2feslint-utils 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/undici-types 370ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2fprop-types 487ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@eslint-community%2fregexpp 498ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/csstype 508ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@eslint%2fjs 606ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@eslint%2feslintrc 685ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@humanwhocodes%2fmodule-importer 326ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@nodelib%2ffs.walk 331ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/chalk 329ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ajv 629ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/doctrine 337ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@ungap%2fstructured-clone 663ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/debug 469ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@humanwhocodes%2fconfig-array 871ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/cross-spawn 555ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/escape-string-regexp 413ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/esutils 221ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-visitor-keys 351ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/espree 361ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-scope 376ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/file-entry-cache 251ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/find-up 316ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/esquery 626ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-deep-equal 559ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/graphemer 291ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/glob-parent 399ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/globals 422ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ignore 384ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-glob 218ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-path-inside 223ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/imurmurhash 294ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/js-yaml 262ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/levn 163ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/json-stable-stringify-without-jsonify 337ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/lodash.merge 272ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/strip-ansi 279ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/optionator 301ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/natural-compare 414ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/minimatch 471ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@rushstack%2feslint-patch 296ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/text-table 470ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-import-resolver-node 446ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-import-resolver-typescript 567ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-plugin-import 519ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-plugin-jsx-a11y 598ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2feslint-plugin-next 958ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-plugin-react 393ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@swc%2fhelpers 391ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2fparser 1477ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/busboy 688ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss 12ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/graceful-fs 350ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-plugin-react-hooks 1006ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/caniuse-lite 903ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2feslint-plugin 1840ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fenv 1474ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/styled-jsx 723ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-darwin-arm64 831ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-linux-arm64-gnu 728ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-darwin-x64 894ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/loose-envify 302ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-linux-x64-gnu 1025ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/nanoid 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-win32-x64-msvc 851ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-win32-arm64-msvc 1187ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-linux-x64-musl 1279ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/source-map-js 269ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/arg 380ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@alloc%2fquick-lru 540ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/scheduler 1241ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/chokidar 492ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-glob 1ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/dlv 235ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/didyoumean 360ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-glob 367ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/micromatch 324ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/lilconfig 401ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/jiti 638ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/normalize-path 479ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object-hash 447ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-win32-ia32-msvc 2878ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next%2fswc-linux-arm64-musl 3484ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-load-config 302ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/picocolors 2077ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-js 509ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/picocolors 848ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-nested 206ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-import 803ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-selector-parser 398ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/resolve 409ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/sucrase 382ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/jiti 1ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-visitor-keys 7ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/espree 8ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/minimatch 9ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/debug 11ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-deep-equal 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/import-fresh 207ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/strip-json-comments 315ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fastq 318ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/json-schema-traverse 352ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@nodelib%2ffs.scandir 397ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/uri-js 208ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-json-stable-stringify 453ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@humanwhocodes%2fobject-schema 493ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ansi-styles 206ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/shebang-command 163ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/supports-color 268ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/path-key 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which 340ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ms 363ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/esrecurse 375ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/acorn-jsx 352ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/estraverse 451ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/acorn 412ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/locate-path 183ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/flat-cache 393ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/path-exists 323ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/argparse 260ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-extglob 351ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/estraverse 657ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/brace-expansion 362ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/prelude-ls 548ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/prelude-ls 362ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/type-fest 661ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/word-wrap 229ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/type-check 550ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/deep-is 461ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ansi-regex 287ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-levenshtein 360ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/type-check 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/parent-module 269ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/resolve-from 297ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/reusify 217ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/run-parallel 253ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@nodelib%2ffs.stat 363ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/queue-microtask 393ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/punycode 219ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/color-convert 261ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-flag 314ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/color-name 286ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/isexe 301ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/shebang-regex 355ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint 9ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-plugin-import-x 371ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/glob 498ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2ftype-utils 856ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ts-api-utils 958ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2ftypes 1264ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2futils 1615ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/resolve 13ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2fvisitor-keys 1670ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2fvisitor-keys 1291ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-core-module 518ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fast-glob 1ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@nolyfill%2fis-core-module 533ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2fscope-manager 2208ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-module-utils 513ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/enhanced-resolve 687ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2fscope-manager 1509ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/get-tsconfig 609ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array.prototype.findlastindex 180ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/doctrine 2ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-module-utils 2ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-bun-module 458ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-core-module 2ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array.prototype.flatmap 249ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@rtsao%2fscc 451ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/hasown 231ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array.prototype.flat 381ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array-includes 674ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object.values 372ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object.fromentries 434ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object.groupby 481ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/semver 371ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/tsconfig-paths 410ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/aria-query 241ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint%2ftypescript-estree 2575ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ast-types-flow 650ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/emoji-regex 509ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-iterator-helpers 487ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/damerau-levenshtein 591ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/axobject-query 623ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/safe-regex-test 344ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/estraverse 9ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/jsx-ast-utils 492ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.includes 446ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/axe-core 1117ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/language-tags 517ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array.prototype.findlast 479ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array.prototype.tosorted 564ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object.entries 458ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/jsx-ast-utils 482ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.repeat 345ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/prop-types 545ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.matchall 679ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/foreground-child 164ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/path-scurry 299ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/minipass 343ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/jackspeak 447ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint 6ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typescript 27ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/semver 2ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/micromatch 4ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/brace-expansion 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/glob-parent 6ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/merge2 320ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/supports-preserve-symlinks-flag 352ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/path-parse 617ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/graceful-fs 8ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/resolve-pkg-maps 513ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/tapable 631ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/call-bind 165ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/call-bind 186ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/define-properties 251ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-string 280ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/get-intrinsic 378ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-object-atoms 511ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/call-bind 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/define-properties 10ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-errors 288ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/define-properties 378ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-object-atoms 269ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-shim-unscopables 344ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-abstract 562ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-abstract 271ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-shim-unscopables 420ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-abstract 503ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-shim-unscopables 551ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/json5 287ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/function-bind 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2fjson5 368ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/strip-bom 296ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/minimist 481ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-abstract 1950ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-define-property 193ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/set-function-length 251ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-property-descriptors 283ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/arraybuffer.prototype.slice 341ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/data-view-byte-length 133ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/define-data-property 493ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/data-view-buffer 272ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object-keys 537ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/available-typed-arrays 358ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/array-buffer-byte-length 573ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/function.prototype.name 284ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-set-tostringtag 398ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/data-view-byte-offset 474ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-property-descriptors 11ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/gopd 250ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-to-primitive 397ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/globalthis 409ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-symbols 205ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/get-symbol-description 534ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-proto 298ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-data-view 185ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-callable 279ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-regex 201ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/internal-slot 460ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-array-buffer 521ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-negative-zero 332ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-shared-array-buffer 304ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-weakref 378ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.trim 210ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object.assign 365ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object-inspect 388ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-typed-array 547ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/regexp.prototype.flags 435ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typed-array-byte-offset 138ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typed-array-byte-length 171ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/safe-array-concat 550ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-proto 3ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typed-array-length 204ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/unbox-primitive 168ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.trimstart 369ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string.prototype.trimend 462ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which-typed-array 189ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typed-array-buffer 524ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-tostringtag 333ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/define-data-property 6ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-callable 7ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-tostringtag 9ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-shared-array-buffer 11ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which-typed-array 4ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/possible-typed-array-names 128ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-symbol 179ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-date-object 258ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/isarray 271ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/functions-have-names 323ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/set-function-name 369ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/for-each 330ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/side-channel 517ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/for-each 384ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/for-each 544ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which-boxed-primitive 359ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/has-bigints 517ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/for-each 528ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/language-subtag-registry 438ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/deep-equal 513ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/iterator.prototype 516ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object-is 334ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-arguments 452ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which-collection 508ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/es-get-iterator 577ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-set 282ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-set 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-number-object 376ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/stop-iteration-iterator 422ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-bigint 449ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-boolean-object 456ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-weakmap 189ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-map 566ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-map 604ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-weakset 434ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/reflect.getprototypeof 424ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/set-function-name 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/loose-envify 8ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/object-assign 156ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/react-is 1076ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/picomatch 242ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/braces 404ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/rimraf 287ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/keyv 422ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/flatted 709ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/p-locate 430ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/glob 8ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/json-buffer 406ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/signal-exit 220ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/lru-cache 234ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@pkgjs%2fparseargs 296ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@isaacs%2fcliui 367ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/callsites 198ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/which-builtin-type 496ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/strip-ansi 13ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/strip-ansi 13ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/wrap-ansi 188ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/wrap-ansi 261ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string-width 387ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string-width 518ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ansi-regex 23ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/string-width 22ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ansi-styles 25ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/emoji-regex 28ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-fullwidth-code-point 235ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eastasianwidth 381ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/p-limit 321ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fill-range 488ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/to-regex-range 357ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-number 380ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/concat-map 382ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/balanced-match 385ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/react 17ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/source-map-js 10ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/nanoid 10ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/streamsearch 310ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@swc%2fcounter 441ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/tslib 499ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/client-only 1119ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/yocto-queue 300ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/js-tokens 222ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-generator-function 602ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-async-function 812ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-finalizationregistry 844ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/inherits 249ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/once 407ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/path-is-absolute 410ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/inflight 467ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fs.realpath 522ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ts-node 1383ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@swc%2fcore 659ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@swc%2fhelpers 4ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@swc%2fwasm 707ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types%2fnode 24ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/typescript 19ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/normalize-path 10ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-value-parser 243ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/lilconfig 5ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-binary-path 304ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/anymatch 308ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/postcss-selector-parser 6ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/read-cache 419ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/camelcase-css 422ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/cssesc 188ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/readdirp 554ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/util-deprecate 276ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/mz 166ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/fsevents 745ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/yaml 523ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/commander 377ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/lines-and-columns 322ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@jridgewell%2fgen-mapping 449ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/pirates 463ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/ts-interface-checker 656ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/binary-extensions 268ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/pify 159ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@jridgewell%2fsourcemap-codec 256ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/any-promise 312ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@jridgewell%2fset-array 315ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/thenify-all 467ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@jridgewell%2ftrace-mapping 684ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@jridgewell%2fresolve-uri 320ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/thenify 341ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/minimist 4ms (cache hit)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/wrappy 294ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/wrappy 345ms (cache updated)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next/env/-/env-14.2.13.tgz 411ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/get-tsconfig/-/get-tsconfig-4.8.1.tgz 412ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz 428ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-config-next/-/eslint-config-next-14.2.13.tgz 462ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz 520ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/types/-/types-8.7.0.tgz 522ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.13.tgz 542ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/parser/-/parser-8.7.0.tgz 564ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/utils/-/utils-8.7.0.tgz 573ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz 581ms (cache miss)
npm http fetch POST 200 https://mirrors.cloud.tencent.com/npm/-/npm/v1/security/advisories/bulk 712ms
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz 676ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types/prop-types/-/prop-types-15.7.13.tgz 675ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/is-bun-module/-/is-bun-module-1.2.1.tgz 679ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz 698ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types/react/-/react-18.3.9.tgz 807ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz 918ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz 927ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/@types/node/-/node-20.16.7.tgz 1032ms (cache miss)
npm http fetch GET 200 https://mirrors.cloud.tencent.com/npm/next/-/next-14.2.13.tgz 3489ms (cache miss)
added 360 packages, and audited 361 packages in 1m
137 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
Initialized a git repository.
Success! Created nextjs-easy-peasy-template at /Users/pe7er/Developer/github/nextjs-easy-peasy-template
      
    
    

项目创建完成后,应该启动一次项目,确保项目运行无误。

安装easy-peasy

首先,你需要安装easy-Peasy。可以使用 npm

npm install easy-peasy

创建一个简单的状态管理模型

接下来,我们来创建一个简单的状态管理模型。假设我们要管理一个计数器的状态:

import { createStore, action } from 'easy-peasy';

// 定义模型
const model = {
  count: 0,
  increment: action((state) => {
    state.count += 1;
  }),
  decrement: action((state) => {
    state.count -= 1;
  }),
};

// 创建 store
const store = createStore(model);

在组件中使用 Easy-Peasy

接下来,我们可以在 React 组件中使用这个 store:

import React from 'react';
import { StoreProvider, useStoreState, useStoreActions } from 'easy-peasy';

const Counter = () => {
  const count = useStoreState((state) => state.count);
  const { increment, decrement } = useStoreActions((actions) => ({
    increment: actions.increment,
    decrement: actions.decrement,
  }));

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>减少</button>
    </div>
  );
};

const App = () => {
  return (
    <StoreProvider store={store}>
      <Counter />
    </StoreProvider>
  );
};

export default App;

总结

通过以上步骤,我们实现了一个简单的计数器应用。Easy-Peasy 的使用极其简单,并且能够有效地管理组件间的状态。如果你正在寻找一个轻量级且易于使用的状态管理解决方案,不妨试试 Easy-Peasy!

希望这篇博客能帮助你更好地理解 Easy-Peasy 的使用。如果你有任何问题或想法,欢迎在评论区分享!

React Native 多环境配置全攻略:环境变量、iOS Scheme 和 Android Build Variant

在 React Native 项目开发中,经常会遇到不同环境(开发、测试、生产)需要不同配置的需求。本文结合实践经验,详细讲解如何管理环境变量(.env 文件)、如何配置和使用 iOS 的 Scheme,以及 Android 的 Build Variant,帮助你构建灵活且高效的多环境应用。

image.png


环境变量管理 — 使用 .env 文件

为什么用 .env

  • 将敏感信息(API 地址、Key 等)和环境相关配置分离
  • 不同环境使用不同的配置,不必修改代码
  • 方便本地和 CI/CD 环境管理

工具选择

  • dotenv:读取 .env 文件的标准方案
  • babel-plugin-inline-dotenv:打包时将环境变量注入代码

使用示例

  1. 在项目根目录创建不同的环境配置文件,比如:
.env.development
.env.test
.env.production

2. 安装依赖:

yarn add dotenv babel-plugin-inline-dotenv --dev

3. 配置 Babel 插件 babel.config.js

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    ['inline-dotenv', {
      path: process.env.DOTENV_CONFIG_PATH || '.env'
    }]
  ]
};

4. 启动时指定环境配置文件:

DOTENV_CONFIG_PATH=.env.test yarn start

iOS 多环境配置

什么是 Scheme 和 Configuration?

  • Scheme:Xcode 的构建方案,管理运行和打包的流程
  • Configuration:对应不同的 Build Settings,比如 Debug、Release,通常和 Scheme 配合使用

新增 Scheme

  • 在 Xcode 顶部菜单选择 Product > Scheme > Manage Schemes...
  • 点击 + 新建 Scheme,复制现有 Scheme,命名为 MyReactnativeTemplate-DevMyReactnativeTemplate-Test
  • 每个 Scheme 关联对应的 Build Configuration

image.png

3. 配置图片

image.png

  • 打开项目的 Build Settings。打开项目的 Build Settings。

  • 搜索 ASSETCATALOG_COMPILER_APPICON_NAME,设置为对应环境的 AppIcon 名称(比如 Debug 配置使用 AppIcon-Debug,Release 使用默认的 AppIcon)。

  • 也可以在 Xcode Scheme 的 Build Configuration 里,针对不同 Scheme 设置不同的 Build Configuration,从而加载不同的 AppIcon。

image.png

~~### 3. 新增 .xcconfig 文件?

~~* 在 Xcode 新建配置文件(比如 Dev.xcconfigTest.xcconfig

  • 配置各环境特有的变量
  • 在 Build Settings 中,将不同 Configuration 指向对应的 .xcconfig~~~~

在 React Native 中使用 Scheme 和环境变量

  • 通过 react-native run-ios --scheme "MyReactnativeTemplate-Dev" 指定 Scheme
  • 使用 DOTENV_CONFIG_PATH=.env.test yarn ios --scheme "MyReactnativeTemplate-Dev" 来启动,确保使用正确的环境变量

关于 xcodebuild 命令

带空格的 Scheme 名称要用引号包裹:

xcodebuild -workspace MyReactnativeTemplate.xcworkspace -configuration Debug -scheme "MyReactnativeTemplate-Dev" -destination 'id=设备ID'

Android 多环境配置

什么是 Build Variant 和 Flavor?

  • Build Variant = Build Type + Flavor
  • 通过 Flavor 可以生成多个不同版本的 APK(比如开发版、测试版、生产版)

配置 build.gradle

android/app/build.gradle 中新增 flavor:

android {
    ...
    flavorDimensions "env"
    productFlavors {
        dev {
            dimension "env"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
        }
        test {
            dimension "env"
            applicationIdSuffix ".test"
            versionNameSuffix "-test"
        }
        prod {
            dimension "env"
        }
    }
}

环境变量管理

  • 使用第三方库 react-native-config 管理 Android 端环境变量
  • 根据 flavor 加载不同的 .env 文件,比如 .env.dev.env.test

构建和运行指定 Flavor

# 运行开发版
yarn android --variant=devDebug

# 运行测试版
yarn android --variant=testDebug

# 打包生产版
cd android && ./gradlew assembleProdRelease

App 内集成开发者工具实现环境切换

在开发过程中,为了方便调试和验证不同环境,很多项目会在 App 里内置开发者工具,支持动态切换环境配置。常见实现有两种方式:

配置写在代码里,开发者工具里切换

  • 将多环境配置(API 地址、Key 等)预先写在代码里(JSON 或常量)
  • 开发者工具页面展示环境选项(比如开发、测试、预发布、生产)
  • 用户选择后,App 内部切换到对应配置,刷新接口调用等

优点:实现简单,不依赖外部服务
缺点:配置发布需跟随 App 版本更新,不够灵活

从后台配置中心动态拉取环境配置(类似 Nacos)

  • App 启动或打开开发者工具时,向配置中心请求最新环境配置列表和内容
  • 用户选择环境后,App 动态加载对应的配置(比如 API 地址)
  • 支持远程更新配置,无需重新发布 App

优点:配置管理集中,灵活性高
缺点:需要搭建并维护配置中心,增加复杂度


无论哪种方式,都能极大提升多环境调试效率。建议根据项目规模和需求选择合适方案。

总结与建议

  • 环境变量:用 .env 文件和 dotenv 系列库统一管理,方便调试和打包
  • iOS:通过 Scheme + Configuration + .xcconfig 实现多环境,运行和打包时指定 Scheme 和环境变量
  • Android:利用 Build Flavor + Build Variant 配置多环境,结合 react-native-config 实现环境变量注入
  • 启动/打包:使用命令时指定 Scheme 或 Flavor,确保打包环境正确
  • App中增加开发者工具时,则需要统一配置不同环境的配置

参考资料

Native Environment Variables](github.com/goatandshee…)

LeetCode 530. 二叉搜索树的最小绝对差:两种解法详解(迭代+递归)

LeetCode 上一道经典的二叉搜索树(BST)题目——530. 二叉搜索树的最小绝对差,这道题看似简单,却能很好地考察我们对 BST 特性的理解,以及二叉树遍历方式的灵活运用。下面我会从题目分析、核心思路、两种解法拆解,到代码细节注释,一步步帮大家搞懂这道题,新手也能轻松跟上。

一、题目解读

题目很直白:给一个二叉搜索树的根节点 root,返回树中任意两个不同节点值之间的最小差值,差值是正数(即两值之差的绝对值)。

这里有个关键前提——二叉搜索树的特性:中序遍历二叉搜索树,得到的序列是严格递增的(假设树中没有重复值,题目未明确说明,但测试用例均满足此条件)。

这个特性是解题的核心!因为递增序列中,任意两个元素的最小差值,一定出现在相邻的两个元素之间。比如序列 [1,3,6,8],最小差值是 3-1=2,而不是 8-1=7 或 6-3=3。所以我们不需要暴力枚举所有两两组合,只需要在中序遍历的过程中,记录前一个节点的值,与当前节点值计算差值,不断更新最小差值即可。

二、核心解题思路

  1. 利用 BST 中序遍历为递增序列的特性,将“任意两节点的最小差值”转化为“中序序列中相邻节点的最小差值”;

  2. 遍历过程中,维护两个变量:min(记录当前最小差值,初始值设为无穷大)、pre(记录前一个节点的值,初始值设为负无穷大,避免初始值影响第一次差值计算);

  3. 遍历每个节点时,用当前节点值与pre 计算绝对值差值,更新 min,再将 pre 更新为当前节点值;

  4. 遍历结束后,min 即为答案。

接下来,我们用两种最常用的遍历方式实现这个思路:迭代中序遍历(解法1)和递归中序遍历(解法2)。

三、解法一:迭代中序遍历(非递归)

迭代遍历的核心是用“栈”模拟递归的调用过程,避免递归深度过深导致的栈溢出(虽然这道题的测试用例大概率不会出现,但迭代写法更通用,适合处理大型树)。

3.1 代码实现(带详细注释)

// 先定义 TreeNode 类(题目已给出,此处复用)
class TreeNode {
  val: number
  left: TreeNode | null
  right: TreeNode | null
  constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
    this.val = (val === undefined ? 0 : val)
    this.left = (left === undefined ? null : left)
    this.right = (right === undefined ? null : right)
  }
}

// 解法1:迭代中序遍历
function getMinimumDifference_1(root: TreeNode | null): number {
  // 边界处理:空树返回0(题目中树至少有一个节点?但严谨起见还是判断)
  if (!root) return 0;
  let min = Infinity; // 最小差值,初始为无穷大
  let pre = -Infinity; // 前一个节点的值,初始为负无穷大
  const stack: TreeNode[] = []; // 用于模拟中序遍历的栈
  let curr: TreeNode | null = root; // 当前遍历的节点

  // 第一步:将左子树所有节点压入栈(中序遍历:左 -> 根 -> 右)
  while (curr) {
    stack.push(curr);
    curr = curr.left; // 一直向左走,直到最左节点
  }

  // 第二步:弹出栈顶节点,处理根节点,再遍历右子树
  while (stack.length) {
    const node = stack.pop(); // 弹出栈顶(当前要处理的根节点)
    if (!node) continue; // 防止空节点(理论上不会出现)

    // 处理右子树:将右子树的所有左节点压入栈
    if (node.right) {
      let right: TreeNode | null = node.right;
      while (right) {
        stack.push(right);
        right = right.left;
      }
    }

    // 计算当前节点与前一个节点的差值,更新最小差值
    min = Math.min(min, Math.abs(pre - node.val));
    // 更新pre为当前节点值,为下一个节点做准备
    pre = node.val;
  }

  return min;
};

3.2 思路拆解

  1. 初始化:栈用于存储待处理的节点,curr指向根节点,先将根节点的所有左子节点压入栈(因为中序遍历要先访问左子树);

  2. 弹出栈顶节点(此时该节点的左子树已处理完毕),先处理其右子树(将右子树的所有左节点压入栈,保证下一次弹出的是右子树的最左节点);

  3. 计算当前节点与pre的差值,更新min,再将pre更新为当前节点值;

  4. 重复上述过程,直到栈为空,遍历结束。

优势:不依赖递归栈,避免递归深度过大导致的栈溢出,空间复杂度由递归的 O(h)(h为树高)优化为 O(h)(栈的最大深度也是树高),实际运行更稳定。

四、解法二:递归中序遍历

递归写法更简洁,代码量少,思路也更直观,适合树的深度不大的场景。核心是用递归函数实现中序遍历的“左 -> 根 -> 右”顺序。

4.1 代码实现(带详细注释)

// 解法2:递归中序遍历
function getMinimumDifference_2(root: TreeNode | null): number {
  if (!root) return 0;
  let min = Infinity; // 最小差值
  let pre = -Infinity; // 前一个节点的值

  // 递归函数:实现中序遍历
  const dfs = (node: TreeNode) => {
    if (!node) return; // 递归终止条件:节点为空

    // 1. 遍历左子树(左)
    if (node.left) dfs(node.left);

    // 2. 处理当前节点(根):计算差值,更新min和pre
    min = Math.min(min, Math.abs(pre - node.val));
    pre = node.val;

    // 3. 遍历右子树(右)
    if (node.right) dfs(node.right);
  }

  // 从根节点开始递归
  dfs(root);
  return min;
};

4.2 思路拆解

  1. 定义递归函数 dfs,参数为当前节点,负责遍历以该节点为根的子树;

  2. 递归终止条件:当前节点为空,直接返回;

  3. 先递归遍历左子树(保证左子树先被处理);

  4. 处理当前节点:计算当前节点与pre的差值,更新min,再将pre更新为当前节点值;

  5. 最后递归遍历右子树;

  6. 从根节点调用dfs,完成整个树的遍历,返回min。

优势:代码简洁,思路直观,容易理解和编写;劣势:当树的深度很大时(如链式树),会出现递归栈溢出,此时迭代写法更合适。

五、两种解法对比与总结

解法 遍历方式 时间复杂度 空间复杂度 优势 劣势
解法1 迭代中序 O(n)(每个节点遍历一次) O(h)(h为树高,栈的最大深度) 稳定,无栈溢出风险,通用 代码稍长,需要手动维护栈
解法2 递归中序 O(n)(每个节点遍历一次) O(h)(递归栈深度) 代码简洁,思路直观,易编写 深度过大时会栈溢出

关键总结

  1. 这道题的核心是利用 BST 中序遍历为递增序列,将“任意两节点最小差值”转化为“相邻节点最小差值”,避免暴力枚举;

  2. 两种解法的核心逻辑一致,只是遍历方式不同,可根据树的深度选择:树深较小时用递归,树深较大时用迭代;

  3. 注意初始值的设置:min设为无穷大(保证第一次差值能更新min),pre设为负无穷大(避免初始值与第一个节点值计算出不合理的差值);

  4. 边界处理:空树返回0(题目中树至少有一个节点,但严谨起见必须判断)。

六、拓展思考

如果这道题不是 BST,而是普通二叉树,该怎么解?

答案:先遍历所有节点,将节点值存入数组,再对数组排序,计算相邻元素的最小差值。时间复杂度 O(n log n)(排序耗时),空间复杂度 O(n)(存储所有节点值),效率低于本题的解法,由此可见利用数据结构特性解题的重要性。

好了,这道题的两种解法就讲解完毕了。希望大家能通过这道题,加深对 BST 特性和二叉树中序遍历的理解,下次遇到类似题目能快速想到解题思路。

Windows判断是笔记本还是台式

前言

在开发桌面应用程序时,我们经常需要根据设备类型来调整功能或界面。例如,触摸板管理功能只对笔记本电脑有意义,而对台式机用户来说完全不需要。本文将介绍一种基于系统机箱类型(Chassis Type)的可靠检测方法。

正文

Windows系统通过WMI(Windows Management Instrumentation)提供了丰富的硬件信息查询接口。其中,Win32_SystemEnclosure 类包含了系统机箱的详细信息,包括一个关键属性:ChassisTypes

ChassisTypes 不同的数值代表不同的设备形态:

笔记本电脑相关类型:

  • 8: Portable(便携式)
  • 9: Laptop(笔记本)
  • 10: Notebook(笔记本)
  • 14: Sub Notebook(子笔记本)
  • 30: Tablet(平板电脑)
  • 31: Convertible(可转换)
  • 32: Detachable(可拆卸)

台式机相关类型:

  • 3: Desktop(桌面)
  • 4: Low Profile Desktop(低姿态桌面)
  • 5: Pizza Box(披萨盒式)
  • 6: Mini Tower(迷你塔式)
  • 7: Tower(塔式)
  • 13: All in One(一体机)
  • 15: Space-Saving(节省空间型)
  • 16: Lunch Box(午餐盒式)

详细文档可以看 :learn.microsoft.com/en-us/windo…

Power Shell可以直接运行命令查看

(Get-CimInstance -ClassName Win32_SystemEnclosure).ChassisTypes

image.png

结尾

感兴趣的可以去试试

“啪啪啪”三下键盘,极速拉起你的 uni-app 项目!

说实话,我也不想造轮子。但试了一圈之后,我发现了一个让我忍不了的问题:选了不要某个功能,生成的代码里居然还有它的 import 和空壳文件。 与其花半小时手动删代码,不如用 hy-uni —— 三下键盘,1 秒钟搞定!


🚫 那些年,我们新建项目后手动删过的代码

如果你经常用社区的高分脚手架创建项目,一定会遇到这个进退两难的死胡同:

  • 官方模板太"毛坯":API 拦截器、状态管理全要自己从 0 开始配。新手直接劝退。

  • 社区模板太"精装":不仅送你一堆组件,还送你几个业务全景页。新建项目第一件事,就是花半小时去删那些不需要的页面和 npm 包。最痛苦的是,删的时候还得提心吊胆,生怕漏删了某个 import 导致整个项目一跑就白屏报错。

第 21 次从头搭项目时,我终于受不了了。于是,我过年时花了点时间写了 hy-uni


🎯 先说结论:三下键盘,极速拉起项目

一条命令,三下键盘,1 秒钟,带给你一个干干净净的、随时可进入业务开发的工业级 uni-app 项目:

# ⚡ 极速拉起纯净骨架(1 秒钟)
npx hy-uni my-app --pure
# 或者 📋 交互式精装配置(30 秒内完成)
npx hy-uni my-app

核心理念:你不要的功能,连一行代码、一段注释、一个 npm 依赖,都不该出现在最终的产物中。


⚡ 速度对比(为什么说"极速"?)

方案 时间 特点
hy-uni --pure ⚡ 1 秒 三下键盘极速拉起纯净骨架
hy-uni (交互) 📋 30 秒 选择功能后自动生成完整项目
官方脚手架 5 分钟+ 毛坯房,需要自己配置工程化
社区全量模板 10 分钟+ 功能全但冗余,需要手动删代码

关键对比:hy-uni 不仅快,而且不用删代码 —— 你不选的功能从代码到依赖全部消失。


💻 极客最爱的"双轨"构建体验

很多老手开发者拥有"代码洁癖",喜欢毫无业务代码的"极净空壳";也有很多开发者希望项目能"满级出生",自带网络请求和主题切换方案。

在这款 CLI 中,我们将选择权完全交还给你。

路线 A:极速构建"极致纯净"空壳(老手狂喜)

对于只想要**"帮我把工程化基建搭好,其他的我自己来"**的极客,你只需在命令后敲入一个 --pure 参数:


npx hy-uni my-app --pure

啪啪啪三下键盘,敲下回车,1秒钟静默生成。 没有任何繁琐的交互问答选项,你将直接获得一个强迫症狂喜的极净项目:

  • 只有基础工程化体系:Vue 3 + TypeScript + Vite + UnoCSS + Pinia 开箱即用。

  • 没有任何网络请求、主题切换、业务示例等多余代码。

  • 目录结构极其纯粹,没有多余的文件夹。

路线 B:交互式精装配置(开箱即用)

如果不加 --pure,CLI 则会提供完全可定制的丝滑交互面板:


┌ 🚀 火叶 - 快速创建高性能 uni-app 项目
│
● 模板来源: 缓存 (~/.huoye/templates/) [2天前更新]
│
◇ 请输入项目名称:
│ my-app
│
◇ 请选择创建路径:
│ ./demo
│
◇ 是否需要网络请求层?
│ ○ Yes / ● No
│
◆ 是否需要业务示例页面?
│ ○ Yes / ● No
│
◆ 是否需要主题管理?
│ ○ Yes / ● No
│
◆ 确认创建项目?
│ ● Yes / ○ No
│
◇ 🎉 恭喜!您的项目已准备就绪。
│
◇ Getting Started  ─────────╮
│                           │
│ $ cd demo/my-app          │
│ $ pnpm install            │
│ $ pnpm dev:h5             │
│                           │
├───────────────────────────╯

此时,选择全选 Yes 的你,将获得一个"满级配置"项目:

  • 封装极佳的 Http 客户端、请求拦截器体系及全局错误分类处理机制。

  • 完善的亮暗色主题无缝切换落地方案及 CSS 变量体系。

最硬核的是:无论你是走纯净路线还是全选路线,生成的项目 App.vuemain.ts 以及 package.json 中的所有代码,都会像你自己手写的一般融洽,没有任何一点"被暴力注销掉"的痕迹。

💡 温馨提示:三个功能之间有依赖关系。"业务示例页面"依赖"网络请求层"——因为示例必须有 API 封装才能跑起来。所以如果你不选"网络请求层",CLI 就不会问你要不要"业务示例"。这样设计是为了保证生成的项目永远可以直接运行,没有任何破碎的依赖关系。


💡 三种使用场景速查

我想要 命令 适合谁
极速纯净空壳 npx hy-uni my-app --pure 有代码洁癖的老手,想自己搭业务
交互式精装配置 npx hy-uni my-app 想要完整方案,但不想要冗余代码
本地开发版本 npx hy-uni my-app --local 项目贡献者,想用最新开发模板

📂 看看生成出来的项目差异

路线 A 生成结果(--pure)

my-app/
├── src/
│ ├── pages/
│ │ ├── index/index.vue
│ │ └── about/about.vue
│ ├── layouts/default.vue
│ ├── store/index.ts
│ ├── utils/
│ │ ├── platform.ts
│ │ ├── system.ts
│ │ ├── data.ts
│ │ └── time.ts
│ ├── style/
│ └── static/
├── vite.config.ts
├── tsconfig.json
└── package.json ← 只有基础依赖

路线 B 生成结果(全选)


my-app/
├── src/
│ ├── pages/
│ │ ├── index/index.vue
│ │ ├── about/about.vue
│ │ ├── theme/ ← 新增
│ │ └── examples/ ← 新增
│ │ ├── api-demo.vue
│ │ ├── form-demo.vue
│ │ └── list-demo.vue
│ ├── api/ ← 新增
│ │ ├── client.ts
│ │ ├── interceptors.ts
│ │ ├── errors.ts
│ │ └── modules/
│ ├── composables/
│ │ └── useTheme.ts ← 新增
│ ├── config/
│ │ └── theme.ts ← 新增
│ ├── components/
│ │ └── ThemeToggle.vue ← 新增
│ ├── store/
│ │ ├── theme.ts ← 新增
│ │ ├── index.ts
│ │ └── modules/
│ │ ├── app.ts
│ │ └── counter.ts ← 新增
│ ├── layouts/default.vue
│ ├── utils/
│ ├── style/
│ └── static/
├── vite.config.ts
├── tsconfig.json
└── package.json ← 完整的依赖列表

对比一目了然 —— 不选就是真的没有,不是"注释掉"。


🛠️ 不只是干净:开箱即用的重型工程底座

不管你怎么选裁剪,hy-uni 都为你提供了工业级的开发体验,包含了 7 个 Vite 核心插件的自动装配:

插件 作用
vite-plugin-uni-pages 页面自动路由生成
vite-plugin-uni-layouts 布局系统搭建
vite-plugin-uni-manifest manifest 编程化配置
vite-plugin-uni-components 组件按需自动导入
unplugin-auto-import Vue / uni-app API 自动导入
UnoCSS 原子化极速 CSS 构建
mp-selector-transform 小程序选择器兼容隔离转换

这意味着,创建完项目后:

  • 你不需要手动导入 refonMounted

  • 你不需要手动去繁琐的 pages.json 注册页面和组件。

  • 路径别名 @/src/ 已全部打通。

  • 开发体验直接拉满。


✨ 你到底能得到什么?

基础工程化(所有项目都有)

  • Vue 3 + TypeScript —— 类型安全,开发爽

  • Vite 5 —— 毫秒级热更新,极速开发

  • 7 个 Vite 插件 —— 页面自动路由、组件自动导入、manifest 编程化配置等,全配好

  • UnoCSS —— 按需生成原子化 CSS,再也不用手写 class

  • Pinia 状态管理 —— 开箱即用的持久化存储(适配小程序)

  • ESLint + TypeScript 类型检查 —— 代码规范自动化

可选功能 1:网络请求层

选了它,项目会多出完整的 src/api/ 目录:

import { get, post } from "@/api"
// GET 请求,自动拼接 params
const users = await get("/users", { page: 1, limit: 10 })
// POST 请求
const result = await post("/users", { name: "张三", age: 25 })

你获得了什么:

  • HTTP 客户端(基于 uni.request,支持 GET/POST/PUT/DELETE/PATCH)

  • 请求/响应/错误拦截器(自动注入 Token、处理超时等)

  • 7 种自定义错误分类(网络、超时、鉴权、权限等)

  • 跨平台兼容(H5 / 小程序 / App 无缝切换)

  • 完整的 API 模块化示例

不选它? src/api/ 目录根本不存在,package.json 里也没任何相关依赖。干干净净。

可选功能 2:主题管理

选了它,你就能这样用:

<script setup>
import { useTheme } from "@/composables/useTheme"
const { isDark, themeStore } = useTheme()
</script>
<template>
<button @click="themeStore.toggleTheme()">
{{ isDark ? "切换到亮色" : "切换到暗色" }}
</button>
</template>

你获得了什么:

  • 亮色/暗色/跟随系统 三种主题模式

  • 8 种预设主色调,可自定义

  • 20+ CSS 变量自动注入

  • 多端适配(H5 用 CSS 变量、小程序用全局事件、App 用状态栏同步)

  • 主题切换组件 + 完整的设置页面

不选它? 上面所有文件全部消失。布局组件里的主题代码也会被移除,替换成一个固定的 background-color: #f8f8f8 —— 不是留空,而是提供正确的 fallback。

可选功能 3:业务示例页面

选了它(需要先选网络请求层),你会得到 3 个完整的业务演示:

  • API 调用演示 —— 列表获取、详情查看、数据创建的完整流程

  • 表单演示 —— 输入、选择、复选、日期选择器,带表单验证

  • 列表演示 —— 上拉加载、下拉刷新、搜索过滤的完整实现

这不是 "Hello World",每个页面都是可以直接拿来改改就用的业务代码

不选它? 这些示例页面全部消失,首页上的导航入口也会一起消失(不会留下死链接)。


⚙️ 底层揭秘:如何做到代码级无痕裁剪?

一般的脚手架提供的是"多套模板分支组合"。而 hy-uni 创新性地引入了 "特征标记系统 (Feature Markers)",实现了一份源码,2^N 种自由组合引擎

我们在架构底层源码中,巧妙地隐藏了特定的注释标记:

1. 单行精确抹除

如果在 CLI 里没选 examples 示例功能,下面带有 // 【examples】 标记的代码行,会从物理层面直接消失:

export * from "./modules/app"
export { useCounterStore } from "./modules/counter" // 【examples】

2. 块级区域剥离(支持多语言环境)

如果没选 theme 主题功能,被包裹的代码块整块剥离(支持 TS、SCSS、Vue 甚至 HTML 注释):

<!-- 【theme:start】 -->
<view class="nav-link" @click="goToPage('/pages/theme')">
    <text>主题设置</text>
</view>
<!-- 【theme:end】 -->

3. 独门绝技:反向兜底(Fallback)裁剪

这是市面上其他脚手架极难做到的技术细节。针对"如果不选某个高阶模块,我仍然需要保留一套写死的基础兜底代码"的场景,我们设计了 ! 反向保留标记:


.layout {
    // 【!theme:start】 (如果没选动态主题,就保留这段写死的极简灰色背景)
    background-color: #f8f8f8;
    // 【!theme:end】

    // 【theme:start】 (如果选了主题,才保留动态的 CSS 变量注入机制)
    background-color: var(--bg-color-primary);
    transition: background-color 0.3s;
    // 【theme:end】
}

正是这套底层切割引擎,加上我们对 npm 依赖 dependencies 的按树剥离,以及支持功能间的链式感知(不支持底层功能时不展示进阶询问逻辑),才铸就了极致纯净的代码产物质量。


🔧 进阶:把它变成你们团队的专属黑科技

"这套裁剪逻辑不错,但我司有祖传架构,我单纯想白嫖这套神级裁剪引擎怎么办?"

完全没问题。整个脚手架能力是靠底层模板根目录的 .templaterc.json 驱动的:

{
"features": {
    "auth": {
           "name": "权限管理",
           "files": ["src/store/user.ts"],
           "dependencies": ["jwt-decode"]
        }
    }
}

结合在你的祖传代码里打上好 // 【auth】 标记,你就可以把 hy-uni 当作你们内部团队私有化的高阶脚手架来直接复用!

(剧透:在这个大版本之后,我们将正式支持 hy-uni template add 命令,允许你直接接管并挂载任意外部 Git 仓库,搭建你的私有定制生态!)


🚀 立即体验(极速拉起只需 3 个命令)

别再对着一堆乱糟糟的精装房一筹莫展了:

# 极速纯净版
npx hy-uni my-app --pure

创建后的常用命令

cd my-app
pnpm install

# 开发命令
pnpm dev:h5 # H5 本地开发(localhost:3000)
pnpm dev:mp # 微信小程序开发
pnpm dev:app # App 开发

# 构建命令
pnpm build:h5 # H5 生产构建
pnpm build:mp # 小程序构建

# 检查命令
pnpm lint # ESLint 检查 + 自动修复
pnpm type-check # TypeScript 类型检查


📊 跟现有方案对比

官方模板 社区全量模板 hy-uni
创建后能直接开发 ❌ 需要自己搭 ✅ 能,但要先删一堆 ✅ 开箱即用
功能选择 ❌ 无 ❌ 无 / 模板分支 ✅ 交互式按需选择
不要的功能 N/A ⚠️ 自己删(怕误删) ✅ 从代码到依赖全清理
生成代码质量 空壳 ⚠️ 可能有残留 ✅ 零残留,像手写的
模板维护成本 ⚠️ 高(N 个分支) ✅ 低(1 份模板)
极速纯净模式 --pure 1秒钟

🔗 获取地址(直达阵地)

核心源码不到 500 行,没有任何冗余包装。如果你也是代码洁癖患者,恰好懂我对极致整洁的坚持,欢迎来给我点一个宝贵的 Star!使用中发现任何 Bug,随时 Issue 见!


📌 总结

hy-uni

  • 我只想要骨架--pure 1秒钟搞定,零冗余

  • 我想要完整方案 → 交互式选择,按需组合

  • 我想要纯净但有示例 → 选 API + 示例,不选主题

  • 我想用自己的模板 → 即将支持,用我们的引擎

核心理念:你不要的功能,连一行代码都不该出现。


🚀 现在就试试


npx hy-uni my-app

让我们一起告别"删文件夹"的时代。

AI 全栈时代的工程化护栏:Vben-Nest 让 Mock 契约落地成真实后端

AI 让“会写代码的人”变多了,但也让一个老问题更显眼:写得快并不等于做得稳。

  • vibe coding 很爽,但在企业协作/长期迭代里,如果缺少统一规范与质量门禁,返工会非常昂贵
  • 前后端分离的联调常常依赖 Apifox MCP 这类工具,但契约同步、环境对齐、反复调用依然麻烦,而且还会额外消耗 token
  • 真正吸引人的方向是“全栈 + AI”:把重复劳动交给 AI,把关键决策交给工程化与契约,降低个人做全栈的门槛

这份项目模板 vben-nest 的核心思路很直接:在 vben 的工程化底座上新增 Nest 后端(apps/server),并让后端接口完全适配 vben 原本的 mock 契约。前端调用尽量不改,只替换“接口实现者”,从 mock 走向真实后端更平滑、更可演进。

1. 为什么不是“随便写接口”:全栈的关键是契约

一个判断是:随着 AI 能力增强,“前后端一个人搞”会越来越常见。原因并不神秘:很多业务难点不在 UI 或 API 的某一端,而在业务逻辑本身。

当 AI 能把样板代码写得更快时,决定交付质量的就变成了:

  • 需求是否能被拆成稳定的接口契约
  • 前后端是否能围绕同一套领域模型演进
  • 多人协作与长期维护里,边界是否足够清晰、成本是否可控

社区里也常有人吐槽 vben“很重”。但把视角从“个人 demo”切换到“企业协作”,它更像是在提前支付必要成本:规范、质量门禁、脚手架、依赖治理,以及可观测的目录结构与约定。

2. vibe coding 的边界:快要配护栏

vibe coding 适合快速验证与个人实验;一旦进入团队协作或中长期迭代,劣势会迅速放大:风格不一致、边界不清晰、可测试性差、回归成本高。AI 越强,越容易把“写得像能跑”误当成“工程上可交付”。

更理想的组合是:

  • AI 负责加速产出
  • 工程化负责约束质量与一致性

3. 这个项目做了什么:用 Nest 复刻并升级 vben mock

vben 自带的后端 mock(见 apps/backend-mock)已经非常接近真实后端:它不是浏览器里的 mock.js,而是一套独立服务实现,因此能覆盖文件上传、复杂逻辑、鉴权流程等更真实的场景。

vben-nest 在此基础上新增了 Nest 后端(见 apps/server),并坚持一个核心原则:

前端不改接口调用方式,只替换“实现者”。

路径、方法、响应结构、鉴权方式尽量保持一致,从 mock 平滑迁移到真实后端;甚至可以做到“先跑起来,再逐步替换为真实业务”。

4. 兼容策略与实现要点:对齐调用习惯,也保留演进空间

4.1 接口层:兼容 vben 的响应约定

Nest 侧做了三件事,降低前端切换成本:

  • 使用全局前缀 api,对齐 vben 的请求习惯(见 main.ts
  • 通过全局响应拦截器,统一包装为 { code, data, message, error }(见 ResponseInterceptor
  • 通过全局异常过滤器,统一输出错误结构(见 HttpExceptionFilter

这样前端的请求层可以继续使用原有的 code/data 成功码约定(例如 playground 的请求拦截器依旧按 successCode: 0 解析,见 request.ts)。

4.2 鉴权层:从"能用"到"更像生产"

很多 mock 方案只做"伪登录",但真实项目通常会有 refresh token、cookie 策略、过期处理等细节。Nest 侧采用:

这让"用模板练全栈"更贴近真实业务,而不是停留在 demo 层面。

4.3 数据层:从 mock-data 到可落库演进

为了让后端具备真实项目的演进空间,这里引入:

可以先用 seed 数据把前端跑通,再逐步把 mock-data 替换成真实表结构与业务逻辑。

4.4 工程化:把 vben 的护栏带到后端

这类模板的“核心价值”不在于多写了几个接口,而在于少搭了一套后端工程规范。

Nest 同样处于 TypeScript 生态,把后端放进 vben 的 monorepo 后,直接获得:

  • 代码格式化与 lint 约束(Prettier/ESLint/Stylelint)
  • Git Hooks 质量门禁(Lefthook + Commitlint,见 lefthook.yml
  • 统一依赖治理与脚本编排(pnpm workspace + turbo)

在 AI 参与编码的前提下,这套护栏能把“产出速度”与“可维护性”拉到一个更平衡的位置。

5. 快速开始

环境要求

  • Node.js >= 20.19
  • pnpm >= 10(推荐使用 corepack)
  • Docker Desktop(推荐,用于一键启动 PostgreSQL)

安装与运行

# 克隆项目
git clone https://github.com/MiniJude/vben-nest.git
cd vben-nest

# 安装依赖
npm i -g corepack
pnpm install

# 启动数据库 (Docker Compose)
docker compose -f apps/server/docker-compose.yml up -d

# 初始化数据库结构与种子数据
pnpm -F @vben/server run db:init

# 启动后端服务
pnpm -F @vben/server run dev
# 默认端口:3000

# 启动前端 Playground (新开终端)
pnpm dev:play

默认账号

  • vben / 123456
  • admin / 123456
  • jack / 123456

6. 目录结构速览:前端多方案 + 后端可落库

  • 前端应用:apps/web-*(多 UI 方案示例)
  • Playground:playground/(用于快速体验与联调)
  • Mock 服务:apps/backend-mock/(vben 原生 mock 服务实现)
  • Nest 后端:apps/server/(本项目新增,适配 mock 契约)

6. 全栈 + AI 的实践建议:契约先行

更推荐的协作方式是“契约先行(Contract First)”:

  • 先把接口契约稳定下来(URL、method、code/data 结构、分页约定、错误约定)
  • 再让 AI 生成 DTO、Controller、Service、Prisma Schema 的骨架
  • 依靠 lint、类型系统、提交规范,把代码质量稳定在可维护区间

前后端分离下,工具链可以帮助联调,但契约才是协作的“唯一事实来源”。

7. 适合谁使用

  • 想从 vben 上手全栈的前端同学:用熟悉的前端工程体系,把后端也纳入同一条流水线
  • 想快速搭建企业级全栈脚手架的小团队:少做重复造轮子,把精力留给业务
  • 想在真实工程约束下用 AI 提效的人:让 AI 提速,同时保留规范与可演进性

8. 项目链接

github.com/MiniJude/vb…

9. 后续演进

当前版本更偏向“理念与路径”的最小落地:先把接口契约与工程化护栏对齐,让 mock 能平滑替换为可演进的真实后端。面向生产的后端工程还需要持续补齐监控、日志、权限、安全、审计等能力,本项目也会按迭代节奏逐步完善。

Electron 无边框窗口拖拽实现

Electron 无边框窗口拖拽实现详解:从问题到完美解决方案

技术栈: Electron 40+, Vue 3.5+, TypeScript 5.9+

🎯 问题背景

在开发 Electron 无边框应用时,遇到一个经典问题:如何让用户能够拖拽移动整个窗口?

传统的 Web 应用有浏览器标题栏可以拖拽,但 Electron 的无边框窗口 (frame: false) 完全去除了系统原生的标题栏,这就需要我们自己实现拖拽功能。

挑战

  1. 右键误触发: 用户右键点击时窗口也会跟着移动
  2. 定时器泄漏: 鼠标抬起后窗口仍在跟随鼠标
  3. 事件覆盖不全: 忘记处理鼠标离开窗口等边界情况
  4. 性能问题: 频繁的位置计算导致卡顿
  5. 安全考虑: 如何在保持功能的同时确保 IPC 通信安全

本文将从零开始,实现一个 Electron 窗口拖拽解决方案。

🛠️ 技术方案概览

我们采用 渲染进程 + IPC + 主进程 的三层架构:

[Vue 组件][IPC 安全通道][Electron 主进程][窗口控制]

核心优势:

  • ✅ 精确区分鼠标左/右键
  • ✅ 完整的事件生命周期管理
  • ✅ 内存安全(无定时器泄漏)
  • ✅ 安全的 IPC 通信
  • ✅ 流畅的用户体验(60fps)

🔧 详细实现步骤

第一步:主进程窗口配置

首先确保你的 Electron 窗口正确配置为无边框模式:

// electron/main/index.ts
const win = new BrowserWindow({
  title: 'Main window',
  frame: false,           // 关键:禁用系统标题栏
  transparent: true,      // 透明窗口(可选)
  backgroundColor: '#00000000', // 完全透明
  width: 288,
  height: 364,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.mjs'),
  }
})

第二步:预加载脚本 - 安全的 IPC 桥梁

使用 contextBridge 安全地暴露 API 给渲染进程:

// electron/preload/index.ts
import { ipcRenderer, contextBridge } from 'electron'

contextBridge.exposeInMainWorld('ipcRenderer', {
  // ... 其他 IPC 方法
  
  // 暴露窗口拖拽控制方法
  windowMove(canMoving: boolean) {
    ipcRenderer.invoke('windowMove', canMoving)
  }
})

为什么这样做?

  • 避免直接暴露完整的 ipcRenderer
  • 限制可调用的方法范围
  • 符合 Electron 安全最佳实践

第三步:主进程拖拽逻辑

创建专门的拖拽工具函数:

// electron/main/utils/windowMove.ts
import { screen } from "electron";

// 全局定时器引用 - 关键!
let movingInterval: NodeJS.Timeout | null = null;

export default function windowMove(
  win: Electron.BrowserWindow | null, 
  canMoving: boolean
) {
  let winStartPosition = { x: 0, y: 0 };
  let mouseStartPosition = { x: 0, y: 0 };

  if (canMoving && win) {
    // === 启动拖拽 ===
    console.log("main start moving");
    
    // 记录起始位置
    const winPosition = win.getPosition();
    winStartPosition = { x: winPosition[0], y: winPosition[1] };
    mouseStartPosition = screen.getCursorScreenPoint();
    
    // 清理已存在的定时器 - 防止重复
    if (movingInterval) {
      clearInterval(movingInterval);
      movingInterval = null;
    }
    
    // 启动位置更新定时器 (20ms ≈ 50fps)
    movingInterval = setInterval(() => {
      const cursorPosition = screen.getCursorScreenPoint();
      
      // 相对位移算法
      const x = winStartPosition.x + cursorPosition.x - mouseStartPosition.x;
      const y = winStartPosition.y + cursorPosition.y - mouseStartPosition.y;
      
      // 更新窗口位置
      win.setResizable(false); // 拖拽时禁止调整大小
      win.setBounds({ x, y, width: 288, height: 364 }); // 使用setBounds 同时设置位置和宽高防止拖动过程窗口变大,宽高可动态获取
    }, 20);
    
  } else {
    // === 停止拖拽 ===
    console.log("main stop moving");
    
    // 清理定时器
    if (movingInterval) {
      clearInterval(movingInterval);
      movingInterval = null;
    }
    
    // 恢复窗口状态
    if (win) {
      win.setResizable(true);
    }
  }
}

关键设计点:

  1. 全局定时器: movingInterval 声明在模块级别,确保能被正确清理
  2. 相对位移算法: 基于起始位置的相对移动,避免累积误差
  3. 防重复机制: 每次启动前清理已有定时器
  4. 窗口状态管理: 拖拽时禁用调整大小,结束后恢复

第四步:渲染进程事件处理

在 Vue 组件中精确处理鼠标事件:

<!-- src/App.vue -->
<script setup lang="ts">
import Camera from './components/Camera.vue'

// 调用主进程拖拽方法
const windowMove = (canMoving: boolean): void => {
  window?.ipcRenderer?.windowMove(canMoving);
}

// 只有左键按下时才开始移动
const handleMouseDown = (e: MouseEvent) => {
  if (e.button === 0) { // 0 = 左键, 1 = 中键, 2 = 右键
    windowMove(true);
  }
}

// 鼠标抬起时停止移动(任何按键)
const handleMouseUp = () => {
  windowMove(false);
}

// 鼠标离开容器时停止移动
const handleMouseLeave = () => {
  windowMove(false);
}

// 右键菜单处理 - 关键!
const handleContextMenu = (e: MouseEvent) => {
  e.preventDefault(); // 阻止默认右键菜单
  windowMove(false);  // 确保停止拖拽
}
</script>

<template>
  <div class="app-container" 
       @mousedown="handleMouseDown" 
       @mouseleave="handleMouseLeave"
       @mouseup="handleMouseUp" 
       @contextmenu="handleContextMenu">
    <Camera />
  </div>
</template>

鼠标按键值参考:

  • e.button === 0: 左键 (Left click)
  • e.button === 1: 中键 (Middle click)
  • e.button === 2: 右键 (Right click)

第五步:主进程 IPC 处理器

注册 IPC 处理器并集成拖拽逻辑:

// electron/main/index.ts
import windowMove from './utils/windowMove'

// ... 其他代码 ...

// 注册 IPC 处理器
ipcMain.handle("windowMove", (_, canMoving) => {
  console.log('ipcMain.handle windowMove', canMoving)
  windowMove(win, canMoving)
})

🔒 安全最佳实践

1. IPC 方法限制

// 好的做法:只暴露必要方法
contextBridge.exposeInMainWorld('ipcRenderer', {
  windowMove: (canMoving) => ipcRenderer.invoke('windowMove', canMoving)
})

// 避免:暴露完整 ipcRenderer
// contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)

2. 输入验证

// 在主进程中验证输入
if (typeof canMoving !== 'boolean') {
  throw new Error('Invalid parameter');
}

3. 窗口引用安全

// 始终检查窗口是否存在
if (!win || win.isDestroyed()) {
  return;
}

🔄 替代方案对比

方案 A: CSS -webkit-app-region: drag (推荐用于简单场景)

.drag-area {
  -webkit-app-region: drag;
}

优点: 零 JavaScript,硬件加速,无 IPC 开销
缺点: 无法区分鼠标按键,会阻止所有鼠标事件

方案 B: 完整的自定义拖拽 (本文方案)

优点: 完全可控,支持复杂交互,可区分按键
缺点: 需要 IPC 通信,代码量较大

选择建议

  • 简单应用: 使用 CSS 方案
  • 复杂交互: 使用本文的自定义方案
  • 混合方案: 在非交互区域使用 CSS,在需要精确控制的区域使用自定义方案

💡 扩展功能思路

1. 拖拽区域限制

// 限制窗口不能拖出屏幕
const bounds = screen.getDisplayNearestPoint(cursorPosition).bounds;
const newX = Math.max(bounds.x, Math.min(x, bounds.x + bounds.width - windowWidth));
const newY = Math.max(bounds.y, Math.min(y, bounds.y + bounds.height - windowHeight));

2. 拖拽动画效果

// 拖拽开始时添加阴影
win.webContents.executeJavaScript(`
  document.body.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
`);

// 拖拽结束时移除
win.webContents.executeJavaScript(`
  document.body.style.boxShadow = 'none';
`);

3. 多显示器支持

// 获取所有显示器信息
const displays = screen.getAllDisplays();
// 根据当前显示器调整拖拽行为

📚 完整项目结构

electron-camera/
├── electron/
│   ├── main/
│   │   ├── utils/windowMove.ts    # 拖拽核心逻辑
│   │   └── index.ts               # 主进程入口
│   └── preload/index.ts           # IPC 安全桥梁
└── src/
    └── App.vue                    # 渲染进程事件处理

🤝 总结

通过本文的完整实现,你将获得一个:

  • 功能完整 的窗口拖拽解决方案
  • 安全可靠 的 IPC 通信架构
  • 性能优秀 的用户体验
  • 易于维护 的代码结构

这个方案已经在实际项目中经过充分测试,可以直接用于你的 Electron 应用开发。

在实现过程中发现还有一个好的库,github.com/Wargraphs/e… 有空可以试试。

如果你觉得这篇文章对你有帮助,请点赞、收藏或分享给其他开发者!

有任何问题或改进建议,欢迎在评论区讨论! 🚀

❌