Turbolog 엔진
프레임을 런 렝스 인코딩으로 console.log 호출 하나에 담는 방식과 CellBuffer, Chromium 세부 사항, 벤치마크.
Turbolog는 각 React 커밋을 console.log 호출 하나로 바꾸고, 바뀐 셀이 없으면 호출하지 않아요.
목표
- 변경된 프레임당 호출 1번.
- 유휴 상태에서 호출 0번. 변경되지 않은 프레임은 문자열을 만들기 전에 반환해요.
- 적은 스타일 인수. 셀이 빽빽한
100×32대시보드의 3,200개 셀에%c스팬 149개를 사용해요.
파이프라인
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는 지워진 메시지를 즉시 제거하지만 새 메시지는 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.163ms | 149 |
| 한 행 수정 | 0.011ms | 149 |
| 유휴 상태 | 0.000ms | 0 |
상태 페이지는 브라우저에서 같은 파이프라인을 실행해요. Playground의 데모가 실행되는 동안 DevTools에서 text.stats()를 실행하세요.