Turbolog 엔진

프레임을 런 렝스 인코딩으로 console.log 호출 하나에 담는 방식과 CellBuffer, Chromium 세부 사항, 벤치마크.

Turbolog는 각 React 커밋을 console.log 호출 하나로 바꾸고, 바뀐 셀이 없으면 호출하지 않아요.

목표

  1. 변경된 프레임당 호출 1번.
  2. 유휴 상태에서 호출 0번. 변경되지 않은 프레임은 문자열을 만들기 전에 반환해요.
  3. 적은 스타일 인수. 셀이 빽빽한 100×32 대시보드의 3,200개 셀에 %c 스팬 149개를 사용해요.

파이프라인

React ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.log

CellBuffer

프레임은 한 번만 할당한 문자, 색상, 속성용 평면 타입 배열에 저장돼요.

변경 감지 게이트

각 행의 해시를 계산해요. 바뀐 내용이 없으면 드라이버가 일찍 반환해요:

if (frame === null) return SKIPPED; // 0 console callsconsole.log(frame.format, ...frame.args);

프레임 교체

Turbolog는 프레임을 덧붙이고, 첫 프레임, invalidate() 이후, 100개 프레임이 쌓인 뒤 유휴 상태일 때, 300개가 쌓였을 때만 콘솔을 지워요. DevTools는 지워진 메시지를 즉시 제거하지만 새 메시지는 50ms 간격으로 제한해 그리므로, 프레임마다 지우면 빈 콘솔이 깜박여요. 덧붙인 프레임은 한 번의 그리기 안에서 교체되고, 너비가 0인 커튼 스팬이 이전 프레임을 화면에서 가려요. 이전 프레임을 보려면 위로 스크롤하세요.

프레임 속도는 약 15fps로 조절돼요. 더 빠른 프레임은 DevTools의 스로틀 타이머를 재설정하고 버려져요.

런 길이 인코딩

일치하는 셀은 하나의 %c 스팬으로 합쳐져요. 스팬은 줄바꿈을 넘어 이어지고, CSS 문자열은 인터닝돼요.

console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");

Chromium 세부 사항

  • 리터럴 %는 %%로 이스케이프돼요.
  • URL과 path:line 텍스트에는 DevTools가 스타일을 다시 적용하지 않도록 워드 조이너를 넣어요.
  • 스팬은 CSS 개별 속성만 사용해요. DevTools는 펼쳐진 모든 속성을 모든 줄에 복사해요.
  • 배경이 빈틈없이 이어지도록 스팬을 조정해요.

Firefox와 Safari

Chromium은 %c 스팬의 각 줄을 인라인 블록으로 배치해요. Firefox와 Safari는 스팬을 인라인으로 유지하므로 세로 여백을 무시하고 긴 행을 줄바꿈해요. CssConsoleDriver는 CSS 기능 검사로 브라우저를 감지하고, 이 두 콘솔에서는 여백 없이 줄 높이를 14px로 설정하고, text-wrap-mode: nowrap으로 줄바꿈을 끄고, line-height: 100vh 커튼으로 이전 프레임을 화면 밖으로 밀어내요. 콘솔에 맞춰 크기를 조정할 때 Firefox의 소스 링크 열에 필요한 공간을 남겨 둬요. Safari 페이지에서는 Web Inspector가 사용할 수 있는 사용자 설치 SF Mono를 쓸 수 없으므로, Safari는 글꼴 목록에서 Menlo를 맨 앞에 둬요. 콘솔을 직접 선택하려면 createConsoleRoot에 browser를 전달하세요.

벤치마크

셀이 빽빽한 100×32 대시보드에서 packages/core/test/perf.test.ts로 측정한 1,000개 프레임의 평균(Apple M5 Pro, Bun 1.4.4):

프레임시간%c 스팬
전체 다시 그리기(레이아웃, 그리기, 인코딩)0.163ms149
한 행 수정0.011ms149
유휴 상태0.000ms0

상태 페이지는 브라우저에서 같은 파이프라인을 실행해요. Playground의 데모가 실행되는 동안 DevTools에서 text.stats()를 실행하세요.

GitHub에서 이 페이지 편집