Turbolog 引擎
帧如何通过游程编码合并为一次 console.log 调用,以及 CellBuffer、Chromium 细节和基准测试。
Turbolog 将每次 React 提交转换为一次 console.log 调用;如果没有单元格变化,则不调用。
目标
- 每个发生变化的帧只调用一次。
- 空闲时零调用。 未变化的帧在构建字符串之前就返回。
- 少量样式参数。 密集的
100×32仪表盘包含 3200 个单元格,只需 149 个%cspan。
处理流水线
React ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.logCellBuffer
帧存储在用于字符、颜色和属性的扁平类型化数组中,这些数组只分配一次。
变化检测关卡
每行都会计算哈希值。如果没有变化,驱动程序会提前返回:
if (frame === null) return SKIPPED; // 0 console callsconsole.log(frame.format, ...frame.args);帧切换
Turbolog 追加帧,仅在首帧、调用 invalidate() 之后、累积 100 帧后的空闲时以及达到 300 帧时清空控制台。DevTools 会立即移除已清空的消息,但以 50 ms 的节流间隔绘制新消息,因此每帧都清空会导致控制台闪现空白。追加的帧在一次绘制中完成切换,零宽度的幕帘 span 将上一帧挡在视野之外。向上滚动可查看更早的帧。
帧速率控制在约 15 fps。更快的帧会重置 DevTools 的节流计时并被丢弃。
游程编码
样式相同的单元格合并为一个 %c span。span 可跨越换行符,CSS 字符串则通过驻留机制复用。
console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");Chromium 细节
- 字面量
%转义为%%。 - URL 和
path:line文本会插入 word joiner,防止 DevTools 重新设置其样式。 - span 仅使用 CSS 长属性。DevTools 会将每个展开后的属性复制到每一行。
- span 经过调整,使背景平铺时不留间隙。
Firefox 和 Safari
Chromium 将 %c span 的每一行布局为行内块。Firefox 和 Safari 则保持 span 的行内布局,因此会忽略垂直外边距,并将长行折行。CssConsoleDriver 通过 CSS 特性检测识别浏览器,并为这两个控制台设置 14 px 行高且不留外边距,使用 text-wrap-mode: nowrap 关闭折行,再通过 line-height: 100vh 幕帘将更早的帧推到视野之外。适配控制台尺寸时会为 Firefox 的源码链接列预留空间。Safari 将 Menlo 放在字体栈首位,因为页面无法使用用户安装的 SF Mono,而 Web Inspector 可能使用该字体绘制。向 createConsoleRoot 传入 browser 可自行选择控制台。
基准测试
packages/core/test/perf.test.ts 在密集的 100×32 仪表盘上运行,取 1000 帧的平均值(Apple M5 Pro,Bun 1.4.4):
| 帧 | 耗时 | %c span 数 |
|---|---|---|
| 全量重绘(布局、绘制、编码) | 0.163 ms | 149 |
| 单行编辑 | 0.011 ms | 149 |
| 空闲 | 0.000 ms | 0 |
状态页面会在你的浏览器中运行相同的处理流水线。在 Playground 中的演示运行期间,在 DevTools 中运行 text.stats()。