← Blog

Render React vào console.log

Text.js vẽ ứng dụng React trong console DevTools bằng một lệnh gọi console.log có định dạng cho mỗi frame.

Adam

Text.js render ứng dụng React trong console của Chrome DevTools. Ngoài một ô nhập ẩn, trang không có giao diện. Mỗi frame của ứng dụng là một lệnh gọi console.log.

Console làm màn hình hiển thị

console.log nhận một chuỗi định dạng. Mỗi %c áp dụng CSS trong đối số tiếp theo cho phần văn bản phía sau nó, cho đến %c tiếp theo:

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

Các kiểu này có thể đặt màu chữ, màu nền, chữ đậm, chữ nghiêng và gạch chân, còn phông chữ của console là phông đơn cách (Menlo trên macOS). Với phông đơn cách và màu sắc có thể thay đổi theo từng đoạn ký tự, console hoạt động như một lưới ô ký tự có định dạng. Có thể vẽ đường viền, bảng, ô nhập và biểu đồ trên đó.

Một frame là một lưới ô

Bộ render lưu mỗi frame trong một CellBuffer. Ký tự nằm trong một mảng định kiểu, màu chữ và màu nền nằm trong hai mảng khác, còn các thuộc tính như chữ đậm và gạch chân nằm trong mảng thứ tư. Các mảng chỉ cấp phát một lần cho một kích thước nhất định và tái sử dụng cho mọi frame.

Để ghi một frame vào console, driver duyệt từng dòng và nhóm các ô liền kề có cùng kiểu thành từng đoạn. Mỗi đoạn trở thành một span %c, và các dòng nối với nhau bằng \n. Bảng tổng quan thử nghiệm 100×32 của chúng tôi, gồm 3.200 ô, mã hóa thành khoảng 200 span.

Một lệnh gọi cho mỗi frame

Ghi từng dòng thành một thông báo riêng không hiệu quả. Mỗi thông báo có đường viền và liên kết mã nguồn riêng trong DevTools, và DevTools thu gọn các thông báo liên tiếp giống nhau sau một bộ đếm số lần lặp, nên hai dòng giống nhau trong bảng sẽ gộp thành một. Một frame ghi dưới dạng một thông báo duy nhất sẽ giữ nguyên thành một khối.

Trước hết cần escape hai thứ trong văn bản. Ký tự % được viết thành %%, để văn bản như 100% không dùng mất một đối số định dạng. DevTools cũng biến URL thành liên kết và vẽ lại chúng bằng màu riêng, nên :// và www. có thêm một word joiner (U+2060), một ký tự không có độ rộng:

export function escapeConsoleText(text: string, allowLink = false): string {  let s = text;  if (s.includes("%")) s = s.replaceAll("%", "%%");  if (!allowLink) {    if (s.includes(LINK_SCHEME)) s = s.replaceAll(LINK_SCHEME, LINK_SCHEME_SAFE);    if (s.includes(WWW) || s.includes("WWW.")) s = s.replace(/www\./gi, (m) => `${m.slice(0, 3)}\u2060.`);  }  return s;}

Frame nhàn rỗi không ghi gì vào console

Mỗi dòng trong bộ đệm đều có giá trị băm. Nếu không có giá trị băm nào thay đổi kể từ frame trước, driver trả về trước khi tạo chuỗi và không đụng đến console. Ứng dụng đang chờ dữ liệu nhập không thực hiện lệnh gọi nào.

Khi một dòng thay đổi, driver xóa console và ghi frame mới:

const { frame, dirtyRows } = this.encode(buffer, options);if (frame === null) return SKIPPED;if (this.clearEachFrame) target.clear();target.log(frame.format, ...frame.args);

Chỉ những dòng có giá trị băm thay đổi mới mã hóa lại. Các dòng còn lại tái sử dụng span từ frame trước.

Nhập liệu

Console không nhận thao tác gõ phím, nên trang thu nhận chúng. Một <textarea> ẩn nhận thao tác nhấn phím, dán và soạn thảo qua IME rồi chuyển chúng thành sự kiện phím cho ứng dụng. Nhấp vào trang sẽ đưa tiêu điểm vào đó.

Tiếp theo

Các bản mẫu @textjs/core 0.0.x trên npm chứa mọi thứ trong một gói. Text.js 0.1 chuyển bộ render mô tả ở đây vào @textjs/core, bộ reconciler của React vào @textjs/react, và thêm App Router.