阅读视图

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

OpenClaw 跟病毒的区别是什么?

节日期间在家办公,我坐在书房的电脑前,盯着满屏飘红的终端😖

webpack_error_terminal_style_match.png

我没有中勒索病毒,也没有被黑客攻击。我只是在之前,极其手欠地给跑在后台的 OpenClaw 下达了一句简单的语音指令:帮我把这个老项目里的无用 npm 依赖清理一下,顺便跑通本地编译。

openclaw_feishu_chat_conversation.png

然后我就去客厅看电视了。

等我两个小时后回来,发现风扇狂转。打开终端一看,这玩意儿不仅把我的 package-lock.json 给删了,还因为有个老旧的 Sass 模块死活装不上,它自己去网上搜了个不知道谁写的 Python 脚本跑了一遍,顺手把我的全局 Node 环境降级到了两年前的版本,最后还在根目录下给我留了几十个不知名的临时编译文件🤬🤬。

看着这片惨状,我脑子里突然冒出一个极其荒诞的问题: 一个拥有系统最高执行权限的 OpenClaw,跟一个木马病毒的区别到底是什么?

如果仔细推敲,你会发现这两者的行为轨迹惊人地相似,甚至可以说,前者带来的工程灾难往往更具欺骗性。


在搞破坏?

以前我们在电脑上跑个脚本,报错了就停在那,等你来排查,过程相对可控的。

但现在的 OpenClaw 是个拥有极高自主性的 Agent。它最大的卖点是遇到问题会自动尝试解决。这在写写单纯的文本时是个优点,但在复杂的现代前端工程里,这就是个彻头彻尾的灾难🤔。

当一个病毒遇到权限阻断时,它会疯狂尝试提权、扫描端口、注入进程。 那 OpenClaw 遇到前端编译报错时会干嘛?

它会像一个极其鲁莽的瞎子:

  • 它发现 pnpm install 报错了,它不会去思考是不是内网镜像源挂了,而是自作主张把它换成 npm,瞬间摧毁你精心维护的 Monorepo 幽灵依赖机制(symlink)。
  • 它发现有个类型找不到,它不会去查 .d.ts 声明,而是极其粗暴地去改你 node_modules 里的源码,或者给你全剧加上 @ts-ignore
  • 如果遇到文件死锁,它甚至敢在终端里直接替你敲下 rm -rf

病毒搞破坏是为了勒索你,而 OpenClaw 把你的系统搞崩溃,仅仅是因为它想完成你那句帮我跑通编译。

后台静默执行

做了 9 年研发,我看过无数次因为一行配置写错导致的线上 P0 级事故。所以越是资深的工程师,越在乎执行边界。

我们为什么需要 Git?为什么需要 Code Review?为什么 CI/CD 要分发不同的环境权限? 因为我们要清楚地知道,谁在什么时候,动了哪行代码,引发了什么后果。

但 OpenClaw 打破了这个铁律。它是一个跑在你电脑或者服务器上的巨大黑盒。

当你让它接管你的工作流时,你根本不知道它为了完成目标,在后台下载了多少个包含潜在风险的三方库。你也不知道它在调用工具链的时候,有没有把你的环境变量、甚至包含数据库密码的 .env 文件当成上下文,明文发送给了大模型的 API 服务器。

传统病毒窃取你的隐私,至少还会偷偷摸摸加个壳。而 OpenClaw 是你亲手把服务器的 Root 权限双手奉上🙌。


一段真实的案例

为了让大家更直观地感受这种病毒式操作。我复盘了一下昨天它为了修复一个构建错误,所做出的决策链路。

这是一个典型的高级前端遇到问题时的处理逻辑:

1. 发现 Webpack 构建报 chunk error 错误

screenshot-20260407-151125.png

2. 检查报错日志,发现是内存溢出 (OOM)

screenshot-20260407-151038.png

3.修改启动脚本,增加 Node 内存上限,完事🤷‍♂️

export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

而 OpenClaw 在面对同一个报错时,它的终端执行记录:

# 1. 发现构建报错
npm run build 

# 2. 它看不懂 OOM,以为是依赖问题,执行了极其致命的操作
rm -rf node_modules
rm package-lock.json
npm install --legacy-peer-deps

# 3. 依赖彻底乱套,报了更多编译错误。它决定更换打包工具
npm install -g vite
# 强行在原本的 Webpack 项目里塞入一段残缺的 vite.config.js

# 4. 依然跑不通,它甚至去改了操作系统的环境😖😖😖
sudo chmod -R 777 ./ 

看到最后那个 chmod 777 的时候,我后背都在冒冷汗😢。 它为了解决一个权限微小的编译告警,直接把你整个项目的安全底裤都给扒了。这不是病毒是什么?


我想泼一盆冷水🫡

现在全网都在狂欢,各大社区都在比拼谁的 Agent 更聪明,谁能让电脑完全自己写代码、自己部署。

很多人沉迷于这种看着终端自己跳动代码的爽感中,觉得这就是未来。

但我不得不泼一盆冷水。在工程领域,不可控的自动化,比纯手工还要危险一万倍😒。

不管是 OpenClaw 还是未来更牛的智能体,只要它不具备真实世界的工程常识和后果承担能力(到目前为止都不可能为你背锅!!!),它就是一个披着 AI 外衣的高危病毒。

咱们在敲下回车之前,脑子里想的是:这会影响线上吗?会引发竞态问题吗?接手的同事能看懂吗? 而 Agent 脑子里只有计算概率:根据统计学,下达这个指令,满足用户当前 prompt 的概率哪个最大?它不在乎你的硬盘会不会被占满,不在乎你的生产环境会不会被污染。


所以,咱们这些在一线干活的兄弟们,清醒一点。

工具终究是工具,它可以帮你查 API,可以帮你写正则,可以帮你生成模版代码。但千万别把系统的控制权和架构的决策权,交给一个随时可能暴雷的 AI Agent

把危险关在沙盒里,让执行处于监控下。如果你做不到这一点,那你电脑里跑着的那个每天对你嘘寒问暖的 OpenClaw,真的比熊猫烧香还要可怕的。🤔

对此大家怎么看?

Suggestion.gif

一周狂揽40K+ Star⭐ 的 Pretext 到底有多变态?

这周的前端圈,可以说是被一个叫 Pretext 的项目彻底刷屏了。

短短几天,GitHub 狂揽 41K+ Stars ⭐⭐⭐

很多刚入行的小伙伴看完官方那个极简的 Readme 可能一头雾水:不就是一个算文本长宽高的 JS 库吗?为啥能火成这样?CSS 的 word-wrapflex 难道不够用吗?

v2_741bc7c1a79445528b75ddc1980d6ccd@46958_img_gif.gif

v2_c43f8b79c3f6400d9a995d5f0adc869d@46958_img_gif.gif

v2_715e9ba3c8aa4ea7b8e1bc5b41f87ead@46958_img_gif.gif

但如果是被各种复杂表格、虚拟列表、Canvas 渲染折磨过的老兵,看到 Pretext 的那一刻,绝对会有一种激动的。

因为这个库,极其优雅地干掉了前端性能优化里最恶心、最顽固的问题——强制同步布局(Forced Synchronous Layout)导致的重排(Reflow)。

今天,咱们不念官方文档,结合我这几年的填坑血泪史,聊聊这个 41K Star 的怪物到底解决了什么世界级痛点,以及我们在真实的业务里该怎么用它。


那些年用过的 getBoundingClientRect

前端开发有个大难题:一串动态文本渲染出来到底有多高?

设想一个极度真实的业务场景: 你在做一个拥有十万条数据的 虚拟滚动列表(Virtual List)。为了让列表丝滑,你只能渲染视口内的那 20 条数据。 但问题来了,每条数据里的用户评论长度是不固定的。有的人发了一句 哈哈😁,有的人发了 800 字的写字楼小作文。 在渲染之前,你必须提前知道每一行的高度,才能计算出整个虚拟列表的滚动条位置和绝对定位的 top 值。

在 Pretext 出现之前,我们是怎么做的? 用的往往是最原始、极其粗暴的 离屏 DOM 测量法(Offscreen Measurement)

// 极其恶心的传统测量法:DOM 测算
function measureTextHeightOldWay(text, width, fontSize) {
  // 1. 创建一个隐藏的 div
  const hiddenDiv = document.createElement('div');
  hiddenDiv.style.visibility = 'hidden';
  hiddenDiv.style.position = 'absolute';
  hiddenDiv.style.width = `${width}px`;
  hiddenDiv.style.fontSize = `${fontSize}px`;
  hiddenDiv.innerText = text;

  // 2. 强行塞入 DOM 树
  document.body.appendChild(hiddenDiv);

  // 3. 读取高度(灾难的开始!!!)
  const height = hiddenDiv.offsetHeight; // 或者 getBoundingClientRect()

  // 4. 销毁 DOM
  document.body.removeChild(hiddenDiv);

  return height;
}

代码看着没毛病? 但如果在初始化时,你在一个循环里把这段代码跑了 1000 次,你的页面会当场卡死白屏!

为什么?因为浏览器底层是一个极度慵懒的系统。你操作 DOM 节点,它通常会先攒着,等这一帧结束再一次性绘制。 但当你调用了 offsetHeight 或者 getBoundingClientRect 时,浏览器为了给你一个最精确的值,会被迫打断所有的优化,立刻在主线程里重新计算整个页面的布局(Reflow)。

你循环调用 1000 次,浏览器就被迫重排 1000 次。这种 布局抖动(Layout Thrashing) 是前端性能的头号杀手。


Pretext 的降维打击 - 不碰 DOM,纯数学演算

而 Pretext 的核心卖点,就写在它的第一句介绍里:纯 JavaScript/TypeScript 库,避免了对 DOM 进行测量。

image.png

它完全抛弃了把元素塞进 DOM 里量一下的蠢办法。 你要算这段文字占据多少像素?好,你告诉我字体、字号、容器宽度,我直接在 JS 内存里,通过底层的文本排版算法,硬生生给你出来!

咱们直接上代码,看看接入 Pretext 之后,世界变得有多清爽:

// 使用 Pretext
import { measureText } from 'pretext';

function measureTextHeightNewWay(text, containerWidth) {
  // 没有任何 DOM 操作!直接传入参数计算
  const metrics = measureText(text, {
    fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
    fontSize: 14,
    lineHeight: 1.5,
    maxWidth: containerWidth,
    // 甚至支持复杂的换行策略
    wordBreak: 'break-word', 
  });

  // 直接拿到精准的宽高和行数!
  return metrics.height; 
}

对比一下,这带来了什么工程级别的质变?

快到离谱: 因为不触碰任何 DOM API,不会引起一丝一毫的浏览器重排。同样的测量 1000 条数据,用传统 DOM 法可能需要 300ms(掉帧卡顿),用 Pretext 只需要 2ms。

解锁 Web Worker 潜能: 以前因为要操作 DOM,测量文本的脏活必须在浏览器的主线程(UI 线程)干,极容易阻塞页面。现在它是纯 JS 计算了,你完全可以把这十万条文本高度的计算逻辑,扔到 Web Worker 里去并行跑!主线程依然丝滑如初。

跨平台降维打击: 因为纯 JS/TS,它不仅能在浏览器 DOM 里跑,它还能在 Canvas 游戏引擎里跑,在 Node.js 服务端渲染(SSR)里跑,甚至未来能在 React Native 里跑。

Pretext vs getBoundingClientRect 性能对比

image.png

更多好玩的demo 👉:https://chenglou.me/pretext/

image.png

看似比较简单,实则硬核的技术深水区

其实纯 JS 测算文本这个想法,很多前端老手都想过。为什么直到 2026 年,才被 Pretext 彻底做成了一个 40K+ Star 的杀手级项目?

因为文本排版(Text Layout)是一个深不见底的黑洞。

你以为算个宽度就是 字符数 × 字体宽度? 太天真了。你需要考虑英文单词的断词(换行不能把单词截断)、需要考虑阿拉伯语的从右到左(RTL)、需要考虑中文的标点符号避头尾规则、更别提那些五花八门的 Emoji(有的 Emoji 占好几个字节,但在屏幕上只是一个字符)。

更变态的是,不同浏览器(Chrome / Safari)底层的字体引擎(HarfBuzz 等)渲染规则都有细微差异。

Pretext 的作者 chenglou(做过 React Core,写过 ReasonML 的真大神👍👍👍)用了一种极其聪明且符合现在 AI 时代的方法:将浏览器自身的字体引擎作为基准进行迭代对齐。

image.png

它没有去傻傻地重写一套从零开始的渲染引擎,而是找到了一套能与主流浏览器高度拟合的纯数学计算逻辑。精度极高,且极其轻量。

这不是在造轮子,这是在用极客思维给现有的前端标准打补丁。


那么,哪些场景该果断接入 Pretext?

虽然我把它吹爆了,但作为一个老油条,我必须负责任地告诉你:不要脑子一热,把项目里所有的普通 CSS 排版都换成它。 CSS 引擎依旧是渲染标准流最稳定、最简单的方案。

Pretext 是属于极端场景。 遇到以下三种情况,直接掏出它:

复杂数据看板 / 大规模动态虚拟列表: 前面提到的,需要提前精确知道变长文本高度,来进行复杂绝对定位 计算的场景。

Canvas / WebGL 富文本渲染: 用过 Canvas 的人都知道,Canvas 里的 fillText 极其原始,根本不支持自动换行。以前我们在 Canvas 里画多行文本简直是噩梦,现在可以直接用 Pretext 算好每一行的位置,然后精确绘制。

基于 Node.js 的海报/PDF 自动生成系统: 服务端没有 DOM 环境,以前为了算一下文本会不会超出海报边界,还得专门在服务端起一个无头浏览器(Puppeteer),贼耗服务器资源。现在直接 Node.js 引入 Pretext 纯端计算,一台 2 核机器能顶过去 8 核的并发量。


这才是前端该有的样子🤔

这两年,前端圈充满了大模型、AI 生成代码的焦虑,似乎一切不加个 AI 前缀就不够前沿。

但看到 Pretext 这种纯粹为了解决计算机图形学底层痛点、一行一行扣性能、追求极致优雅的开源项目,短短几天收获 40K+ Star,我心里其实是挺欣慰的。

它证明了一件事:在花里胡哨的概念之外,这个世界上永远有那些扎根在工程最深处、被真实痛点折磨的开发者。

真正高级的前端工程能力,不是你接了多少个最新的大模型 API,而是当系统出现肉眼可见的卡顿时,精准地指出那句隐藏在万行代码里的 offsetHeight,然后用纯粹的数学与算法,把页面性能拉升两个数量级。

周末了,别只顾着看个热闹,去把 Pretext 拉下来,在本地建个 Canvas 或者虚拟列表的 Demo 跑一跑。

那种看着耗时从 300 毫秒断崖式下跌到 2 毫秒的爽感,才是写代码真正的乐趣😁。

前端资质越高,越来越不敢随便升级框架?

上个星期五下午,临近下班,组里一个刚入职不久、技术热情极高的小伙子,给我提了个极具分量的 PR。

他跑到我工位旁,眼里闪着光:老大,我把咱们那个核心中后台项目的 React 从 17 直接升到 19 了,顺便把 Webpack 换成了 Rsbuildrelease note 说性能提升了将近 40%,我本地跑了一下,秒开!

看着他求表扬的神情,我的心却瞬间沉到了谷底。

我点开 package.json 的 diff,好家伙,红绿相间的变动多达七十多处。除了 React 自身的跨代大版本,连带着状态管理、路由、甚至底下好几个用来处理复杂 Excel 导出的老旧插件,全被强行 npm update 到了最新版。

65f2e014-3840-4833-8131-25faeed66fb9.png

我深吸了一口气,默默把他的 PR 关了,并告诉他:本地跑通不算通。这个合并如果今天发到线上,明晚咱们整个组大概率都要在 P0 故障复盘会上做检讨。😖

很多年轻前端可能觉得我太保守,甚至有点老顽固。 技术社区里天天都在吹 Vue 3.x 又出了什么革命性宏,React 19 的 Server Components 有多颠覆,Vite 又把构建速度压缩了多少毫秒等等。

在他们眼里,升级框架就像给手机系统点一下更新那么简单,不仅能提效,写进简历里还能多一句---主导项目底层技术栈升级。

但在前端领域摸爬滚打了很多年、背过无数次线上事故的锅之后,我慢慢明白了一个极其骨感的现实告诉你:前端资质越高,越不敢随便升级框架。


升级其实没那么简单

年轻时总有一种错觉,以为升级框架就是改一下 package.json 里的数字,然后顺着终端里的 warning 把废弃的 API 替换掉就完事了。

但真实的业务工程,是一个由无数个三方库、内部包、魔改组件和历史妥协交织而成的巨大复杂度。

就拿上面那个小伙子强升 React 19 来说。他只看到了 React 19 把 forwardRef 废弃了,直接把 ref 当 prop 传就行,代码看起来确实优雅了。

但他没看到的是,咱们项目里深埋着一个 4 年前离职前辈写的、极其复杂的虚拟滚动表格组件。当年为了在老版本里拿到底层 DOM,前辈用了极其 Hack 的方式去代理 ref

// 离职前辈留下的祖传代码
// 强行拦截并劫持了内部的 ref 实例,用来做复杂的聚焦与按键接管
const ComplexTable = React.forwardRef((props, ref) => {
  const internalRef = useRef();
  
  useImperativeHandle(ref, () => ({
    forceScroll: (y) => {
      // 依赖了早期版本某 UI 库极其脆弱的内部结构
      internalRef.current.querySelector('.ant-table-body').scrollTop = y;
    },
    // ... 其他 20 个不知道哪里会调用的这些方法
  }));

  return <Table ref={internalRef} {...props} />;
});

当你兴冲冲地把大版本一升,底层的渲染时机变了,或者旧版 UI 库的 DOM 结构因为不兼容而错位了。本地跑的时候,页面确实渲染出来了。

但等到下周一,财务部门的同事在处理每个月一万条数据的工资单,习惯性地按下回车键想要让表格自动滚动到下一行时——整个页面直接白屏崩溃😢。

这种深水区的依赖断裂,TypeScript 查不出来,E2E 测试如果没有覆盖到这一步也抓不到。最后买单的,就是签字同意合入代码的技术负责人。

牵一发而动全身。在没有 100% 自动化测试覆盖率的团队里,升级底层框架不叫重构,那叫排雷🤷‍♂️。


技术自嗨?

很多程序员在谈论框架升级时,往往会陷入一种技术自嗨的盲区。

你看,新的打包工具让首屏渲染快了 0.5 秒!

确实很棒。但然后呢?

站在业务视角的逻辑是极其冰冷的:如果一个系统目前运行稳定,没有遇到致命的性能瓶颈,也没有阻碍新需求的迭代,那么动它的底层,就是典型的 ROI(投资回报率)倒挂。

你花了两周时间解决各种依赖冲突,把 Vue 2 强行拔高到了 Vue 3,把 Options API 费劲巴拉地重构成了 Composition API。

如果升级成功了呢, 业务侧毫无感知,产品经理不会多给你发一毛钱奖金,老板甚至觉得你这两周业务产出为零😖。

升级失败了(或者带入了暗病):阻断了核心业务流程,造成客户流失,你就是那个没事找事、搞出 P0 事故的千古罪人。

这不是叫你摆烂,而是工程学里一条极其重要的铁律:If it ain't broke, don't fix it.(只要没坏,就别瞎修。)

我们写代码是为了解决业务问题,而不是为了满足自己对时髦技术的热爱。把屎山代码翻新成现代化技术栈,它依旧可能是一座逻辑混乱的屎山,只不过现在是一座编译速度更快的屎山罢了😒。


什么时候才该升?

那难道我们就永远守着老旧的版本,在历史包袱里等死吗? 当然不是。

高级前端和初级前端的区别就在于:初级前端为了新而升级,高级前端为了解决痛点而升级。

遇到以下三种情况,哪怕风险再大,我也一定带着团队往上升:

如果触及了不可逾越的性能天花板。 比如旧版框架的 Virtual DOM 算法在十万级数据渲染时已经彻底锁死主线程,而新版的并发特性或细粒度更新能从底层解决这个问题。

安全漏洞与 LTS(长期支持)结束。 底层依赖被扫出高危漏洞,且官方不再为老版本提供补丁,如果不升,过不了公司的内部安全红线。

生态彻底断裂。 现有的技术栈已经古老到找不到能兼容的周边库了,新招来的员工看这代码像看甲骨文,维护成本已经远超升级成本。

而且,真正老道的升级,绝不是开个新分支一把梭。 那是细致入微的依赖盘点、是灰度发布、是双栈运行、是哪怕天塌下来也能在 5 分钟内切回老代码的降级预案。


前几天我在社区看到一句话,深以为然:每一个看似极其保守的技术决策背后,都站着一个曾经被线上 Bug 毒打得死去活来的灵魂😁。

所以,当你的组长拒绝你那份华丽的底层升级改造方案时,别急着在心里骂他老土。他不是不懂新技术,他只是比你更懂那条在深夜里突然响起的线上报警短信,有多么让人绝望😒。

对于一线开发者来说,关注前沿技术、保持对框架底层演进的好奇心,绝对是好事。它能保持你的敏锐度,让你在写新项目时拥有更好的技术选型视野。

但在一个沉淀了无数业务逻辑的历史工程面前,请保持谨慎。

真正的技术大佬,不是那个天天在项目里倒腾最新框架的极客。而是那个哪怕手握一大把老旧框架,也能稳稳当当把复杂的业务需求切得明明白白,让系统稳如老狗的架构师。

对此大家怎么看?

下班啦下班啦下班啦下班啦下班啦下班啦1,下班啦下班啦下班啦下班啦.gif

❌