فراتر از عبارات معمولی: تقویت تجزیه ارزش CSS در ابزار توسعه کروم

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

آیا متوجه شده‌اید که ویژگی‌های CSS در تب Styles Chrome DevTools اخیراً کمی زیباتر به نظر می‌رسند؟ این به‌روزرسانی‌ها که بین Chrome 121 و 128 ارائه شده‌اند، نتیجه بهبود قابل توجهی در نحوه تجزیه و ارائه مقادیر CSS هستند. در این مقاله، ما شما را از طریق جزئیات فنی این تبدیل راهنمایی می‌کنیم - از یک سیستم تطبیق عبارات منظم به یک تجزیه‌کننده قوی‌تر حرکت می‌کنیم.

اجازه دهید DevTools فعلی را با نسخه قبلی مقایسه کنیم:

بالا: این جدیدترین کروم است، پایین: کروم 121.

تفاوت زیادی دارد، درست است؟ در اینجا خلاصه ای از پیشرفت های کلیدی آورده شده است:

  • color-mix . یک پیش نمایش مفید که به صورت بصری دو آرگومان رنگی را در تابع color-mix نشان می دهد.
  • pink . پیش نمایش رنگ قابل کلیک برای رنگ pink نامگذاری شده. روی آن کلیک کنید تا انتخابگر رنگ برای تنظیمات آسان باز شود.
  • var(--undefined, [fallback value]) . مدیریت متغیرهای تعریف نشده بهبود یافته، با متغیر تعریف نشده خاکستری و مقدار بازگشتی فعال (در این مورد، یک رنگ HSL) با پیش نمایش رنگ قابل کلیک نمایش داده می شود.
  • hsl(…) : یکی دیگر از پیش نمایش های رنگی قابل کلیک برای تابع رنگ hsl که دسترسی سریع به انتخابگر رنگ را فراهم می کند.
  • 177deg : یک ساعت زاویه ای قابل کلیک که به شما امکان می دهد به طور تعاملی مقدار زاویه را بکشید و تغییر دهید.
  • var(--saturation, …) : یک پیوند قابل کلیک به تعریف ویژگی سفارشی، که پرش به اعلان مربوطه را آسان می کند.

تفاوت چشمگیر است. برای رسیدن به این هدف، باید به DevTools یاد می‌دادیم که مقادیر ویژگی‌های CSS را بسیار بهتر از قبل درک کند.

آیا این پیش نمایش ها از قبل در دسترس نبودند؟

اگرچه ممکن است این نمادهای پیش‌نمایش آشنا به نظر برسند، اما همیشه به طور مداوم نمایش داده نمی‌شوند، به خصوص در نحو پیچیده CSS مانند مثال بالا. حتی در مواردی که آنها کار می کردند، اغلب به تلاش قابل توجهی برای عملکرد صحیح آنها نیاز بود.

دلیل آن این است که سیستم تجزیه و تحلیل مقادیر از اولین روزهای توسعه DevTools به طور ارگانیک در حال رشد بوده است. با این حال، نتوانسته است با ویژگی‌های جدید شگفت‌انگیز اخیری که از CSS دریافت می‌کنیم، و افزایش متناظر در پیچیدگی زبان همگام شود. این سیستم نیاز به طراحی مجدد کامل داشت تا با تکامل همگام شود و این دقیقاً همان کاری است که ما انجام دادیم!

نحوه پردازش مقادیر ویژگی CSS

در DevTools، فرآیند رندر و تزئین اعلان‌های دارایی در تب Styles به دو مرحله مجزا تقسیم می‌شود:

  1. تحلیل ساختاری این مرحله اولیه اظهارنامه دارایی را تشریح می کند تا اجزای اساسی آن و روابط آنها را شناسایی کند. به عنوان مثال، در border: 1px solid red ، 1px به عنوان طول، solid به عنوان یک رشته و red به عنوان یک رنگ تشخیص می دهد.
  2. رندرینگ. بر اساس تحلیل ساختاری، فاز رندر این اجزا را به یک نمایش HTML تبدیل می کند. این متن ویژگی نمایش داده شده را با عناصر تعاملی و نشانه های بصری غنی می کند. به عنوان مثال، مقدار رنگ red با یک نماد رنگ قابل کلیک ارائه می شود که با کلیک کردن، یک انتخابگر رنگ را برای اصلاح آسان نشان می دهد.

عبارات منظم

پیش از این، ما به عبارات منظم (regexes) برای تشریح مقادیر ویژگی برای تجزیه و تحلیل ساختاری تکیه می‌کردیم. ما فهرستی از رجکس‌ها را برای مطابقت با بیت‌هایی از ارزش‌های دارایی که برای تزئین در نظر گرفتیم، نگهداری می‌کنیم. برای مثال، عباراتی وجود داشتند که با رنگ‌های CSS، طول‌ها، زاویه‌ها، زیرعبارات پیچیده‌تر مانند فراخوانی تابع var و غیره مطابقت داشتند. ما متن را از چپ به راست اسکن کردیم تا تجزیه و تحلیل ارزش را انجام دهیم و به طور مداوم به دنبال اولین عبارت از لیست می‌گردیم که با قطعه بعدی متن مطابقت دارد.

در حالی که این در بیشتر مواقع خوب کار می کرد، تعداد مواردی که رشد نکردند. در طول سال‌ها، ما تعداد زیادی گزارش اشکال دریافت کرده‌ایم که تطبیق آن به درستی انجام نشده است. همانطور که ما آنها را تعمیر کردیم - برخی از اصلاحات ساده، برخی دیگر کاملاً مفصل - ما مجبور شدیم در رویکرد خود تجدید نظر کنیم تا بدهی فنی خود را حفظ کنیم. بیایید نگاهی به برخی از مسائل بیندازیم!

تطبیق color-mix()

regex ما برای تابع color-mix() به صورت زیر بود :

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

که با نحو آن مطابقت دارد:

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

سعی کنید مثال زیر را برای تجسم مسابقات اجرا کنید.

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);

نتیجه را برای تابع ترکیب رنگ مطابقت دهید.

مثال ساده تر به خوبی کار می کند. با این حال، در مثال پیچیده تر، تطابق <firstColor> hsl(177deg var(--saturation و تطابق <secondColor> 100%) 50%)) ، که کاملاً بی معنی است.

ما می دانستیم که این یک مشکل است. از این گذشته، CSS به‌عنوان یک زبان رسمی معمولی نیست، بنابراین ما قبلاً برای مقابله با آرگومان‌های تابع پیچیده‌تر، مانند توابع var ، مدیریت خاصی را در نظر گرفته‌ایم. با این حال، همانطور که در تصویر اول مشاهده می کنید، هنوز در همه موارد کار نمی کند.

همسان tan()

یکی از باگ های خنده دار گزارش شده در مورد تابع tan() مثلثاتی بود. regexی که ما برای تطبیق رنگ ها استفاده می کردیم شامل یک عبارت فرعی \b[a-zA-Z]+\b(?!-) برای تطبیق رنگ های نامگذاری شده مانند کلمه کلیدی red بود. سپس بررسی کردیم که آیا قسمت تطبیق شده در واقع یک رنگ نامگذاری شده است یا خیر، و حدس بزنید، tan نیز یک رنگ نامگذاری شده است! بنابراین، ما به اشتباه عبارات tan() را به عنوان رنگ تفسیر کردیم.

مطابق با var()

بیایید نگاهی به مثال دیگری بیندازیم، توابع var() با یک بازگشت حاوی دیگر ارجاعات var() : var(--non-existent, var(--margin-vertical)) .

regex ما برای var() خوشبختانه با این مقدار مطابقت دارد. به جز، در اولین پرانتز بسته شدن تطبیق متوقف می شود. بنابراین متن بالا به عنوان var(--non-existent, var(--margin-vertical) مطابقت داده می شود. این یک محدودیت کتاب درسی برای تطبیق عبارات منظم است. زبان هایی که به پرانتز منطبق نیاز دارند اساساً منظم نیستند.

انتقال به تجزیه کننده CSS

هنگامی که تجزیه و تحلیل متن با استفاده از عبارات منظم کار نمی کند (زیرا زبان تجزیه و تحلیل شده منظم نیست) یک مرحله متعارف بعدی وجود دارد: استفاده از تجزیه کننده برای گرامر نوع بالاتر. برای CSS، این به معنی تجزیه کننده برای زبان های بدون زمینه است. در واقع، چنین سیستم تجزیه‌کننده‌ای قبلاً در پایگاه کد DevTools وجود داشته است: CodeMirror's Lezer ، که برای مثال، پایه‌ای برای برجسته کردن نحو در CodeMirror، ویرایشگری است که در پنل Sources پیدا می‌کنید. تجزیه‌کننده CSS Lezer به ما اجازه می‌دهد درخت‌های نحوی (غیر انتزاعی) را برای قوانین CSS تولید کنیم و برای استفاده آماده بود. پیروزی.

یک درخت نحو برای مقدار ویژگی «hsl(177 درجه var(--اشباع، 100%) 50%)». این یک نسخه ساده شده از نتیجه تولید شده توسط تجزیه کننده Lezer است که گره های صرفا نحوی را برای کاما و پرانتز کنار گذاشته است.

به جز، خارج از جعبه، ما دریافتیم که انتقال مستقیم از تطابق مبتنی بر regex به تطبیق مبتنی بر تجزیه‌کننده غیرممکن است: این دو رویکرد از جهت‌های متضاد کار می‌کنند. هنگام تطبیق قطعات مقادیر با عبارات منظم، DevTools ورودی را از چپ به راست اسکن می‌کند، و مکرراً سعی می‌کند اولین تطابق را از لیست مرتب الگوها پیدا کند. با یک درخت نحو، تطبیق از پایین به بالا شروع می‌شود، برای مثال، ابتدا آرگومان‌های یک فراخوانی را تجزیه و تحلیل می‌کنند، قبل از تلاش برای مطابقت با فراخوانی تابع. آن را به عنوان ارزیابی یک عبارت حسابی در نظر بگیرید، جایی که ابتدا عبارات پرانتز شده، سپس عملگرهای ضربی و سپس عملگرهای افزایشی را در نظر بگیرید. در این کادربندی، تطابق مبتنی بر regex مربوط به ارزیابی عبارت حسابی از چپ به راست است. ما واقعاً نمی‌خواستیم کل سیستم تطبیق را از ابتدا بازنویسی کنیم: 15 جفت تطبیق‌دهنده و رندر مختلف با هزاران خط کد برای آنها وجود داشت، که بعید می‌کرد بتوانیم آن را در یک نقطه عطف ارسال کنیم.

بنابراین ما به راه حلی رسیدیم که به ما امکان می داد تغییرات تدریجی ایجاد کنیم که در زیر با جزئیات بیشتر توضیح خواهیم داد. به طور خلاصه، رویکرد دو فازی را حفظ کردیم، اما در مرحله اول سعی می‌کنیم عبارات فرعی را از پایین به بالا مطابقت دهیم (در نتیجه با جریان regex شکسته می‌شود) و در فاز دوم از بالا به پایین رندر می‌کنیم. در هر دو مرحله، می‌توانیم از تطبیق‌ها و رندرهای مبتنی بر regex استفاده کنیم، عملاً بدون تغییر، و بنابراین می‌توانیم آنها را یکی یکی منتقل کنیم.

فاز 1: تطبیق از پایین به بالا

فاز اول کم و بیش دقیقاً و منحصراً همان کاری را که روی جلد می گوید انجام می دهد. درخت را به ترتیب از پایین به بالا پیمایش می کنیم و سعی می کنیم زیر عبارات را در هر گره درخت نحوی که بازدید می کنیم مطابقت دهیم. برای تطبیق یک زیر عبارت خاص، یک تطبیق می‌تواند از regex درست مانند سیستم موجود استفاده کند. از نسخه 128، ما هنوز در موارد معدودی انجام می دهیم، به عنوان مثال، برای تطبیق طول ها . متناوبا، یک تطبیق می تواند ساختار زیردرختی که در گره فعلی ریشه دارد را تجزیه و تحلیل کند. این به آن اجازه می دهد تا خطاهای نحوی را بگیرد و اطلاعات ساختاری را همزمان ثبت کند.

مثال درخت نحو را از بالا در نظر بگیرید:

فاز 1: تطبیق از پایین به بالا در درخت نحو.

برای این درخت، تطبیق‌کنندگان ما به ترتیب زیر اعمال می‌شوند:

  1. hsl( 177deg var(--saturation, 100%) 50%) : ابتدا، اولین آرگومان فراخوانی تابع hsl ، زاویه رنگ را کشف می کنیم. ما آن را با یک تطبیق زاویه مطابقت می دهیم تا بتوانیم مقدار زاویه را با نماد زاویه تزئین کنیم.
  2. hsl(177deg var(--saturation, 100%) 50%) : دوم، ما فراخوانی تابع var را با تطبیق var کشف می کنیم. برای چنین تماس هایی ما عمدتاً می خواهیم دو کار انجام دهیم:
    • اعلان متغیر را جستجو کنید و مقدار آن را محاسبه کنید و به ترتیب یک لینک و یک popover به نام متغیر اضافه کنید تا به آنها متصل شوید.
    • اگر مقدار محاسبه شده یک رنگ است، تماس را با نماد رنگی تزئین کنید. در واقع مورد سومی هم وجود دارد، اما بعداً در مورد آن صحبت خواهیم کرد.
  3. hsl(177deg var(--saturation, 100%) 50%) : در نهایت، عبارت فراخوانی تابع hsl را مطابقت می دهیم تا بتوانیم آن را با نماد رنگ تزئین کنیم.

علاوه بر جستجوی عبارت‌های فرعی که می‌خواهیم تزئین کنیم، در واقع ویژگی دومی وجود دارد که به عنوان بخشی از فرآیند تطبیق اجرا می‌کنیم. توجه داشته باشید که در مرحله 2 گفتیم که مقدار محاسبه شده را برای نام متغیر جستجو می کنیم. در واقع، ما آن را یک قدم جلوتر می‌گذاریم و نتایج را در درخت منتشر می‌کنیم. و نه فقط برای متغیر، بلکه برای مقدار بازگشتی! تضمین شده است که هنگام بازدید از یک گره تابع var ، فرزندان آن از قبل بازدید شده اند، بنابراین ما از قبل نتایج هر تابع var که ممکن است در مقدار بازگشتی ظاهر شود را می دانیم. بنابراین ما می‌توانیم به‌راحتی و کم‌هزینه توابع var را با نتایج آن‌ها جایگزین کنیم، که به ما اجازه می‌دهد به سؤالاتی مانند «آیا نتیجه این var یک رنگ است؟» پاسخ دهیم، همانطور که در مرحله 2 انجام دادیم.

فاز 2: رندر از بالا به پایین

برای فاز دوم، جهت را برعکس می کنیم. با گرفتن نتایج مسابقه از فاز 1، درخت را با پیمایش به ترتیب از بالا به پایین به HTML تبدیل می کنیم. برای هر گره بازدید شده، بررسی می کنیم که آیا مطابقت دارد یا خیر و اگر چنین است، رندر مربوطه تطبیق دهنده را فراخوانی می کنیم. ما با گنجاندن یک تطبیق و رندر پیش‌فرض برای گره‌های متن، از نیاز به مدیریت ویژه برای گره‌هایی که فقط حاوی متن هستند (مانند NumberLiteral "50٪") اجتناب می‌کنیم. رندرها به سادگی گره‌های HTML را خروجی می‌دهند، که وقتی کنار هم قرار می‌گیرند، نمایشی از ارزش ویژگی از جمله تزئینات آن را تولید می‌کنند.

فاز 2: رندر از بالا به پایین در درخت نحو.

برای درخت مثال، در اینجا ترتیبی که ارزش ویژگی ارائه می شود، آمده است:

  1. از فراخوانی تابع hsl دیدن کنید. مطابقت داشت، بنابراین رندر تابع رنگ را فراخوانی کنید. دو کار انجام می دهد:
    • مقدار واقعی رنگ را با استفاده از مکانیسم جایگزینی on-the-fly برای هر آرگومان var محاسبه می کند، سپس یک نماد رنگ ترسیم می کند.
    • به صورت بازگشتی فرزندان CallExpression را رندر می کند. این به طور خودکار از رندر کردن نام تابع، پرانتزها و کاماها که فقط متن هستند، مراقبت می کند.
  2. از اولین آرگومان فراخوانی hsl دیدن کنید. مطابقت دارد، بنابراین رندر زاویه را صدا کنید، که نماد زاویه و متن زاویه را ترسیم می کند.
  3. از آرگومان دوم که فراخوانی var است دیدن کنید. مطابقت دارد، بنابراین رندر var را صدا کنید، که خروجی زیر را دارد:
    • متن var( در ابتدا.
    • نام متغیر را با پیوندی به تعریف متغیر یا با رنگ متن خاکستری برای نشان دادن تعریف نشده بودن آن تزئین می کند. همچنین یک popover به متغیر اضافه می کند تا اطلاعاتی در مورد مقدار آن نشان دهد.
    • کاما و سپس به صورت بازگشتی مقدار بازگشتی را ارائه می دهد.
    • یک پرانتز بسته
  4. آخرین آرگومان فراخوانی hsl را مشاهده کنید. مطابقت نداشت، بنابراین فقط محتوای متنی آن را خروجی بگیرید.

آیا توجه کرده اید که در این الگوریتم، یک رندر به طور کامل نحوه رندر شدن فرزندان یک گره همسان را کنترل می کند؟ رندر بازگشتی کودکان فعال است. این ترفند همان چیزی است که مهاجرت گام به گام از رندر مبتنی بر regex به رندر مبتنی بر درخت نحو را فعال می کند. برای گره‌هایی که با یک regex-matcher قدیمی تطبیق داده می‌شوند، رندر مربوطه می‌تواند به شکل اصلی خود استفاده شود. در اصطلاح درخت نحوی، مسئولیت رندر کردن کل زیردرخت را بر عهده می‌گیرد و نتیجه آن (یک گره HTML) می‌تواند به طور تمیز به فرآیند رندر اطراف متصل شود. این به ما این امکان را داد که تطبیق‌دهنده‌ها و رندرها را به صورت جفتی پورت کنیم و آنها را یکی یکی عوض کنیم.

یکی دیگر از ویژگی‌های جالب رندرها که رندر فرزندان گره همسان خود را کنترل می‌کنند این است که به ما این توانایی را می‌دهد تا در مورد وابستگی بین نمادهایی که اضافه می‌کنیم استدلال کنیم. در مثال بالا، رنگ تولید شده توسط تابع hsl به وضوح به مقدار رنگ آن بستگی دارد. یعنی رنگ نشان داده شده توسط نماد رنگ به زاویه نشان داده شده توسط نماد زاویه بستگی دارد. اگر کاربر ویرایشگر زاویه را از طریق آن نماد باز کند و زاویه را تغییر دهد، اکنون می‌توانیم رنگ نماد رنگ را در زمان واقعی به‌روزرسانی کنیم:

همانطور که در مثال بالا می بینید، ما از این مکانیسم برای جفت کردن آیکون های دیگر نیز استفاده می کنیم، مانند color-mix() و دو کانال رنگ آن، یا توابع var که یک رنگ را از حالت اولیه خود برمی گرداند.

تاثیر عملکرد

هنگامی که به این مشکل برای بهبود قابلیت اطمینان و رفع مشکلات طولانی مدت پرداختیم، با توجه به اینکه شروع به اجرای یک تجزیه کننده کامل کردیم، انتظار رگرسیون عملکرد را داشتیم. برای آزمایش این، ما یک معیار ایجاد کرده‌ایم که حدود 3.5 هزار اعلان ویژگی را ارائه می‌کند و هر دو نسخه مبتنی بر regex و مبتنی بر تجزیه‌کننده را با 6x throttling در ماشین M1 نمایه می‌کند.

همانطور که انتظار داشتیم، رویکرد مبتنی بر تجزیه 27 درصد کندتر از رویکرد مبتنی بر regex برای آن مورد بود. روش مبتنی بر regex 11 ثانیه برای رندر و رویکرد مبتنی بر تجزیه کننده 15 ثانیه برای ارائه طول کشید.

با توجه به بردهایی که از رویکرد جدید به دست آوردیم، تصمیم گرفتیم با آن پیش برویم.

قدردانی

عمیق ترین قدردانی ما از سوفیا املیانوا و جسلین ین برای کمک ارزشمند آنها در ویرایش این پست است!

کانال های پیش نمایش را دانلود کنید

استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیش‌فرض خود در نظر بگیرید. این کانال‌های پیش‌نمایش به شما امکان می‌دهند به جدیدترین ویژگی‌های DevTools دسترسی داشته باشید، APIهای پلت‌فرم وب پیشرفته را آزمایش کنید و قبل از اینکه کاربرانتان این کار را انجام دهند، مشکلات موجود در سایت خود را پیدا کنید!

تماس با تیم Chrome DevTools

از گزینه های زیر برای بحث در مورد ویژگی ها و تغییرات جدید در پست یا هر چیز دیگری مربوط به DevTools استفاده کنید.

  • پیشنهاد یا بازخورد خود را از طریق crbug.com برای ما ارسال کنید.
  • با استفاده از گزینه های بیشتر ، مشکل DevTools را گزارش کنیدبیشتر > راهنما > گزارش مشکلات DevTools در DevTools.
  • توییت در @ChromeDevTools .
  • نظرات خود را در مورد ویدیوهای YouTube DevTools یا نکات DevTools در YouTube ما بنویسید.