Bạn có nhận thấy các thuộc tính CSS trong thẻ Kiểu của Công cụ của Chrome cho nhà phát triển gần đây có vẻ chỉn chu hơn một chút không? Những bản cập nhật này, được triển khai giữa Chrome 121 và 128, là kết quả của một đ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 bạn về các chi tiết kỹ thuật của quá trình 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 DevTools hiện tại với phiên bản trước:
Khá khác biệt, phải không? Sau đây là thông tin chi tiết về các điểm cải tiến chính:
color-mix
. Bản xem trước tiện lợi trình bày trực quan hai đối số màu trong hàmcolor-mix
.pink
. Bản xem trước màu có thể nhấp được cho màu được đặt tênpink
. Nhấp vào biểu tượng này để mở công cụ chọn màu giúp bạn 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 có 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) 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 có thể nhấp khác cho hàm màuhsl
, cung cấp quyền 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, …)
: Đường liên kết có thể nhấp đế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 là rất rõ ràng. Để đạt được điều này, chúng tôi phải hướng dẫn DevTools hiểu rõ hơn nhiều về các giá trị thuộc tính CSS so với trước đây.
Những bản xem trước này đã có sẵn 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 hoạt động, bạn thường phải nỗ lực đáng kể để các công cụ này hoạt động đúng cách.
Lý do là 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 của DevTools. 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ị thuộc tính CSS
Trong DevTools, quá trình kết xuất và trang trí nội dung khai báo thuộc tính trong thẻ Styles (Kiểu) được chia thành hai giai đoạn riêng biệt:
- Phân tích cấu trúc. Giai đoạn ban đầu này 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ệ giữa các thành phần đó. Ví dụ: trong phần khai báo
border: 1px solid red
, trình biên dịch sẽ nhận dạng1px
là một độ dài,solid
là một chuỗi vàred
là một màu. - Kết xuất. Dựa trên kết quả phân tích cấu trúc, giai đoạn kết xuất sẽ chuyển đổi các thành phần này thành một bản trình bày HTML. Việc này sẽ làm phong phú văn bản thuộc tính hiển thị bằng các thành phần tương tác và tín hiệu hình ảnh. Ví dụ: giá trị màu
red
được hiển thị bằng một biểu tượng màu có thể nhấp vào. Khi nhấp vào biểu tượng này, một bộ chọn màu sẽ xuất hiện để bạn dễ dàng sửa đổi.
Cụm từ thông dụng
Trước đây, chúng ta 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 mục đích 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 để so khớp các bit giá trị thuộc tính mà chúng tôi xem xét trang trí. Ví dụ: có các biểu thức khớp với màu sắc, độ dài, góc CSS, các biểu thức con phức tạp 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 hoạt động tốt trong hầu hết các trường hợp, nhưng số lượng trường hợp không hoạt động vẫn tiếp tục tăng lên. Trong những năm qua, chúng tôi đã nhận được một số lượng lớn báo cáo lỗi về việc so khớp không chính xác. Khi khắc phục các lỗi này (một số lỗi đơn giản, một số lỗi khá phức tạp), chúng tôi đã phải suy nghĩ lại phương pháp để tránh tình trạng nợ kỹ thuật. Hãy cùng xem một số vấn đề!
So khớp color-mix()
Biểu thức chính quy mà chúng ta sử dụng cho hàm color-mix()
là như sau:
/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g
Cú pháp 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);
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>
là hsl(177deg var(--saturation
và kết quả khớp <secondColor>
là 100%) 50%))
, hoàn toàn vô nghĩa.
Chúng tôi biết đây là vấn đề. Xét cho cùng, CSS là một ngôn ngữ chính thức không thông thường, vì vậy, chúng tôi đã đưa vào cách xử lý đặc biệt để xử lý các đối số hàm phức tạp hơn, chẳng hạn như 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 hoạt động trong một số trường hợp.
So khớp tan()
Một trong những lỗi được báo cáo thú vị 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 được so khớ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 ta đã diễn giải sai biểu thức tan()
là màu sắc.
Trùng khớp với var()
Hãy xem một ví dụ khác, các hàm var()
có phương thức 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 ta cho var()
sẽ khớp với giá trị này. Ngoại trừ việc nó sẽ dừng so khớp tại dấu ngoặc đóng đầu tiên. Vì vậy, văn bản ở trên được so khớp là var(--non-existent, var(--margin-vertical)
. Đây là một giới hạn trong sách giáo khoa về việc so khớp biểu thức chính quy. Về cơ bản, các ngôn ngữ yêu cầu phải so khớp dấu ngoặc đơn là không chính quy.
Chuyển sang trình phân tích cú pháp CSS
Khi việc 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 phải là ngôn ngữ chính quy), bạn cần thực hiện bước tiếp theo chính tắc: sử dụng trình phân tích cú pháp cho cú pháp loại cao hơn. Đối với CSS, điều đó có nghĩa là trình phân tích cú pháp cho các ngôn ngữ không có ngữ cảnh. Trên thực tế, hệ thống trình phân tích cú pháp như vậy đã tồn tại trong cơ sở mã DevTools: Lezer của CodeMirror, là nền tảng cho các tính năng như làm nổi bật cú pháp trong CodeMirror, trình soạn thảo mà bạn tìm thấy trong bảng điều khiển 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.
Tuy nhiên, ngay từ đầu, chúng tôi nhận thấy không thể di chuyển trực tiếp từ phương thức so khớp dựa trên biểu thức chính quy sang phương thức so khớp dựa trên trình phân tích cú pháp: hai phương pháp này hoạt động theo hướng đối lập. Khi so khớp các phần giá trị với biểu thức chính quy, DevTools sẽ quét dữ liệu đầu vào từ trái sang phải, liên tục tìm kiếm kết quả khớp sớm nhất trong danh sách mẫu đã sắp xếp. Với cây cú pháp, việc so khớp sẽ bắt đầu từ dưới lên, ví dụ: trước tiên, hãy phân tích các đối số của lệnh gọi trước khi cố gắng so khớp lệnh gọi hàm. Hãy coi việc này là đá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 dấu 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 trình so khớp và cặp trình kết xuất khác nhau, với hàng nghìn dòng mã cho các trình so khớp và trình kết xuất đó, khiến chúng tôi khó có thể phát hành hệ thống so khớp trong một mốc duy nhất.
Vì vậy, 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ẽ mô tả chi tiết hơn về giải pháp này ở bên dưới. Tóm lại, chúng ta vẫn giữ phương pháp hai giai đoạn, nhưng trong giai đoạn đầu tiên, chúng ta cố gắng so khớp biểu thức con 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 ta hiển thị từ trên xuống. Trong cả hai giai đoạn, chúng ta có thể sử dụng các trình so khớp và trình kết xuất dựa trên biểu thức chính quy hiện có, về cơ bản không thay đổi, do đó có thể di chuyển từng trình so khớp và trình kết xuất.
Giai đoạn 1: So khớp từ dưới lên
Giai đoạn đầu tiên ít nhiều cũng thực hiện chính xác và riêng biệt những gì được viết 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 độ dài 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ắt nguồn từ nút hiện tại. Nhờ đó, trình phân tích cú pháp có thể phát hiện lỗi cú pháp và ghi lại thông tin cấu trúc cùng một lúc.
Hãy xem xét ví dụ về cây cú pháp ở trên:
Đối với cây này, trình so khớp của chúng ta sẽ áp dụng theo thứ tự sau:
hsl(
177deg
var(--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àmhsl
, đó là góc màu sắc. Chúng ta so khớp giá trị này với trình so khớp góc để có thể trang trí giá trị góc bằng biểu tượng góc.hsl(177deg
var(--saturation, 100%)
50%)
: Thứ hai, chúng ta khám phá lệnh gọi hàmvar
bằng trình so khớp var. Đối với các lệnh gọi như vậy, chúng ta chủ yếu muốn làm hai việc:- Tìm nội dung 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à một 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í lệnh gọi bằng biểu tượng màu nếu giá trị được tính toán là màu. Thực ra còn một điều thứ ba, nhưng chúng ta sẽ nói về điều đó sau.
hsl(177deg var(--saturation, 100%) 50%)
: Cuối cùng, chúng ta so khớp biểu thức gọi cho hàmhsl
để có thể trang trí hàm này bằng biểu tượng màu.
Ngoài việc tìm kiếm các biểu thức con mà chúng ta muốn trang trí, thực sự còn 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. Và không chỉ cho biến mà còn cho giá trị dự phòng! Chúng tôi đảm bảo rằng khi truy cập vào 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 và nhanh chóng thay thế các hàm var
bằng kết quả của các hàm đó, nhờ đó có thể dễ dàng trả lời các câu hỏi như "Kết quả của lệnh gọi var
này có phải là màu không?", như chúng ta đã làm ở bước 2.
Giai đoạn 2: Kết xuất 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 ta hiển thị cây thành HTML bằng cách duyệt qua cây theo thứ tự từ trên xuống dưới. Đối với mỗi nút đã truy cập, chúng ta kiểm tra xem nút đó có khớp hay không và 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 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 đó.
Đối với cây ví dụ, sau đây là thứ tự hiển thị giá trị thuộc tính:
- Truy cập vào lệnh gọi hàm
hsl
. Hàm này đã khớp, vì vậy, hãy gọi trình kết xuất hàm màu. Phương thức này 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 cho mọi đối số
var
, sau đó vẽ biểu tượng màu. - Hiển thị đệ quy các phần tử con của
CallExpression
. Thao tác này sẽ tự động xử lý việc hiển thị tên hàm, dấu ngoặc đơn và dấu phẩy, đây chỉ là văn bản.
- Tính toán giá trị màu thực tế bằng cách sử dụng cơ chế thay thế nhanh cho mọi đối số
- Truy cập vào đố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. - Truy cập vào đố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í tên biến bằng đường liên kết đến định nghĩa của biến hoặc bằng màu văn bản xám để cho biết biến chưa được xác định. Tên biến cũng thêm một 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 đó hiển thị giá trị dự phòng theo đệ quy.
- Một dấu ngoặc đơn đóng.
- Văn bản
- Truy cập vào đối số cuối cùng của lệnh gọi
hsl
. Không khớp nên chỉ xuất nội dung văn bản.
Bạn có nhận thấy rằng trong thuật toán này, một lượt kết xuất 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 đã so khớp không? Việc hiển thị đệ quy các phần tử con là chủ động. Thủ thuật này đã cho phép di chuyển từng bước từ chế độ kết xuất dựa trên biểu thức chính quy sang chế độ kết xuất dựa trên cây cú pháp. Đố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, thành phần này sẽ chịu trách nhiệm hiển thị toàn bộ cây con và kết quả của thành phần này (một nút HTML) có thể được cắm gọn gàng vào quy trình kết xuất xung quanh. Điều này cho phép chúng ta chuyển các trình so khớp và trình kết xuất theo cặp và hoán đổi từng trình so khớp và trình kết xuấ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ì chúng ta hiện 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 của nó, hoặc các hàm var
trả về một màu từ dự phòng.
Tác độ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ị.
Do những thành công mà chúng tôi đạt được từ phương pháp mới này, nên chúng tôi quyết định tiếp tục sử dụng phương pháp này.
Lời cảm ơn
Chúng tôi vô cùng cảm ơn Sofia Emelianova và Jecelyn Yeen đã giúp đỡ chúng tôi chỉnh sửa bài đăng này!
Tải 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.
- Gửi ý kiến phản hồi và yêu cầu về tính năng cho chúng tôi tại crbug.com.
- Báo cáo sự cố của Công cụ cho nhà phát triển bằng cách sử dụng biểu tượng Tuỳ chọn khác > Trợ giúp > Báo cáo sự cố của Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
- Gửi tweet đến @ChromeDevTools.
- Để lại bình luận về Tính năng mới trong video trên YouTube trong Công cụ cho nhà phát triển hoặc Mẹo về công cụ cho nhà phát triển trong các video trên YouTube.