Ngoài biểu thức chính quy: Nâng cao tính năng phân tích cú pháp giá trị CSS trong Công cụ của Chrome cho nhà phát triển

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

Bạn có nhận thấy các thuộc tính CSS trong Công cụ của Chrome cho nhà phát triển không Thẻ Kiểu gần đây có vẻ chỉn chu hơn một chút? Những bản cập nhật này được triển khai từ Chrome 121 đến 128. Đây là kết quả của những điểm cải tiến đáng kể trong cách chúng tôi phân tích cú pháp và trình bày các giá trị CSS. Trong bài viết này, chúng tôi sẽ hướng dẫn cho bạn thông tin kỹ thuật về quy tắc chuyển đổi này – chuyển từ hệ thống so khớp biểu thức chính quy sang một trình phân tích cú pháp mạnh mẽ hơn.

Hãy so sánh Công cụ cho nhà phát triển hiện tại với phiên bản trước đó:

Trên cùng: đây là Chrome mới nhất, Dưới cùng: Chrome 121.

Khá khác biệt, đúng không? Sau đây là thông tin chi tiết về các điểm cải tiến chính:

  • color-mix. Một bản xem trước tiện lợi trình bày trực quan 2 đối số màu trong hàm color-mix.
  • pink. Bản xem trước màu có thể nhấp cho màu đã đặt tên pink. Nhấp vào biểu tượng đó để mở một công cụ chọn màu giúp dễ dàng điều chỉnh.
  • var(--undefined, [fallback value]). Cải thiện khả năng xử lý các biến không xác định, với biến không xác định bị chuyển sang màu xám và giá trị dự phòng đang hoạt động (trong trường hợp này là màu HSL) được hiển thị cùng với bản xem trước màu có thể nhấp vào.
  • hsl(…): Một bản xem trước màu khác có thể nhấp vào cho hàm màu hsl, giúp truy cập nhanh vào công cụ chọn màu.
  • 177deg: Đồng hồ góc có thể nhấp cho phép bạn kéo và sửa đổi giá trị góc theo cách tương tác.
  • var(--saturation, …): Một đường liên kết nhấp vào được đến định nghĩa thuộc tính tuỳ chỉnh, giúp bạn dễ dàng chuyển đến phần khai báo có liên quan.

Sự khác biệt rất rõ ràng. Để đạt được điều này, chúng tôi phải hướng dẫn Công cụ cho nhà phát triển hiểu rõ hơn về giá trị của thuộc tính CSS rất nhiều so với trước đây.

Những bản xem trước này chưa được cung cấp phải không?

Mặc dù các biểu tượng xem trước này có vẻ quen thuộc, nhưng chúng không phải lúc nào cũng được hiển thị nhất quán, đặc biệt là trong cú pháp CSS phức tạp như ví dụ trên. Ngay cả trong những trường hợp chúng hoạt động đúng cách, người ta thường phải nỗ lực rất nhiều để làm cho chúng hoạt động đúng cách.

Nguyên nhân là do hệ thống phân tích giá trị đã phát triển một cách tự nhiên kể từ những ngày đầu tiên của Công cụ cho nhà phát triển. Tuy nhiên, CSS chưa thể theo kịp các tính năng mới tuyệt vời gần đây mà chúng tôi nhận được từ CSS cũng như sự gia tăng tương ứng về độ phức tạp của ngôn ngữ. Hệ thống cần phải thiết kế lại toàn bộ để bắt kịp sự phát triển và đó chính là điều chúng tôi đã làm!

Cách xử lý giá trị của thuộc tính CSS

Trong Công cụ cho nhà phát triển, quá trình kết xuất và trang trí nội dung khai báo thuộc tính trong thẻ Kiểu được chia thành hai giai đoạn riêng biệt:

  1. Phân tích cấu trúc. Giai đoạn đầu tiên này sẽ phân tích nội dung khai báo thuộc tính để xác định các thành phần cơ bản và mối quan hệ của chúng. Ví dụ: trong phần khai báo border: 1px solid red, mã này sẽ nhận diện 1px là độ dài, solid là chuỗi và red là màu.
  2. Kết xuất. Dựa trên phân tích cấu trúc, giai đoạn kết xuất chuyển đổi các thành phần này thành bản trình bày HTML. Điều này làm phong phú văn bản thuộc tính được hiển thị bằng các thành phần tương tác và chỉ dẫn bằng hình ảnh. Ví dụ: giá trị màu red được hiển thị với một biểu tượng màu có thể nhấp vào. Khi nhấp vào, bạn sẽ thấy một công cụ chọn màu để dễ dàng sửa đổi.

Cụm từ thông dụng

Trước đây, chúng tôi dựa vào biểu thức chính quy (regex) để phân tích các giá trị thuộc tính cho phân tích cấu trúc. Chúng tôi duy trì một danh sách các biểu thức chính quy để khớp với các giá trị của thuộc tính mà chúng tôi đã cân nhắc là trang trí. Ví dụ: có những biểu thức khớp với màu, độ dài, góc của CSS, các biểu thức phụ phức tạp hơn, chẳng hạn như lệnh gọi hàm var, v.v. Chúng tôi quét văn bản từ trái sang phải để phân tích giá trị, liên tục tìm biểu thức đầu tiên trong danh sách khớp với phần tiếp theo của văn bản.

Mặc dù cách này hầu như luôn hiệu quả, nhưng số trường hợp vấn đề lại không tiếp tục tăng. Trong những năm qua, chúng tôi đã nhận được rất nhiều báo cáo lỗi trong đó kết quả so khớp chưa chính xác. Khi chúng tôi sửa các lỗi đó – một số bản sửa lỗi đơn giản, một số bản sửa lỗi khác lại khá phức tạp – chúng tôi phải suy nghĩ lại về cách tiếp cận của mình nhằm hạn chế nợ kỹ thuật. Hãy cùng xem xét một số vấn đề!

Trùng khớp với color-mix()

Biểu thức chính quy mà chúng tôi đã sử dụng cho hàm color-mix() như sau:

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

Mã này khớp với cú pháp của hàm:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

Hãy thử chạy ví dụ sau để trực quan hoá các kết quả trùng khớp.

const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;

// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);

re.exec('');

// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

Kết quả so khớp cho hàm kết hợp màu.

Ví dụ đơn giản hơn sẽ phù hợp. Tuy nhiên, trong ví dụ phức tạp hơn, kết quả khớp <firstColor>hsl(177deg var(--saturation và kết quả khớp <secondColor>100%) 50%)), hoàn toàn vô nghĩa.

Chúng tôi biết rằng đây là vấn đề. Xét cho cùng, CSS với tư cách là ngôn ngữ chính thức không phải là thông thường, vì vậy chúng tôi đã đưa vào quy trình xử lý đặc biệt để xử lý các đối số hàm phức tạp hơn, chẳng hạn như các hàm var. Tuy nhiên, như bạn có thể thấy trong ảnh chụp màn hình đầu tiên, cách này vẫn không hiệu quả trong mọi trường hợp.

Trùng khớp với tan()

Một trong những lỗi báo cáo hài hước hơn là về hàm lượng giác tan() . Biểu thức chính quy mà chúng tôi đang sử dụng để so khớp các màu bao gồm một biểu thức phụ \b[a-zA-Z]+\b(?!-) để so khớp các màu được đặt tên, chẳng hạn như từ khoá red. Sau đó, chúng ta đã kiểm tra xem phần phù hợp có thực sự là một màu đã được đặt tên hay không và đoán xem tan cũng là một màu được đặt tên! Vì vậy, chúng tôi diễn giải nhầm biểu thức tan() dưới dạng màu.

Trùng khớp với var()

Hãy cùng xem một ví dụ khác, các hàm var() có tính năng dự phòng chứa các tệp tham chiếu var() khác: var(--non-existent, var(--margin-vertical)).

Biểu thức chính quy của chúng tôi cho var() sẽ khớp với giá trị này. Ngoại trừ, hệ thống sẽ ngừng so khớp ở dấu ngoặc đơn đóng đầu tiên. Vì vậy, văn bản ở trên khớp dưới dạng var(--non-existent, var(--margin-vertical). Đây là giới hạn trong sách giáo khoa về việc so khớp biểu thức chính quy. Các ngôn ngữ yêu cầu khớp dấu ngoặc đơn về cơ bản không phổ biến.

Chuyển sang trình phân tích cú pháp CSS

Khi tính năng phân tích văn bản bằng biểu thức chính quy ngừng hoạt động (vì ngôn ngữ được phân tích không thông thường), bước tiếp theo sẽ là chuẩn hoá: sử dụng một trình phân tích cú pháp cho cú pháp loại cao hơn. Đối với CSS, đó là một trình phân tích cú pháp cho các ngôn ngữ không theo ngữ cảnh. Trên thực tế, một hệ thống phân tích cú pháp như vậy đã tồn tại trong cơ sở mã của Công cụ cho nhà phát triển: Lezer của CodeMirror. Đây là nền tảng, chẳng hạn như tính năng đánh dấu cú pháp trong CodeMirror, trình chỉnh sửa mà bạn thấy trong bảng Sources (Nguồn). Trình phân tích cú pháp CSS của Lezer cho phép chúng tôi tạo ra các cây cú pháp (không trừu tượng) cho các quy tắc CSS và đã sẵn sàng để chúng tôi sử dụng. Chiến thắng.

Cây cú pháp cho giá trị thuộc tính &quot;hsl(177deg var(--saturation, 100%) 50%)&quot;. Đây là phiên bản đơn giản của kết quả do trình phân tích cú pháp Lezer tạo ra, bỏ đi các nút cú pháp đơn thuần cho dấu phẩy và dấu ngoặc đơn.

Ngoại trừ, chúng tôi nhận thấy không thể di chuyển trực tiếp từ so khớp dựa trên biểu thức chính quy sang so khớp dựa trên trình phân tích cú pháp: hai cách tiếp cận hoạt động từ các hướng đối lập. Khi khớp các phần giá trị với biểu thức chính quy, Công cụ cho nhà phát triển sẽ quét dữ liệu đầu vào từ trái sang phải và liên tục cố gắng tìm kết quả khớp sớm nhất trong danh sách mẫu có thứ tự. Với cây cú pháp, quá trình so khớp sẽ bắt đầu từ dưới lên, chẳng hạn như phân tích các đối số của cuộc gọi trước, sau đó mới cố gắng so khớp lệnh gọi hàm. Hãy xem việc này giống như việc đánh giá một biểu thức số học, trong đó trước tiên bạn sẽ xem xét các biểu thức trong ngoặc đơn, sau đó là các toán tử nhân rồi đến các toán tử cộng. Trong khung này, việc so khớp dựa trên biểu thức chính quy tương ứng với việc đánh giá biểu thức số học từ trái sang phải. Chúng tôi thực sự không muốn viết lại toàn bộ hệ thống so khớp từ đầu: Có 15 cặp trình so khớp và trình kết xuất khác nhau với hàng nghìn dòng mã, khiến chúng tôi khó có thể giao hàng trong một mốc quan trọng.

Do đó, chúng tôi đã đưa ra một giải pháp cho phép thực hiện các thay đổi gia tăng. Chúng tôi sẽ trình bày chi tiết hơn về giải pháp đó ở bên dưới. Tóm lại, chúng tôi giữ nguyên phương pháp 2 giai đoạn, nhưng trong giai đoạn đầu tiên, chúng tôi cố gắng so khớp biểu thức phụ từ dưới lên (do đó phá vỡ luồng biểu thức chính quy) và trong giai đoạn thứ hai, chúng tôi hiển thị từ trên xuống. Trong cả hai giai đoạn, chúng tôi đều sử dụng trình so khớp và hiển thị dựa trên biểu thức chính quy hiện có, thực tế không thay đổi và do đó có thể di chuyển từng trình đơn một.

Giai đoạn 1: So khớp từ dưới lên

Giai đoạn đầu tiên thực hiện chính xác và độc quyền những gì được nêu trên bìa. Chúng ta truyền tải cây theo thứ tự từ dưới lên trên và cố gắng so khớp các biểu thức phụ ở mỗi nút cây cú pháp mà chúng ta truy cập. Để khớp với một biểu thức phụ cụ thể, trình so khớp có thể sử dụng biểu thức chính quy giống như trong hệ thống hiện có. Kể từ phiên bản 128, chúng tôi vẫn thực hiện trong một số trường hợp, chẳng hạn như đối với thời lượng trùng khớp. Ngoài ra, trình so khớp có thể phân tích cấu trúc của cây con đã bị can thiệp vào hệ thống tại nút hiện tại. Điều này cho phép mô-đun này phát hiện các lỗi cú pháp và ghi lại thông tin cấu trúc cùng lúc.

Hãy xem xét ví dụ về cây cú pháp ở trên:

Giai đoạn 1: So khớp từ dưới lên trên cây cú pháp.

Đối với cây này, các trình so khớp của chúng tôi sẽ áp dụng theo thứ tự sau:

  1. hsl(177degvar(--saturation, 100%) 50%): Trước tiên, chúng ta khám phá đối số đầu tiên của lệnh gọi hàm hsl, đó là góc hue. Chúng ta so khớp góc đó với một công cụ so khớp góc để có thể trang trí giá trị góc bằng biểu tượng góc.
  2. hsl(177degvar(--saturation, 100%)50%): Tiếp theo, chúng ta phát hiện lệnh gọi hàm var có trình so khớp var. Đối với các cuộc gọi như vậy, chúng ta chủ yếu muốn thực hiện 2 việc:
    • Tra cứu phần khai báo của biến và tính toán giá trị của biến đó, đồng thời thêm một đường liên kết và cửa sổ bật lên vào tên biến để kết nối với các biến đó.
    • Trang trí cuộc gọi bằng biểu tượng màu nếu giá trị đã tính là một màu. Thực sự còn một điều thứ ba, nhưng chúng ta sẽ nói về điều đó sau.
  3. hsl(177deg var(--saturation, 100%) 50%): Cuối cùng, chúng ta so khớp biểu thức lệnh gọi cho hàm hsl để có thể trang trí biểu thức đó bằng biểu tượng màu.

Ngoài việc tìm kiếm các biểu thức phụ mà chúng ta muốn trang trí, thực sự có một tính năng thứ hai mà chúng ta đang chạy trong quá trình so khớp. Xin lưu ý rằng trong bước 2, chúng ta đã nói là chúng ta tra cứu giá trị được tính toán cho tên biến. Trên thực tế, chúng tôi tiến thêm một bước và truyền kết quả lên cây. Không chỉ đối với biến mà còn đối với giá trị dự phòng! Đảm bảo rằng khi truy cập một nút hàm var, các nút con của nút đó đã được truy cập trước, vì vậy chúng ta đã biết kết quả của mọi hàm var có thể xuất hiện trong giá trị dự phòng. Do đó, chúng ta có thể dễ dàng thay thế các hàm var bằng kết quả tương ứng mà không tốn nhiều chi phí. Điều này cho phép chúng ta trả lời các câu hỏi đơn giản như "Kết quả của var này có gọi một màu không?", như đã làm trong bước #2.

Giai đoạn 2: Hiển thị từ trên xuống

Đối với giai đoạn thứ hai, chúng ta đảo ngược hướng. Lấy kết quả so khớp từ giai đoạn 1, chúng tôi kết xuất cây thành HTML bằng cách truyền tải theo thứ tự từ trên xuống dưới. Đối với mỗi nút được truy cập, chúng tôi kiểm tra xem nút đó có khớp hay không. Nếu có, hãy gọi trình kết xuất tương ứng của trình so khớp. Chúng ta tránh cần xử lý đặc biệt đối với các nút chỉ chứa văn bản (như NumberLiteral "50%") bằng cách thêm trình so khớp và trình kết xuất mặc định cho các nút văn bản. Trình kết xuất chỉ tạo ra các nút HTML. Khi kết hợp các nút này lại với nhau, trình kết xuất sẽ tạo ra nội dung trình bày giá trị thuộc tính, bao gồm cả các phần trang trí của thuộc tính đó.

Giai đoạn 2: Hiển thị từ trên xuống trên cây cú pháp.

Đối với cây ví dụ, dưới đây là thứ tự hiển thị giá trị thuộc tính:

  1. Truy cập lệnh gọi hàm hsl. Kết quả này trùng khớp, vì vậy, hãy gọi trình kết xuất hàm màu. Nó thực hiện hai việc:
    • Tính toán giá trị màu thực tế bằng cách sử dụng cơ chế thay thế nhanh chóng cho mọi đối số var, sau đó vẽ một biểu tượng màu.
    • Kết xuất đệ quy phần tử con của CallExpression. Thao tác này tự động đảm nhận việc hiển thị tên hàm, dấu ngoặc đơn và dấu phẩy, vốn chỉ là văn bản.
  2. Truy cập đối số đầu tiên của lệnh gọi hsl. Giá trị đã khớp, vì vậy hãy gọi trình kết xuất góc. Trình kết xuất này sẽ vẽ biểu tượng góc và văn bản của góc.
  3. Truy cập đối số thứ hai, đó là lệnh gọi var. Kết quả này đã khớp, vì vậy, hãy gọi var renderer (trình kết xuất đồ hoạ), cho ra như sau:
    • Văn bản var( ở đầu.
    • Tên biến và trang trí bằng một đường liên kết đến định nghĩa của biến hoặc bằng màu văn bản màu xám để cho biết biến đó là không xác định. Thao tác này cũng thêm cửa sổ bật lên vào biến để hiển thị thông tin về giá trị của biến.
    • Dấu phẩy, sau đó kết xuất đệ quy giá trị dự phòng.
    • Một dấu ngoặc đơn đóng.
  4. Truy cập vào đối số cuối cùng của lệnh gọi hsl. Kết quả không khớp, vì vậy chỉ xuất nội dung văn bản của nó.

Bạn có nhận thấy rằng trong thuật toán này, lượt hiển thị kiểm soát hoàn toàn cách hiển thị các phần tử con của một nút được khớp không? Hiển thị đệ quy phần tử con là chủ động. Thủ thuật này hỗ trợ quá trình di chuyển từng bước từ hiển thị dựa trên biểu thức chính quy sang hiển thị dựa trên cú pháp dạng cây. Đối với các nút khớp với trình so khớp biểu thức chính quy cũ, bạn có thể sử dụng trình kết xuất đồ hoạ tương ứng ở dạng ban đầu. Trong thuật ngữ cây cú pháp, nó sẽ chịu trách nhiệm kết xuất toàn bộ cây con và kết quả của nó (nút HTML) có thể được thêm dễ dàng vào quá trình kết xuất xung quanh. Cách này cho phép chúng ta lựa chọn trình so khớp cổng và trình kết xuất theo cặp, cũng như hoán đổi từng trình kết xuất một.

Một tính năng thú vị khác của trình kết xuất kiểm soát quá trình kết xuất phần tử con của nút được so khớp là cho phép chúng ta giải thích về các phần phụ thuộc giữa các biểu tượng mà chúng ta đang thêm. Trong ví dụ trên, màu do hàm hsl tạo ra rõ ràng phụ thuộc vào giá trị màu sắc của hàm đó. Điều đó có nghĩa là màu sắc mà biểu tượng màu hiển thị phụ thuộc vào góc mà biểu tượng góc hiển thị. Nếu người dùng mở trình chỉnh sửa góc thông qua biểu tượng đó và sửa đổi góc, thì giờ đây chúng ta có thể cập nhật màu của biểu tượng màu theo thời gian thực:

Như bạn có thể thấy trong ví dụ trên, chúng ta cũng sử dụng cơ chế này cho các cặp biểu tượng khác, chẳng hạn như cho color-mix() và hai kênh màu hoặc các hàm var trả về một màu từ màu dự phòng.

Ảnh hưởng đến hiệu suất

Khi tìm hiểu sâu về vấn đề này nhằm cải thiện độ tin cậy và khắc phục các vấn đề lâu dài, chúng tôi dự kiến một số thay đổi về hiệu suất vì chúng tôi đã bắt đầu chạy một trình phân tích cú pháp hoàn chỉnh. Để kiểm tra điều này, chúng tôi đã tạo một điểm chuẩn hiển thị khoảng 3,5 nghìn nội dung khai báo thuộc tính và lập hồ sơ cả các phiên bản dựa trên biểu thức chính quy và dựa trên trình phân tích cú pháp với điều tiết 6 lần trên máy M1.

Như chúng tôi dự kiến, phương pháp dựa trên phân tích cú pháp hoá ra chậm hơn 27% so với phương pháp dựa trên biểu thức chính quy trong trường hợp đó. Phương pháp dựa trên biểu thức chính quy mất 11 giây để hiển thị và phương pháp dựa trên trình phân tích cú pháp mất 15 giây để hiển thị.

Dựa trên những lợi ích có được từ phương pháp mới, chúng tôi đã quyết định sử dụng phương pháp đó.

Xác nhận

Chúng tôi xin gửi lời cảm ơn sâu sắc nhất đến Sofia Emelianova và Jecelyn Yeen vì đã giúp đỡ rất nhiều trong việc chỉnh sửa bài đăng này!

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

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

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

Sử dụng các lựa chọn sau để thảo luận về các tính năng và thay đổi mới trong bài đăng, 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.

  • Gửi đề xuất hoặc phản hồi cho chúng tôi qua crbug.com.
  • Báo cáo sự cố Công cụ cho nhà phát triển bằng cách sử dụng Tuỳ chọn khác   Thêm > Trợ giúp > Báo cáo vấn đề về Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
  • Tweet tại @ChromeDevTools.
  • Để lại bình luận về Tính năng mới của chúng tôi trong Video trên YouTube hoặc Mẹo trong Công cụ cho nhà phát triển Video trên YouTube.