فراتر از عبارات معمولی: تقویت تجزیه ارزش 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 استفاده کنید.

،

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 استفاده کنید.

،

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 ، این به معنای تجزیه کننده برای زبانهای بدون متن است. در حقیقت ، چنین سیستم تجزیه کننده ای در حال حاضر در Codebase Devtools وجود داشته است: Codemirror's Lezer ، که پایه و اساس آن است ، به عنوان مثال ، برجسته سازی نحو در Codemirror ، ویرایشگر شما در پانل منابع . تجزیه و تحلیل CSS Lezer به ما اجازه می دهد درختان نحوی (غیر انتزاعی) را برای قوانین CSS تولید کنیم و برای استفاده ما آماده بود. پیروزی.

یک درخت نحوی برای مقدار خاصیت "HSL (177DEG 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 را با یک متغیر وار کشف می کنیم. برای چنین تماسهایی ما عمدتاً می خواهیم دو کار انجام دهیم:
    • اعلامیه متغیر را جستجو کرده و مقدار آن را محاسبه کنید و به ترتیب یک لینک و یک پاپور به نام متغیر اضافه کنید تا به آنها متصل شوید.
    • اگر مقدار محاسبه شده یک رنگ است ، تماس را با یک نماد رنگی تزئین کنید. در واقع یک چیز سوم وجود دارد ، اما بعداً در مورد آن صحبت خواهیم کرد.
  3. hsl(177deg var(--saturation, 100%) 50%) : در آخر ، ما با بیان تماس برای عملکرد hsl مطابقت داریم تا بتوانیم آن را با نماد رنگ تزئین کنیم.

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

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

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

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

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

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

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

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

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

تاثیر عملکرد

هنگام غواصی در این مشکل برای بهبود قابلیت اطمینان و رفع مشکلات طولانی مدت ، ما انتظار داشتیم که با توجه به اینکه شروع به اجرای یک تجزیه کننده کاملاً پرمخاطب کردیم ، از رگرسیون عملکردی انتظار داشتیم. برای آزمایش این کار ، ما معیار ایجاد کرده ایم که حدود اعلامیه املاک 3.5K را ارائه می دهد و هر دو نسخه مبتنی بر Regex و مبتنی بر تجزیه و تحلیل را با 6 برابر پرتاب بر روی دستگاه M1 پروفایل می کند.

همانطور که انتظار داشتیم ، رویکرد مبتنی بر تجزیه و تحلیل 27 ٪ کندتر از رویکرد مبتنی بر Regex برای آن مورد بود. رویکرد مبتنی بر Regex 11s را برای ارائه و رویکرد مبتنی بر تجزیه و تحلیل به طول انجامید تا 15s را برای ارائه به دست آورد.

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

قدردانی

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

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

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

با تیم Chrome DevTools تماس بگیرید

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