Hỗ trợ CSS trong JS trong Công cụ cho nhà phát triển

Alex Rudenko
Alex Rudenko

Bài viết này nói về tính năng hỗ trợ CSS-in-JS trong Công cụ cho nhà phát triển kể từ Chrome 85. Nhìn chung, chúng tôi muốn nói đến CSS-in-JS và sự khác biệt giữa CSS-in-JS với CSS thông thường mà Công cụ cho nhà phát triển đã hỗ trợ từ lâu.

CSS-in-JS là gì?

Định nghĩa về CSS-in-JS khá mơ hồ. Nói chung, đây là một phương pháp quản lý mã CSS bằng JavaScript. Ví dụ: điều này có thể có nghĩa là nội dung CSS được xác định bằng JavaScript và kết quả CSS cuối cùng được ứng dụng tạo ngay lập tức.

Trong ngữ cảnh của DevTools, CSS-in-JS có nghĩa là nội dung CSS được chèn vào trang bằng CSSOM API. CSS thông thường được chèn bằng các phần tử <style> hoặc <link> và có nguồn tĩnh (ví dụ: nút DOM hoặc tài nguyên mạng). Ngược lại, CSS-in-JS thường không có nguồn tĩnh. Một trường hợp đặc biệt ở đây là nội dung của phần tử <style> có thể được cập nhật bằng CSSOM API, khiến nguồn không đồng bộ với tệp biểu định kiểu CSS thực tế.

Nếu bạn sử dụng bất kỳ thư viện CSS-in-JS nào (ví dụ: styled-component, Emotion, JSS), thư viện đó có thể chèn các kiểu bằng cách sử dụng các API CSSOM trong nền tùy thuộc vào chế độ phát triển và trình duyệt.

Hãy xem một số ví dụ về cách bạn có thể chèn một tệp kiểu bằng CSSOM API tương tự như những gì thư viện CSS-in-JS đang làm.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

Bạn cũng có thể tạo một tệp kiểu hoàn toàn mới:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

Hỗ trợ CSS trong DevTools

Trong DevTools, tính năng được sử dụng phổ biến nhất khi xử lý CSS là ngăn Styles (Kiểu). Trong ngăn Styles (Kiểu), bạn có thể xem những quy tắc áp dụng cho một phần tử cụ thể, chỉnh sửa các quy tắc đó và xem các thay đổi trên trang theo thời gian thực.

Trước năm ngoái, tính năng hỗ trợ cho các quy tắc CSS được sửa đổi bằng API CSSOM khá hạn chế: bạn chỉ có thể xem các quy tắc đã áp dụng nhưng không thể chỉnh sửa các quy tắc đó. Mục tiêu chính của chúng tôi năm ngoái là cho phép chỉnh sửa các quy tắc CSS-in-JS bằng ngăn Kiểu. Đôi khi, chúng ta cũng gọi các kiểu CSS-in-JS là "được tạo" để cho biết rằng các kiểu này được tạo bằng API Web.

Hãy cùng tìm hiểu chi tiết về cách hoạt động của tính năng chỉnh sửa Kiểu trong Công cụ cho nhà phát triển.

Cơ chế chỉnh sửa kiểu trong Công cụ cho nhà phát triển

Cơ chế chỉnh sửa kiểu trong Công cụ cho nhà phát triển

Khi bạn chọn một phần tử trong DevTools, ngăn Styles (Kiểu) sẽ xuất hiện. Ngăn Styles (Kiểu) đưa ra một lệnh CDP có tên là CSS.getMatchedStylesForNode để lấy các quy tắc CSS áp dụng cho phần tử. CDP là viết tắt của Giao thức công cụ dành cho nhà phát triển Chrome. Đây là một API cho phép giao diện người dùng của Công cụ dành cho nhà phát triển nhận thêm thông tin về trang được kiểm tra.

Khi được gọi, CSS.getMatchedStylesForNode sẽ xác định tất cả các tệp kiểu trong tài liệu và phân tích cú pháp các tệp đó bằng trình phân tích cú pháp CSS của trình duyệt. Sau đó, trình phân tích cú pháp sẽ tạo một chỉ mục liên kết mọi quy tắc CSS với một vị trí trong nguồn của tệp định kiểu.

Bạn có thể thắc mắc tại sao lại cần phân tích cú pháp lại CSS? Vấn đề ở đây là vì lý do hiệu suất, chính trình duyệt không quan tâm đến vị trí nguồn của các quy tắc CSS và do đó, trình duyệt không lưu trữ các quy tắc đó. Tuy nhiên, DevTools cần các vị trí nguồn để hỗ trợ việc chỉnh sửa CSS. Chúng tôi không muốn người dùng Chrome thông thường phải chịu thiệt hại về hiệu suất, nhưng chúng tôi muốn người dùng DevTools có quyền truy cập vào các vị trí nguồn. Phương pháp phân tích cú pháp lại này giải quyết cả hai trường hợp sử dụng với ít nhược điểm nhất.

Tiếp theo, quá trình triển khai CSS.getMatchedStylesForNode sẽ yêu cầu công cụ kiểu của trình duyệt cung cấp các quy tắc CSS khớp với phần tử đã cho. Cuối cùng, phương thức này liên kết các quy tắc do công cụ kiểu trả về với mã nguồn và cung cấp phản hồi có cấu trúc về các quy tắc CSS để DevTools biết phần nào của quy tắc là bộ chọn hoặc thuộc tính. Điều này cho phép DevTools chỉnh sửa bộ chọn và các thuộc tính một cách độc lập.

Bây giờ, hãy cùng xem cách chỉnh sửa. Bạn có nhớ CSS.getMatchedStylesForNode trả về vị trí nguồn cho mọi quy tắc không? Đó là yếu tố quan trọng để chỉnh sửa. Khi bạn thay đổi một quy tắc, Công cụ cho nhà phát triển sẽ đưa ra một lệnh CDP khác thực sự cập nhật trang. Lệnh này bao gồm vị trí ban đầu của mảnh quy tắc đang được cập nhật và văn bản mới mà mảnh cần được cập nhật.

Ở phần phụ trợ, khi xử lý lệnh gọi chỉnh sửa, DevTools sẽ cập nhật tệp kiểu mục tiêu. Trình quản lý cũng cập nhật bản sao của nguồn của bảng định kiểu mà trình quản lý duy trì và cập nhật vị trí nguồn cho quy tắc đã cập nhật. Để phản hồi lệnh gọi chỉnh sửa, giao diện người dùng của DevTools sẽ nhận lại các vị trí đã cập nhật cho mảnh văn bản vừa được cập nhật.

Điều này giải thích lý do việc chỉnh sửa CSS-in-JS trong DevTools không hoạt động ngay từ đầu: CSS-in-JS không có nguồn thực tế được lưu trữ ở bất kỳ đâucác quy tắc CSS nằm trong bộ nhớ của trình duyệt theo cấu trúc dữ liệu CSSOM.

Cách chúng tôi thêm tính năng hỗ trợ cho CSS-in-JS

Vì vậy, để hỗ trợ việc chỉnh sửa các quy tắc CSS-in-JS, chúng tôi quyết định rằng giải pháp tốt nhất là tạo một nguồn cho các bảng định kiểu được tạo có thể được chỉnh sửa bằng cơ chế hiện có được mô tả ở trên.

Bước đầu tiên là tạo văn bản nguồn. Công cụ kiểu của trình duyệt lưu trữ các quy tắc CSS trong lớp CSSStyleSheet. Lớp đó là lớp mà bạn có thể tạo các thực thể từ JavaScript như đã thảo luận trước đó. Mã để tạo văn bản nguồn như sau:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

Phương thức này lặp lại các quy tắc tìm thấy trong một thực thể CSSStyleSheet và tạo một chuỗi duy nhất từ đó. Phương thức này được gọi khi tạo một thực thể của lớp InspectorStyleSheet. Lớp InspectorStyleSheet bao bọc một thực thể CSSStyleSheet và trích xuất siêu dữ liệu bổ sung mà DevTools yêu cầu:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

Trong đoạn mã này, chúng ta thấy CSSOMStyleSheetText gọi CollectStyleSheetRules trong nội bộ. CSSOMStyleSheetText được gọi nếu tệp kiểu không phải là tệp kiểu nội tuyến hoặc tệp kiểu tài nguyên. Về cơ bản, hai đoạn mã này đã cho phép chỉnh sửa cơ bản các tệp kiểu được tạo bằng hàm khởi tạo new CSSStyleSheet().

Một trường hợp đặc biệt là các tệp kiểu liên kết với thẻ <style> đã được thay đổi bằng API CSSOM. Trong trường hợp này, tệp kiểu chứa văn bản nguồn và các quy tắc bổ sung không có trong nguồn. Để xử lý trường hợp này, chúng ta sẽ giới thiệu một phương thức để hợp nhất các quy tắc bổ sung đó vào văn bản nguồn. Ở đây, thứ tự rất quan trọng vì bạn có thể chèn các quy tắc CSS vào giữa văn bản nguồn ban đầu. Ví dụ: hãy tưởng tượng phần tử <style> ban đầu chứa văn bản sau:

/* comment */
.rule1 {}
.rule3 {}

Sau đó, trang đã chèn một số quy tắc mới bằng cách sử dụng API JS để tạo thứ tự quy tắc sau: .rule0, .rule1, .rule2, .rule3, .rule4. Văn bản nguồn thu được sau khi thực hiện thao tác hợp nhất sẽ có dạng như sau:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

Việc giữ lại các nhận xét và thụt lề ban đầu rất quan trọng đối với quá trình chỉnh sửa vì vị trí văn bản nguồn của các quy tắc phải chính xác.

Một khía cạnh đặc biệt khác của các tệp kiểu CSS-in-JS là trang có thể thay đổi các tệp kiểu này bất cứ lúc nào. Nếu các quy tắc CSSOM thực tế không đồng bộ với phiên bản văn bản, thì tính năng chỉnh sửa sẽ không hoạt động. Để làm được điều này, chúng tôi đã giới thiệu một trình thăm dò cho phép trình duyệt thông báo cho phần phụ trợ của DevTools khi một tệp định kiểu đang được thay đổi. Sau đó, các tệp định kiểu đã biến đổi sẽ được đồng bộ hoá trong lệnh gọi tiếp theo đến CSS.getMatchedStylesForNode.

Với tất cả các phần này, tính năng chỉnh sửa CSS-in-JS đã hoạt động nhưng chúng tôi muốn cải thiện giao diện người dùng để cho biết liệu một bảng định kiểu đã được tạo hay chưa. Chúng tôi đã thêm một thuộc tính mới có tên là isConstructed vào CSS.CSSStyleSheetHeader của CDP mà giao diện người dùng sử dụng để hiển thị đúng nguồn của quy tắc CSS:

Biểu định kiểu có thể tạo

Kết luận

Để tóm tắt câu chuyện của chúng ta tại đây, chúng ta đã xem xét các trường hợp sử dụng liên quan đến CSS-in-JS mà DevTools không hỗ trợ và tìm hiểu giải pháp để hỗ trợ các trường hợp sử dụng đó. Điều thú vị trong quá trình triển khai này là chúng tôi có thể tận dụng chức năng hiện có bằng cách tạo các quy tắc CSSOM CSS có văn bản nguồn thông thường, tránh việc phải thiết kế lại hoàn toàn cấu trúc chỉnh sửa kiểu trong DevTools.

Để biết thêm thông tin cơ bản, hãy xem đề xuất thiết kế của chúng tôi hoặc lỗi theo dõi của Chromium. Lỗi này tham chiếu đến tất cả các bản vá có liên quan.

Tải các kênh xem trước xuống

Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Các kênh xem trước này cho phép bạn sử dụng các tính năng mới nhất của DevTools, kiểm thử các API nền tảng web tiên tiến và giúp bạn tìm thấy vấn đề trên trang web của mình trước khi người dùng phát hiện ra!

Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển

Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng, bản cập nhật mới hoặc bất kỳ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.