Nemakej v korporátu a přidej se k nám!

Container Queries, aneb jak jsme polyfillovalli polyfill

21.06.2023

Vždy, když se prohlížeče naučí nějaké nové vychytávky, je to pro nás ve Webnode tak trochu hořkosladká událost. Ačkoliv bychom se nejradši hned plní nadšení vrhnuli na implementaci každé žhavé CSS/HTML/JS novinky do našeho WTF (Webnode Templating Framework), veškerou radost z "nové hračky" většinou hned přebije vědomí, že podpora napříč systémy a prohlížeči bude nejspíš ještě dost mizerná a nezanedbatelné skupině našich uživatelů by to v jejich zastaralých browserech prostě nefungovalo - minimálně tedy pokud by se nám nepodařilo najít nějaký šikovný polyfill, který by podporu legacy browserů za cenu pár stažených kilobajtů navíc vyřešil.

Container Queries (dříve také známé jako "Element Queries") jsme měli na seznamu "tohle chceme" věcí vlastně už hodně dlouho. Myšlenka mít možnost stylovat DOM elementy v závislosti na vlastnostech jejich rodičů místo neohrabaných Media Queries (stylování elementů v závislosti na šířce celého viewportu) už rezonuje frontendovou komunitou mnoho let, avšak až teprve nedávno se po letech různých pokusů a omylů povedlo vymyslet skutečně funkční specifikaci, prosadit ji do CSS standardů a implementovat do většiny moderních prohlížečů, s tím, že celou situaci pak ještě několik měsíců brzdil Firefox, který Container Queries začal oficiálně podporovat až od verze 110 (únor 2023).

Svým způsobem se to ukázalo jako ideální načasování vzhledem k datu našeho poslednímo hackathonu – podpora Firefoxu přišla právě včas na to, abychom Container Queries stihli zařadit na seznam hackathonových témat a konečně se do toho pořádně zavrtali. Odměna za úspěch by byla lákavá - zase o fous víc moderní codebase, ušetřené kilobajty na stylopisech i na JavaScriptu, tím pádem menší datové přenosy z CDN a obecně zase o něco rychlejší načítání stránek našich uživatelů. Ale co více - úspěšná implementace Container Queries by nám pomohla podstatně zjednodušit způsob, jakým pracujeme s obsahem umístěným ve sloupcích (z čehož převážně plynou ony výše zmíněné datové úspory) a krátce řečeno, dost by nám to občas ulehčilo život.

Pro lepší pochopení problému se sloupci - pokud si v šabloně umístí uživatel libovolný obsahový blok do sloupce, tento blok musí umět přizpůsobit svůj vzhled šířce daného sloupce, takže například fotogalerie se čtyřsloupcovým gridem se v úzkém sloupci přepne na dvousloupcový grid, stejně jako by to udělala při zobrazení na stejně širokém telefonu.

S Container Queries se taková situace dá vyřešit během chvilky a s prstem v nose, klasické Media Queries jsou na to ale už bohužel krátké. Ve Webnode jsme to nakonec vyřešili kouskem JavaScriptu, který v případě potřeby přepočítává velikost sloupců na stránce a pro každý sloupec dynamicky přidává či odebírá CSS třídy označující jeho šířku na základě předem definovaných breakpointů. Každý element, který musí podporovat změnu vzhledu ve sloupci, má pak svá speciální CSS pravidla pro responzivitu ve sloupcích navázaná právě na jména tříd, se kterými operuje výše zmíněný JavaScript. Ve výsledku tak musíme responzivní chování každého obsahového bloku definovat dvakrát - jednou pro celý viewport a jednou pro umístění bloku ve sloupci.  

grid-4: responzivita dle viewportu
grid-4: responzivita dle viewportu
grid-4: responzivita dle sloupců
grid-4: responzivita dle sloupců
grid-4: kompletní řešení s použitím container queries
grid-4: kompletní řešení s použitím container queries

Před startem hackathonu to celé vlastně vypadalo až moc optimisticky – statistiky z caniuse.com naznačovaly nativní podporu v prohlížečích pro cca 95% našich uživatelů, což je samo o sobě hodně krásné číslo. Zbylých 5% pak tvořili převážně uživatelé se staršími verzemi Firefoxu (< 110) a staršími verzemi Safari (< 16), které si rozhodně nemůžeme dovolit ignorovat, ale naštěstí se nám podařilo objevit polyfill pro Container Queries zaštítěný samotným Googlem, o kterém dokonce vyšel na css-tricks.com článek s povzbudivým titulkem "A New Container Query Polyfill That Just Works".

Prostě to funguje! ...akorát že vůbec.

Prvotní proof-of-concept implementace Container Queries do bloku galerie zabrala pár minut a všechno chvíli opravdu krásně fungovalo, tedy alespoň v Chrome. Člověk by si už už naivně myslel, že teď už prostě jen nasadíme polyfill (that just works!), otestujeme všechny prohlížeče, a pak místo nějakého velkého "hackování" prostě přepíšeme do Container Queries tolik kódu, co jen půjde, zatímco se budeme průběžně hydratovat pivem, a nakonec změříme výsledky a poplácáme se po ramenou, jakou krásnou novou technologii se nám to zase povedlo implementovat a jak nám to zase krásně vylepší metriky.

Takže jsme nasadili polyfill, otevřeli starší Firefox... a nic. Tam, kde měly být obrázky galerie úhledně uspořádané do čtyřsloupcového gridu, se objevila jedna tučná vertikální nudle s gigantickými obrázky přes celý web. Safari némlich to samé, po nějakém gridu ani stopy – a teď babo raď.

Po dlouhém bádání a zkoušení všeho možného a nemožného se nakonec podařilo viníka odhalit – v readme polyfillu je totiž někde naspodu malá, ale v našem kontextu velice důležitá poznámka, a to taková, že polyfill sice zvládne zpracovat jedině stylopisy umístěné na stejné doméně nebo styly umístěné přímo ve <style> elementu, se stylopisy stahovanými z "cédéenek" (a obecně z cross-domain lokací) si ale už neporadí. A jako na potvoru, veškeré naše externí stylopisy, které si šablona načítá, jsou samozřejmě umístěné jak jinak než na CDN serverech.

Už to prozměnu vypadalo, že nám tenhle drobný, leč velmi důležitý detail celé snažení s Container Queries zhatí a celou tuhle vychytávku pošle zpět na seznam přání. Veškeré další existující polyfilly se z povahy svého fungování ukázaly jako v našem kontextu nepoužitelné, ale pak se naštěstí objevil spásný nápad. Už teď přece využíváme při buildění našich stylopisů hromadu PostCSS pluginů, z nichž jeden automaticky vytahuje veškerý kód pro desktopová Media Queries do vlastních souborů, aby mobilní uživatelé stahovali méně dat - nešel by tedy zneužít i pro automatické zkopírování veškerých Container Queries pravidel do speciálního stylopisu, který by pak starší prohlížeče mohly pomocí JavaScriptu v případě potřeby načíst a přesypat přímo do nově vytvořeného <style> tagu, odkud by je už polyfill zvládl zpracovat?

Záhy se ukázalo, že takovýhle specifický use-case zmíněný třídící plugin ve svém původním stavu sice nezvládne, ale stačila malá úprava a první výsledky byly na světě. Dalším krokem tedy bylo ověření, zda se náš nově vytvořený speciální stylopis s Container Queries povede načíst JavaScriptem, nacpat do <style> tagu – a jestli to takhle nabastlené ten polyfill vůbec zvládne přechroustat. A světe div se – ono to nakonec opravdu zafungovalo. Sice jsme ještě zdaleka neměli hotovo, ale přesně v ten moment začalo být jasné, že to celé opravdu půjde dotáhnout do zdárného konce.

S novou vlnou motivace to pak už šlo celé v podstatě ráz na ráz. S pomocí trošky starého dobrého reverzního inženýrství se nám povedlo přetavit původní testovací úpravy existujícího PostCSS pluginu do zbrusu nového, na míru šitého pro naše potřeby, a na straně šablony jsme přidali kód, který způsobí, že se moderním prohlížečům nestáhne ani kilobajt navíc - jak polyfill, tak speciální stylopis čistě s Container Queries si stáhnou pouze starší prohlížeče bez jejich nativní podpory.

A tak jsme si tu s trochou nadsázky "vypolyfillovali polyfill" a tím otevřeli cestu k plné implementaci container queries do kódu Webnode šablon. Teď už zbýval tedy jen ten poslední, největší úkol - dostat to všude tam, kde to má smysl.  

Krutá realita implementace

Člověk by skoro i řekl, že dosavadní cesta za nasazením container queries byla už celkem dost trnitá, a samotná implementace by tedy už mohla jít relativně hladce, ale chyba lávky. Sice jsme tak trochu tušili, že nás to na pár místech ještě trochu potrápí, ale že nás čeká cesta všemi devíti kruhy Danteho pekla, to nás opravdu nenapadlo.

Bacha na ty EMka

Jeden z prvních šotků na nás vykoukl hned záhy, a co si budem - pokud bychom se nad tím trochu víc zamysleli, mohlo by nám to dojít už dřív. V rámci programování vlastních PostCSS pluginů jsme totiž automaticky implementovali i převod pixelových breakpointů v container queries na jednotky EM, a pak jsme se hrozně divili, že nám ty breakpointy nějak nesedí. Vysvětlení bylo nakonec zcela logické - při přepočítávání EM zpět na PX totiž hraje roli font-size parent elementu, takže pokud se měnila velikost textu parenta prvku, na kterém jsme měli definovaný kontejner, samozřejmě se měnila i pixelová hodnota breakpointu. Takže náš krásný převodní plugin vzal zase velmi rychle za své, a nejspíše se převodů na EM zbavíme i pro původní media queries, jelikož nám reálně použití EM nepřináší vůbec nic.

Tady je Krakonošovo

Další nečekanou facku nám daly CSS specifikace, ve kterých stojí, že element s definovaným kontejnerem definuje vlastní vykreslovací kontext - a tudíž tak nabývá skrytého potenciálu rozbít spoustu věcí, které doposud krásně fungovaly. Původní proof-of-concept testy samozřejmě s takovou lahůdkou nepočítaly (protože koho by bavilo zdlouhavě studovat specifikace, když se může rovnou pustit do experimentování), a tak jsme na nefunkční obtékání obrázků textem či na rozbitá fixně pozicovaná tlačítka v mobilním zobrazení košíku a detailu produktu koukali jako ta příslovečná husa do flašky. Dostudování dokumentace ale splnilo svůj účel a s trochou kreativity se nám to nakonec povedlo vyřešit za pomocí kaskádového chování kontejnerů (a jejich vypínání na místech, kde způsobovaly problémy).

Čtyři infarkty stačí, drahoušku

Pravidlo "nedělat releasy do produkce v pátek" má u nás dlouhodobou tradici, a v případě releasu container queries se opět ukázalo, proč ho doporučuje jedenáct z deseti programátorů, které ještě nezabil stres. Ač jsme měli funkcionalitu polyfillu na našich vývojových serverech otestovanou a potvrzenou, po releasu prvního velkého balíku container queries přišel hotový infarkt. Díky rozdílu v CORS pravidlech v produkčním prostředí (o kterém nikdo z frontendu neměl ani tušení) polyfill nezafungoval a nám se tak na starých prohlížečích rozsypaly šablony jako domeček z karet. Pokud by se to, co se dělo potom, mělo nazvat jménem filmu, "Rychle a Zběsile" by bylo jasný vítěz, a ač jsme měli záložní řešení na straně frontendu v podstatě předem hotové, při jeho nasazení se samozřejmě zkomplikovalo naprosto vše, co mohlo.

Další infarktový stav nám pak přivodilo zjištění, že se v rámci raketové implementace container queries sáhlo i na ty části našeho frameworku, na které nebyl v build procesu pro polyfillovací CSS stylopis doposud brán žádný ohled, a tak ač v moderních browserech vše běželo jako na drátkách, v polyfillované verzi se nám zatím nepozorovaně zcela rozpadl typografický systém. Snad jen úplným zázrakem se nám nakonec podařilo vymyslet rychlé a schůdné řešení fungující v kontextu našeho build procesu, bez velkého překopání celého systému jakým funguje náš "polyfill polyfillu", které by se tentokrát navíc už neobešlo bez zásahů aplikačního týmu.

Ani to vše očividně nestačilo, a tak přišla ještě poslední malá třešinka na dortu - díky celé téhle eskapádě se totiž navíc ještě ukázalo, že náš milý polyfill má i další (tentokrát nezdokumentovaná) omezení, například další takový malý, ale velice podstatný detail, že nezvládne pracovat s vícenásobnými definicemi kontejnerů na jednom elementu. Řešení sice tentokrát zabralo chvilku času a nakonec stačilo doslova upravit jen pár řádků kódu a bylo po problému, ale malý infakrt z toho byl stejně.

Konec dobrý, všechno dobré

I přes všechny překážky se dobrá věc nakonec zadařila a do plného nasazení Container Queries nám už naštěstí nic dalšího vidle nehodilo. V průběhu to sice párkrát vypadalo opravdu nahnutě a místy to celé dost zavánělo černými myšlenkami na kompletní revert zpět na původní řešení s Media Queries a s JavaScriptem, ale o to sladší pak byl ten pocit vítězství, když se to nakonec povedlo celé rozběhat se vším všudy.

Znovu se nám však potvrdilo, jak důležité jsou automatizované testy. Jen díky nim se podařilo ty největší průšvihy zachytit takřka okamžitě po releasu a opravit ještě dřív, než si jich stihli všimnout naši uživatelé. Je již však nad slunce jasné, že jednou z dalších výzev pro nás bude posunout to, jak náš frontend testujeme, ještě mnohem dál, abych si takovéhle stresy (snad) jednou mohli nadobro odpustit :)

Za Webnode Pavel Antolík