Pe 2 februarie 2017, Ann Taylor a scris articolul 12 Things We Need to See From WordPress Page Builders in 2017 + Who Already Gets It Right pe CodeInWP. Am îmbunătățit continuu Page Builder Sandwich de mai bine de un an: facem cercetare, ne ascultăm clienții, așa că ne gândeam că Page Builder Sandwich va ajunge în articolul „Who Got it Right” de pe CodeInWP. Totuși, nu am reușit, din cauza performanței. 
Așa că am întrebat-o pe Ann despre asta, iar ea a răspuns:
…M-am jucat puțin cu o versiune gratuită. Este foarte promițătoare: o mulțime de module, o interfață destul de intuitivă, o experiență WYSIWYG excelentă, totul se trage ușor. Cu toate acestea, când am avut în jur de 10 module pe pagină, a devenit greu să continui lucrul din cauza vitezei reduse de editare…
Așadar, conform spuselor ei, Page Builder Sandwich are probleme de performanță. Și cum ascultăm întotdeauna feedbackul, am verificat situația.
Testarea performanței
Aveam nevoie de o modalitate de a verifica performanța PBS; din moment ce PBS rulează în mare parte în browser prin Javascript, profilerul Timeline din Chrome a fost instrumentul potrivit pentru această sarcină. Profilerul timeline poate identifica și măsura procesele care au loc într-o pagină web în timpul execuției, ceea ce este perfect pentru găsirea blocajelor de viteză.
Ne-am gândit că ar trebui să testăm mai întâi cei mai simpli pași – mișcarea mouse-ului peste mai multe elemente și rânduri. Simplu, nu? Eram încrezători că nu vom întâmpina probleme de performanță cu ceva atât de simplu. Din păcate, ne-am înșelat.
După ce am apăsat butonul de înregistrare, am mișcat mouse-ul timp de aproximativ 2 secunde pe ecran, apoi am apăsat butonul de oprire. Iată cronologia rezultată din profiler:
Și iată. Este plin ochi de blocaje de performanță!
Se întâmplă o mulțime de lucruri în grafic, dar problema cea mai evidentă este că există o mulțime de zone mov în el (ceea ce nu ar trebui să fie cazul). În plus, aceste zone mov sunt vizibil foarte late și toate par să atingă vârful graficului. Veți observa, de asemenea, că graficul este mereu plin și că zonele mov aproape că nu coboară niciodată la zero. Se pare că browserul este mereu ocupat făcând ceva și aproape că nu se odihnește niciodată.
Dar ce înseamnă cu adevărat toate acele zone mov?
Profilerul a fost suficient de amabil să ne spună că se numesc reflows. Iată definiția a ceea ce este un reflow de pe site-ul Google Developer:
Reflow este numele procesului din browserul web de recalculare a pozițiilor și geometriilor elementelor din document.. reflow este o operațiune care blochează utilizatorul în browser.
Așadar, asta înseamnă că mișcarea mouse-ului în PBS declanșează o tonă de reflow-uri. Ce e și mai rău este că fiecare reflow pune browserul pe pauză pentru o clipă, astfel încât acesta să își poată finaliza calculele. Mai multe reflow-uri înseamnă mai multe pauze ale browserului, iar experiența devine și mai sacadată. În termeni mai simpli, multe zone mov = rău.
Deci se pare că Ann a avut dreptate: chiar avem un blocaj uriaș de performanță, așa că echipa noastră s-a apucat imediat de treabă pentru a-l remedia pe loc.
Rezolvarea problemei noastre de reflow / sacadare
Înainte de orice altceva, trebuia să facem niște cercetări pentru a afla ce anume cauzează acest reflow în primul rând. Am descoperit că, aparent, o mulțime de lucruri obișnuite în Javascript pot determina browserul să provoace un reflow.
Aproape orice funcție pe care o puteți folosi pentru a obține lățimea, înălțimea sau poziția unui element DOM poate declanșa un reflow. Paul Irish a scris o listă extinsă a tuturor proprietăților și funcțiilor din Javascript care pot face acest lucru. Vestea proastă este că Page Builder Sandwich folosește o mulțime dintre aceste proprietăți și funcții, toate provocând reflow:
- elem.getBoundingClientRect()
- window.getComputedStyle()
- window.scrollY
- și multe altele…
Soluția #1: Memorarea în cache a proceselor costisitoare de reflow
Cea mai evidentă soluție este să NU folosim acele proprietăți și funcții. Cu toate acestea, PBS se bazează masiv pe ele și nu ne putem lipsi de ele.
Soluția care ne-a rămas este să reducem cât mai mult posibil apelurile către acele funcții. Partea grea a fost că am folosit acele proprietăți și funcții de foarte multe ori în diferite părți ale PBS și, de cele mai multe ori, le-am folosit pe același element DOM de mai multe ori. Pentru a rezolva acest lucru, am creat propriul mecanism de memorare în cache pentru fiecare proprietate/funcție problematică.
De exemplu, funcția getBoundingClientRect ne oferă măsurătorile curente în pixeli pentru top, left, width, height, right și bottom ale unui element DOM. Folosim această funcție tot timpul pentru a calcula locația unde să desenăm contururile elementelor în PBS. Iată funcția de caching pe care am creat-o pentru asta:
// Cache: Dreptunghiurile noastre de client sunt păstrate aici, le putem folosi pentru a le goli mai târziu.
var elemsWithBoundingRects = [];
window.pbsGetBoundingClientRect = function( element ) {
// Verificăm dacă am obținut deja dreptunghiul de client anterior.
if ( ! element._boundingClientRect ) {
// Dacă nu, îl obținem și îl salvăm pentru utilizare ulterioară.
element._boundingClientRect = element.getBoundingClientRect();
elemsWithBoundingRects.push( element );
}
return element._boundingClientRect;
};
În loc să apelăm getBoundingClientRect , am folosit acum pbsGetBoundingClientRect . Acum, de fiecare dată când este interogat dreptunghiul de client, salvăm acea valoare pentru referințe viitoare, limitând astfel numărul de declanșatori de reflow.
De asemenea, aveam nevoie de o modalitate de a goli memoria cache atunci când dreptunghiurile de client salvate devin invalidate. Valorile dreptunghiului de client salvat se modifică atunci când utilizatorul derulează pagina sau adaugă/modifică/elimină elemente din ecran. Așa că am creat o funcție de golire a memoriei cache pe care o putem apela oricând avem nevoie de valori proaspete pentru dreptunghiul de client:
var clearClientRects = function() {
var i;
for ( i = 0; i < elemsWithBoundingRects.length; i++ ) {
if ( elemsWithBoundingRects[ i ] ) {
elemsWithBoundingRects[ i ]._boundingClientRect = null;
}
}
elemsWithBoundingRects = [];
};
Am aplicat același mecanism de caching și pentru alte apeluri de funcții costisitoare pe care le folosim de obicei.
Soluția #2: Gruparea citirilor și a scrierilor folosind FastDom
Implementarea soluției #1 a sporit performanța PBS, dar nu a fost suficientă.
Căutând puțin mai mult pe Google, am dat peste un articol scris de Wilson Page despre prevenirea fenomenului de „layout thrashing”. Wilson spune:
Layout Thrashing (reflow) apare atunci când JavaScript scrie, apoi citește violent din DOM, de mai multe ori, provocând reflow-uri ale documentului.
Aparent, nu este suficient să vă limitați apelurile la proprietăți și funcții specifice care declanșează reflow. Când efectuați multiple „citiri” și „scrieri” succesive în DOM, acest lucru declanșează, de asemenea, un reflow în browser. Iată un scenariu tipic, preluat direct de pe site-ul lui Wilson:
// Citire
var h1 = element1.clientHeight;
// Scriere (invalidează layout-ul)
element1.style.height = (h1 * 2) + 'px';
// Citire (declanșează reflow)
var h2 = element2.clientHeight;
// Scriere (invalidează layout-ul)
element2.style.height = (h2 * 2) + 'px';
// Citire (declanșează reflow)
var h3 = element3.clientHeight;
// Scriere (invalidează layout-ul)
element3.style.height = (h3 * 2) + 'px';
// Reflow-ul documentului la finalul cadrului
Exemplul de mai sus declanșează 3 reflow-uri deoarece dimensiunile întregii pagini sunt invalidate după o scriere. Soluția sugerată pentru aceasta este reordonarea codului pentru a aduna la un loc toate citirile și scrierile, astfel încât totul să declanșeze doar 1 reflow în browser:
// Citire
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;
// Scriere (invalidează layout-ul)
element1.style.height = (h1 * 2) + 'px';
element2.style.height = (h2 * 2) + 'px';
element3.style.height = (h3 * 2) + 'px';
// Reflow-ul documentului la finalul cadrului
Fragmentul de mai sus face ca rezolvarea problemei să pară ușoară. Dar, în realitate, în PBS, citirea și scrierea elementelor care declanșează reflow au loc în multe locuri diferite și în multe momente diferite pe parcursul ciclului de viață al PBS. Reorganizarea a mii de linii de cod Javascript nu este fezabilă.
Din fericire pentru noi, Wilson a oferit o soluție și în articolul său. El a creat un mic script utilitar pentru a ajuta la gruparea acelor citiri și scrieri, numit FastDom.
Iată o parte din codul din Page Builder Sandwich care folosește FastDom și include, de asemenea, Soluția #1 de mai sus:
// Gruparea citirilor din alte fire de execuție.
fastdom.measure( function() {
// Folosim dreptunghiul de client salvat în cache.
var rect = window.pbsGetBoundingClientRect( elem );
// Gruparea scrierilor din alte fire de execuție.
fastdom.mutate( function() {
elem.style.top = ( rect.top + window.pbsScrollY() ) + 'px';
elem.style.height = rect.height + 'px';
elem.style.left = rect.left + 'px';
elem.style.width = rect.width + 'px';
}.bind( this ) );
}.bind( this ) );
Ceea ce face FastDom este că grupează toate apelurile fastdom.measure și toate apelurile fastdom.mutate și le execută asincron, grupând practic toate citirile și scrierile la un loc pentru a diminua toate reflow-urile din browser.
Soluția #3: Accesarea obiectelor de stil este costisitoare
De asemenea, am descoperit cu ajutorul jsPerf că unele funcții de obținere a stilurilor erau mai rapide decât altele.
În PBS, avem de obicei nevoie să obținem valoarea curentă a unui stil specific al unui element. De exemplu, în mod normal folosim acest cod pentru a obține marginea superioară (top margin):
var styles = window.getComputedStyle( elem );
var marginTop = styles.marginTop;
Dar am descoperit că procedând astfel este mult mai rapid:
var marginTop = elem.style.marginTop;
Totuși, elem.style.marginTop și getComputedStyle sunt foarte diferite. Prima ne oferă doar stilul inline al elementului și nu ia în considerare foile de stil; iar a doua ia în considerare atât stilurile inline, cât și foile de stil.
Mai mult decât atât, chiar și atunci când rezultatul getComputedStyle a fost memorat în cache, a fost în continuare foarte lent. După cum s-a dovedit, accesarea obiectului de stil în sine este procesul lent.
Din fericire pentru noi, PBS adaugă stiluri inline la elemente și, din moment ce stilurile inline au prioritate (în majoritatea cazurilor) față de foile de stil, vor exista cazuri în care obținerea valorii stilului inline va fi suficientă. Deci, dacă combinăm cele două metode de mai sus, putem obține un cod care rulează mult mai rapid:
var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
var styles = window.getComputedStyle( elem );
marginTop = styles.marginTop;
}
Rezultatele îmbunătățirilor aduse codului
Cele 3 soluții ale noastre de mai sus au necesitat multă cercetare, testare și implementare efectivă (și au amânat lansarea funcției noastre de Fonturi), dar în final totul a meritat.
Revenind la testul nostru inițial de mișcare a mouse-ului pe ecran timp de 2 secunde. Iată vechea cronologie inestetică/sacadată:
Și iată cum arată noua noastră cronologie după îmbunătățirile de performanță:
După cum puteți vedea, există mai puține elemente mov în grafic. Și există o diferență vizibilă în cantitatea de procese efectuate de browser. Elementele dreptunghiulare din partea de jos sunt, de asemenea, mai subțiri, ceea ce înseamnă că procesele sunt rulate mai rapid decât înainte.
Dacă comparați cantitatea de zone mov înalte/late din graficele vechi și noi, vorbim despre o creștere uriașă de performanță de 200%! De asemenea, observăm creșteri de performanță la toate capitolele. Deloc rău!
Concluzii și învățăminte
Sincer să fim, nu credeam că PBS are probleme de performanță înainte ca Ann să aducă vorba despre asta. În principal pentru că foloseam calculatoare orientate spre dezvoltare, iar blocajele de performanță nu erau atât de evidente. Privind în urmă, verificarea performanței ar fi trebuit să fie una dintre prioritățile de top. Dar cel puțin acum, începând cu versiunea 4.3, această problemă a fost rezolvată. 
Pentru cei care doresc să îmbunătățească performanța propriului plugin WordPress, mai ales dacă folosiți mult Javascript, iată concluziile mele din această experiență:
- Limitați/memorați în cache apelurile către proprietățile/funcțiile care declanșează reflow. Folosiți acest ghid: What forces layout/reflow. The comprehensive list.
- Grupați „citirile” și „scrierile” stilurilor DOM. Folosiți FastDom pentru a face acest lucru în locul vostru.
- Folosiți profilerul Timeline din Chrome și asigurați-vă că aveți cât mai puține reflow-uri (elemente mov) posibil.