Hiển thị Diff: Cách xử lý các thay đổi mã nguồn khổng lồ trên trình duyệt

Công nghệ29 tháng 5, 2026·10 phút đọc

Bài viết phân tích các thách thức kỹ thuật khi hiển thị file diff lớn trên trình duyệt và giới thiệu giải pháp CodeView với kỹ thuật ảo hóa "Inverse Sticky" nhằm tối ưu hóa hiệu năng, bộ nhớ và trải nghiệm người dùng.

Hiển thị Diff: Cách xử lý các thay đổi mã nguồn khổng lồ trên trình duyệt

Bạn mở một Pull Request (PR) với kỳ vọng sẽ hiểu rõ những gì đã thay đổi. Với những thay đổi vừa và nhỏ, mọi thứ đều hoạt động trơn tru. Code dễ đọc, file nằm ở đó, bạn cuộn trang, thêm bình luận và quy trình diễn ra suôn sẻ.

Nhưng khi bạn mở một PR lớn hơn—có thể là do một tác nhân AI tạo ra mã nguồn, kiểm thử, dữ liệu mẫu và snapshot, hoặc đơn giản là nhánh code này chạm vào quá nhiều file—bề mặt xem xét (review surface) bắt đầu xuống cấp. Công cụ có thể chỉ hiển thị một file tại một thời điểm, yêu cầu tải từng file riêng lẻ, hoặc khiến thao tác cuộn trở nên nặng nề.

Đây là những đánh đổi khó khăn đối với các vấn đề kỹ thuật phức tạp, nhưng chúng có cái giá: người review cảm thấy giới hạn của công cụ, và các đội sản phẩm phải xây dựng các giải pháp tạm thời để vượt qua giới hạn đó.

Hiển thị diff rất quan trọng, nhưng đối với hầu hết các công cụ, đó không phải là sản phẩm cốt lõi. Sản phẩm thực sự là những gì diễn ra xung quanh code: quy trình review, tự động hóa, đầu ra từ AI, kết quả CI và sự cộng tác. Việc review code nên hỗ trợ công việc đó, chứ không phải là thứ mà mọi đội nhóm phải xây dựng từ con số không.

Đó là lý do khoảng 6 tháng trước, chúng tôi đã phát hành Diffs. Mục tiêu là làm cho phần hiển thị code và diff hoạt động trơn tru, để các đội nhóm có thể dành thời gian cho sản phẩm xung quanh nó.

Ban đầu, chúng tôi chỉ ra mắt với các thành phần cơ bản: File và FileDiff. Chúng tôi nhanh chóng nhận được phản hồi về vấn đề hiệu suất, nên đã theo đuổi một bộ ảo hóa (virtualizer) đơn giản để tránh hiển thị code khi nó nằm ngoài vùng nhìn và một API để chuyển tô màu cú pháp (syntax highlighting) sang các luồng worker (worker threads).

Bộ ảo hóa đơn giản đã giúp ích, nhưng nó chỉ là giải pháp tạm thời. Vẫn còn độ phức tạp O(n×m) lớn, sử dụng nhiều bộ nhớ và hiện tượng "blanking" (khoảng trắng) khi ảo hóa. Thiếu một thành phần cấp cao hơn có thể quản lý toàn bộ bề mặt review và xử lý các vấn đề khó khăn liên quan đến quy mô.

Lớp còn thiếu đó đã trở thành CodeView: một thành phần ưu tiên ảo hóa để xem xét code và diff. Chúng tôi xây dựng nó xung quanh một mục tiêu có vẻ bất khả thi:

Bạn nên có thể hiển thị bất kỳ diff nào.

Tất nhiên là không phải theo nghĩa đen. Có những giới hạn vật lý của trình duyệt, khả năng tính toán và bộ nhớ. Nhưng về mặt thực tế, tôi nghĩ chúng tôi đã đến khá gần mục tiêu đó.

Diff trông đơn giản cho đến khi nó không còn vậy

Bề mặt, việc hiển thị diff trong trình duyệt có vẻ không khó. Đó chỉ là văn bản thôi, đúng không? Trình duyệt được xây dựng để lấy HTML thô và biến nó thành thứ bạn có thể nhìn thấy và tương tác.

Tuy nhiên, một bề mặt review tốt cần nhiều hơn là văn bản. Nó cần tô màu cú pháp, số dòng, chú thích, bình luận, chủ đề (theming), bố cục chia đôi hoặc hợp nhất, chế độ ngắt dòng và đủ tùy chỉnh để phù hợp với sản phẩm của người khác. Mỗi tính năng đều thêm chi phí và độ phức tạp. Tô màu cú pháp làm tăng thời gian xử lý và phình to DOM. Bình luận liên quan đến độ phức tạp bố cục bổ sung mà chúng ta không thể kiểm soát hoàn toàn.

Với CodeView, chúng tôi lấy độ phức tạp trên mỗi file đó và mở rộng quy mô; công việc rẻ tiền cho một diff duy nhất giờ đây có chi phí đáng kể trên một quy trình review lớn. Chúng tôi có thể chia nhỏ các vấn đề thành ba danh mục:

  • Rendering (Hiển thị): Độ phức tạp của DOM tăng nhanh, và trình duyệt có thể bị quá tải khi cuộn hoặc tương tác với trang.
  • Processing (Xử lý): Mọi thao tác trên file hoặc diff được nhân lên, khiến công việc nhanh khi đứng một mình trở nên đắt đỏ khi lặp lại hàng nghìn lần.
  • Memory (Bộ nhớ): Các file và diff lớn được chuyển đổi thành cấu trúc dữ liệu hiển thị, có thể đẩy lên giới hạn bộ nhớ của trình duyệt.

Kỹ thuật Sticky nghịch đảo (Inverse Sticky Technique)

Ảo hóa (Virtualization), hay windowing, là cách giải quyết vấn đề hiển thị. Ý tưởng cơ bản là chỉ hiển thị phần nội dung gần khung nhìn (viewport). Khi bạn cuộn, bộ ảo hóa hiển thị nội dung mới đi vào vùng nhìn và loại bỏ nội dung đã đi ra ngoài.

Giữ DOM nhỏ mang lại nhiều lợi ích: ít bộ nhớ hơn, ít công việc bố cục hơn, ít công việc vẽ (paint) hơn. Tuy nhiên, đánh đổi là bộ ảo hóa phải ước tính hoặc đo lường chiều cao của mọi thứ.

Một vấn đề thêm vào độ phức tạp này là trình duyệt thường quản lý cuộn compositing riêng biệt với việc thực thi JavaScript. Điều này có nghĩa là JavaScript có thể bị tụt hậu sau các cập nhật cuộn. Điều này thường thấy nhất khi sử dụng thanh cuộn để thực hiện các bước nhảy lớn hoặc cuộn quá nhanh—bộ ảo hóa không theo kịp và bạn sẽ cuộn vào các vùng trắng trước khi JavaScript kịp hiển thị nội dung cập nhật.

Đối với CodeView, nhiều đánh đổi ảo hóa đó là không thể chấp nhận được. Việc cuộn gốc của trình duyệt rất quan trọng. Các môi trường dựa trên WebKit cần cảm giác tốt vì Tauri là mục tiêu phổ biến cho các công cụ dành cho nhà phát triển. Và việc xuất hiện khoảng trắng (blanking) là không thể chấp nhận được.

Sau một số thử nghiệm, chúng tôi đã tìm ra một phương pháp lai có thể giữ cuộn gốc, tách biệt vị trí khỏi các cập nhật requestAnimationFrame và làm cho việc xuất hiện khoảng trắng trở nên bất khả thi về mặt thực tế. Chúng tôi gọi kỹ thuật này là Inverse Sticky Technique (Kỹ thuật Sticky nghịch đảo).

Thông thường, position: sticky được dùng để giữ các tiêu đề phần ở trên cùng khi bạn cuộn xuống. Với CodeView, chúng tôi đảo ngược hành vi đó: thay vì ghim phần trên của nội dung hiển thị vào phần trên của khung nhìn, cạnh dưới của vùng hiển thị sẽ dính vào cạnh dưới của khung nhìn khi bạn cuộn qua nó. Khi bạn cuộn ngược lên, cạnh trên sẽ dính vào cạnh trên của khung nhìn.

Điều này mang lại cho chúng tôi cuộn gốc trong khi khung nhìn nằm trong phạm vi hiển thị. Nếu JavaScript bị tụt hậu, vùng hiển thị sẽ dính vào một cạnh thay vì cuộn đi và để lộ khoảng trắng.

Tối ưu hóa bộ nhớ

Với việc ảo hóa đã được giải quyết, vấn đề tiếp theo là tính toán bố cục và kích thước của vùng cuộn. Một bộ ảo hóa hoạt động tốt nhất khi các ước tính của nó gần với thực tế.

Các trường hợp cực đoan, chẳng hạn như diff giữa Linux v6 và v7, có thể có nghĩa là hơn 700 MB nội dung bản vá để phân tích và hiển thị. Một vấn đề tinh tế là các chuỗi đã phân tích có thể giữ lại nhiều bộ nhớ hơn bạn mong đợi. Tùy thuộc vào cách động cơ JavaScript đại diện cho chuỗi con, một chuỗi nhỏ vẫn có thể tham chiếu đến chuỗi lớn hơn nhiều mà nó xuất phát từ đó. Điều đó có nghĩa là bạn có thể phân tích một bản vá khổng lồ, chỉ giữ lại các dòng bạn cần, nhưng vẫn vô tình giữ lại chuỗi đầu vào khổng lồ ban đầu.

Trong trường hợp đó, sao chép chuỗi thực sự có thể tiết kiệm bộ nhớ. Bằng cách buộc nội dung dòng đã phân tích phải tách rời khỏi đầu vào bản vá ban đầu, Diffs có thể giữ dữ liệu cần thiết mà không giữ nguyên chuỗi nguồn khổng lồ đó. Việc này đã giúp giảm mức sử dụng bộ nhớ cho diff Linux từ 2,4 GB xuống khoảng 1,15 GB và thời gian phân tích giảm khoảng 80%.

Ảo hóa giữ cho DOM được gắn kết nhỏ gọn, nhưng nó cũng có thể tạo ra nhiều sự thay đổi phần tử DOM. Chúng tôi đã thêm một nhóm (pool) cho các vùng chứa đó. Thay vì vứt bỏ toàn bộ bao bọc, CodeView có thể dọn dẹp DOM cụ thể của mục và tái sử dụng vỏ bọc cho file hoặc diff tiếp theo. Điều đó làm giảm sự phân bổ bộ nhớ và tránh việc xây dựng lại cùng một cấu trúc bao bọc nhiều lần.

Tô màu cú pháp trì hoãn

Đây là một tính năng chúng tôi đã có trong Diffs một thời gian, nhưng nó vẫn là một phần quan trọng để làm cho các quy trình review lớn cảm thấy mượt mà. Tô màu cú pháp là một trong những tác vụ xử lý tốn kém nhất. Chúng tôi sử dụng Shiki vì nó đủ nhanh cho hầu hết các trường hợp.

Điều quan trọng là việc tô màu bị trì hoãn. Các file và diff có thể hiển thị trước dưới dạng văn bản thuần túy, sau đó yêu cầu đầu ra đã tô màu từ nhóm worker. Mỗi worker sở hữu bộ tô màu Shiki của riêng mình, giữ công việc tốn kém ngoài luồng chính trong khi vẫn cho phép nhiều công việc tô màu tiến triển đồng thời.

Mục tiêu là để tô màu cải thiện bề mặt review, không chặn nó. Code có thể đọc được ngay lập tức và tô màu có thể nâng cao trải nghiệm một cách tiến bộ.

Kết luận

Nếu bạn đã đọc đến đây: cảm ơn bạn rất nhiều. Đây là một dự án lớn với rất nhiều công việc phức tạp đằng sau nó. Tôi tự hào về những gì chúng tôi đã thực hiện được trong giới hạn của một trình duyệt.

Tương lai của Diffs bao gồm các tính năng như chỉnh sửa nhẹ, diff ngữ nghĩa và có thể chuyển một số công việc này lên máy chủ. Trong meantime, mục tiêu vẫn là: Bạn nên có thể hiển thị bất kỳ diff nào.

Lưu ý về Safari: Nhiều phần của CodeView được xây dựng dựa trên các nguyên thủy trình duyệt hoạt động nhất quán trên Chrome và Firefox. Trên WebKit, trải nghiệm đó không phải lúc nào cũng như vậy. Giữa hiệu suất kém và khả năng quan sát hạn chế, nhiều thành công cảm giác như tiến một bước rồi lùi nửa bước. Chúng tôi hy vọng Apple và đội ngũ WebKit sẽ cải thiện các vấn đề về hiệu suất sticky, giới hạn 60Hz của requestAnimationFrame và các công cụ dành cho nhà phát triển để giúp việc xây dựng các công cụ web phức tạp trở nên dễ dàng hơn.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗