2017 оны 2-р сарын 2-нд Энн Тейлор CodeInWP дээр 12 Things We Need to See From WordPress Page Builders in 2017 + Who Already Gets It Right нийтлэлийг бичжээ. Бид Page Builder Sandwich-ийг жилийн турш тасралтгүй сайжруулж ирсэн: бид судалгаагаа хийж, хэрэглэгчдээ сонсдог тул Page Builder Sandwich нь CodeInWP-ийн «Хэн зөв хийсэн бэ» нийтлэлд багтана гэж бодож байлаа. Гэвч гүйцэтгэлийн асуудлаас болоод орж чадсангүй. 
Тиймээс бид Эннээс энэ талаар асуухад тэрээр ингэж хариулав:
…Би үнэгүй хувилбарыг нь бага зэрэг туршиж үзсэн. Энэ нь их ирээдүйтэй харагдаж байна: маш олон модуль, ойлгомжтой UI, гайхалтай WYSIWYG туршлага, бүх зүйлийг чирэхэд хялбар. Гэсэн хэдий ч хуудсан дээр 10 орчим модуль байршуулахад засварлах хурд удааширч, үргэлжлүүлэн ажиллахад хүндрэлтэй болсон…
Түүний хэлснээр бол Page Builder Sandwich гүйцэтгэлийн асуудалтай байжээ. Бид санал хүсэлтийг үргэлж сонсдог тул үүнийг шалгаж үзсэн юм.
Гүйцэтгэлийг шалгах нь
Бидэнд PBS-ийн гүйцэтгэлийг шалгах арга зам хэрэгтэй байв; PBS нь ихэвчлэн Javascript-ээр дамжин хөтөч дээр ажилладаг тул Chrome-ийн Timeline profiler энэ ажилд яг тохирсон хэрэгсэл байлаа. Timeline profiler нь веб хуудас ажиллах явцад ямар процессууд явагдаж байгааг тодорхойлж, хэмжиж чаддаг нь хурдны гацааг олоход төгс тохирно.
Бид эхлээд хамгийн энгийн алхмуудыг туршиж үзэх хэрэгтэй гэж бодсон — хулганаа олон элемент, мөрүүдийн дээгүүр гүйлгэх. Энгийн байгаа биз? Бид ийм энгийн зүйл дээр гүйцэтгэлийн асуудал гарахгүй гэдэгт итгэлтэй байсан. Харамсалтай нь бид буруу боджээ.
Бид бичлэг хийх товчийг дараад, дэлгэц дээр хулганаа ойролцоогоор 2 секунд гүйлгэж, дараа нь зогсоох товчийг дарсан. Profiler-аас гарсан цагийн хуваарийн үр дүн энд байна:
Гэтэл хараач. Гүйцэтгэлийн гацаануудаар дүүрэн байна!
График дээр маш олон зүйл болж байгаа боловч хамгийн илэрхий асуудал нь нил ягаан хэсгүүд маш олон байгаа явдал юм (уг нь тийм байх ёсгүй). Дээрээс нь эдгээр нил ягаан хэсгүүд нь нүдэнд харагдахуйц маш өргөн бөгөөд бүгд графикийн дээд цэгт хүрсэн мэт харагдаж байна. Мөн график нь үргэлж дүүрэн байх бөгөөд нил ягаан хэсгүүд бараг хэзээ ч тэг болж буурахгүй байгааг та анзаарах болно. Хөтөч үргэлж ямар нэгэн зүйл хийж завгүй байж, бараг хэзээ ч амардаггүй мэт харагдаж байна.
Гэхдээ энэ бүх нил ягаан хэсгүүд үнэндээ ямар учиртай вэ?
Profiler нь тэдгээрийг reflows гэж нэрлэдэг болохыг бидэнд харуулсан. Google Developer сайт:
Reflow гэдэг нь баримт бичиг дэх элементүүдийн байрлал, геометрийг дахин тооцоолох вэб хөтчийн процессын нэр юм.. reflow нь хөтөч дээр хэрэглэгчийн үйлдлийг саатуулдаг үйлдэл юм.
Өөрөөр хэлбэл, PBS дотор хулганаа хөдөлгөхөд үй олон reflow өдөөгддөг гэсэн үг юм. Хамгийн муу нь reflow бүр тооцооллоо дуусгахын тулд хөтчийг түр зуур зогсоодог. Илүү олон reflow байна гэдэг нь хөтөч илүү олон удаа гацна гэсэн үг бөгөөд ашиглахад улам бүр гацалттай (janky) болдог. Энгийн үгээр хэлбэл, олон нил ягаан хэсэг = муу.
Тэгэхээр Энний зөв байж: бидэнд үнэхээр гүйцэтгэлийн том гацаа байсан тул манай баг үүнийг шууд засахаар шуурхай ажилдаа оров.
Reflow / Гацалтын асуудлыг шийдвэрлэх нь
Юуны түрүүнд бид энэхүү reflow-ийг юу үүсгээд байгааг олж мэдэхийн тулд судалгаа хийх шаардлагатай байв. Javascript дээрх маш олон түгээмэл үйлдлүүд хөтөч дээр reflow үүсгэхэд хүргэдэг болохыг бид олж мэдсэн.
DOM элементийн өргөн, өндөр эсвэл байршлыг авахад ашиглаж болох бараг бүх функц reflow-ийг өдөөж болно. Пол Айриш үүнийг үүсгэдэг Javascript-ийн бүх шинж чанар, функцүүдийн дэлгэрэнгүй жагсаалтыг бичсэн байдаг. Харамсалтай нь Page Builder Sandwich эдгээр шинж чанар, функцүүдийн ихэнхийг ашигладаг бөгөөд бүгд reflow үүсгэдэг:
- elem.getBoundingClientRect()
- window.getComputedStyle()
- window.scrollY
- болон бусад олон…
Шийдэл #1: Өндөр зардалтай Reflow процессуудыг кэшлэх
Хамгийн ойлгомжтой шийдэл бол эдгээр шинж чанар, функцүүдийг ашиглахгүй байх явдал юм. Гэсэн хэдий ч PBS тэдгээрээс ихээхэн хамаардаг бөгөөд тэдэнгүйгээр ажиллах боломжгүй байв.
Бидэнд үлдсэн шийдэл нь тэдгээр функцүүдийг дуудах давтамжийг аль болох цөөлөх байв. Энд хэцүү зүйл нь бид эдгээр шинж чанар, функцүүдийг PBS-ийн өөр өөр хэсгүүдэд маш олон удаа ашигласан байсан бөгөөд ихэнх тохиолдолд нэг л DOM элемент дээр олон дахин дуудаж байсан юм. Үүнийг засахын тулд бид асуудал үүсгэж буй шинж чанар/функц бүрт зориулж өөрсдийн кэшлэх механизмыг бүтээсэн.
Жишээлбэл, getBoundingClientRect функц нь DOM элементийн одоогийн дээд, зүүн, өргөн, өндөр, баруун, доод пикселийн хэмжилтийг өгдөг. Бид PBS доторх элементүүдийн хүрээг зурах байршлыг тооцоолохдоо энэ функцийг байнга ашигладаг. Үүнд зориулж бидний бүтээсэн кэшлэх функц энд байна:
// Кэш: Манай client rect-үүд энд хадгалагдана, дараа нь цэвэрлэхэд ашиглаж болно.
var elemsWithBoundingRects = [];
window.pbsGetBoundingClientRect = function( element ) {
// Өмнө нь client rect-ийг авсан эсэхийг шалгах.
if ( ! element._boundingClientRect ) {
// Хэрэв үгүй бол, аваад дараа ашиглахаар хадгалах.
element._boundingClientRect = element.getBoundingClientRect();
elemsWithBoundingRects.push( element );
}
return element._boundingClientRect;
};
getBoundingClientRect-ийг дуудахын оронд бид одоо pbsGetBoundingClientRect-ийг ашигладаг болсон. Одоо client rect-ийг лавлах бүрт бид уг утгыг дараа ашиглахаар хадгалж, улмаар reflow өдөөгдөх тоог хязгаарлаж байна.
Мөн хадгалсан client rect хүчингүй болох үед кэшийг цэвэрлэх арга бидэнд хэрэгтэй болсон. Хэрэглэгч хуудсыг гүйлгэх эсвэл дэлгэц дээрх элементүүдийг нэмэх/өөрчлөх/устгах үед хадгалсан client rect-ийн утгууд өөрчлөгддөг. Тиймээс шинэ client rect утга шаардлагатай болсон үед дуудаж болох кэш цэвэрлэх функцийг бид бүтээв:
var clearClientRects = function() {
var i;
for ( i = 0; i < elemsWithBoundingRects.length; i++ ) {
if ( elemsWithBoundingRects[ i ] ) {
elemsWithBoundingRects[ i ]._boundingClientRect = null;
}
}
elemsWithBoundingRects = [];
};
Бид байнга ашигладаг бусад өндөр зардалтай функцийн дуудлагууд дээр ч мөн энэ кэшлэх механизмыг хэрэгжүүлсэн.
Шийдэл #2: FastDom ашиглан Унших & Бичих үйлдлүүдийг бүлэглэх
Шийдэл #1-ийг хэрэгжүүлснээр PBS-ийн гүйцэтгэл сайжирсан ч энэ нь хангалтгүй байв.
Google-ээс дахин бага зэрэг хайсны эцэст бид Уилсон Пейжийн “Layout thrashing”-аас сэргийлэх тухай нийтлэлтэй таарав. Уилсон ингэж хэлжээ:
Layout Thrashing (reflow) нь JavaScript нь DOM дээр олон дахин огцом бичиж, дараа нь уншиж, баримт бичгийн reflow-ийг үүсгэх үед тохиолддог.
Зөвхөн reflow өдөөдөг тодорхой шинж чанар, функцүүдийн дуудлагыг хязгаарлах нь хангалтгүй байжээ. Та DOM дээр олон дараалсан “унших” болон “бичих” үйлдэл хийх үед энэ нь мөн хөтчийн reflow-ийг өдөөдөг байна. Уилсоны сайтаас шууд авсан нийтлэг хувилбар энд байна:
// Унших
var h1 = element1.clientHeight;
// Бичих (бүтцийг хүчингүй болгоно)
element1.style.height = (h1 * 2) + 'px';
// Унших (reflow өдөөнө)
var h2 = element2.clientHeight;
// Бичих (бүтцийг хүчингүй болгоно)
element2.style.height = (h2 * 2) + 'px';
// Унших (reflow өдөөнө)
var h3 = element3.clientHeight;
// Бичих (бүтцийг хүчингүй болгоно)
element3.style.height = (h3 * 2) + 'px';
// Фреймийн төгсгөлд баримт бичигт reflow хийгдэнэ
Дээрх жишээ нь 3 удаа reflow өдөөдөг бөгөөд учир нь бичих үйлдлийн дараа бүхэл бүтэн хуудасны хэмжээсүүд хүчингүй болдог. Үүнийг засах санал болгож буй шийдэл нь бүхэлдээ хөтчийн ердөө 1 reflow-ийг өдөөхийн тулд бүх унших болон бичих үйлдлүүдийг хамтад нь багцлах байдлаар кодыг дахин дараалалд оруулах явдал юм:
// Унших
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;
// Бичих (бүтцийг хүчингүй болгоно)
element1.style.height = (h1 * 2) + 'px';
element2.style.height = (h2 * 2) + 'px';
element3.style.height = (h3 * 2) + 'px';
// Фреймийн төгсгөлд баримт бичигт reflow хийгдэнэ
Дээрх кодын хэсэг нь асуудлыг шийдэхэд хялбар харагдуулж байна. Гэвч бодит байдал дээр PBS дотор reflow үүсгэдэг зүйлсийг унших, бичих нь PBS-ийн ажиллах явцад маш олон өөр газар, өөр өөр цагт тохиолддог. Олон мянган мөр бүхий Javascript кодыг дахин зохион байгуулах нь боломжгүй зүйл байв.
Бидний аз болоход, Уилсон нийтлэлдээ мөн шийдлийг өгсөн байв. Тэрээр эдгээр унших, бичих үйлдлүүдийг хамтад нь багцлахад туслах FastDom хэмээх жижиг туслах скрипт бүтээсэн байв.
Page Builder Sandwich-ээс FastDom-ийг ашигласан бөгөөд дээрх Шийдэл #1-ийг багтаасан зарим код энд байна:
// Бусад урсгалын унших үйлдлүүдийг хамтад нь багцлах.
fastdom.measure( function() {
// Манай кэшлэсэн client rect-ийг ашиглах.
var rect = window.pbsGetBoundingClientRect( elem );
// Бусад урсгалын бичих үйлдлүүдийг хамтад нь багцлах.
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 ) );
FastDom-ийн хийдэг зүйл бол бүх fastdom.measure дуудлага болон бүх fastdom.mutate дуудлагыг нэгтгэн асинхроноор гүйцэтгэж, бүх унших, бичих үйлдлүүдийг үр дүнтэй бүлэглэснээр хөтөч дээрх reflow-ийг багасгадаг явдал юм.
Шийдэл #3: Style объектод хандах нь өндөр зардалтай
Мөн jsPerf-ийн тусламжтайгаар зарим стиль авах функцүүд бусдаасаа илүү хурдан болохыг бид олж мэдсэн.
PBS дээр бид ихэвчлэн элементийн тодорхой стилийн одоогийн утгыг авах шаардлагатай болдог. Жишээлбэл, бид дээд зайг (top margin) авахын тулд ихэвчлэн үүнийг ашигладаг:
var styles = window.getComputedStyle( elem );
var marginTop = styles.marginTop;
Гэвч ингэж хийх нь хамаагүй хурдан болохыг бид олж мэдсэн:
var marginTop = elem.style.marginTop;
Гэсэн хэдий ч elem.style.marginTop болон getComputedStyle нь хоорондоо маш өөр юм. Эхнийх нь зөвхөн тухайн элементийн inline стилийг өгдөг бөгөөд ямар ч stylesheet-ийг тооцдоггүй; харин хоёр дахь нь inline стиль болон stylesheet-ийн аль алиныг нь тооцдог.
Түүнчлэн getComputedStyle -ийн үр дүнг кэшилсэн байсан ч маш удаан хэвээр байв. Мэдэгдэж байгаагаар, стиль объектод өөрт нь хандах нь удаан процесс байжээ.
Бидний аз болоход, PBS нь элементүүдэд inline стиль нэмдэг ба inline стиль нь stylesheet-ээс (ихэнх тохиолдолд) давуу эрхтэй байдаг тул inline стилийн утгыг авах нь хангалттай байх тохиолдлууд бий. Тиймээс дээрх хоёр аргыг хослуулснаар бид хамаагүй хурдан ажиллах кодыг гаргаж авч чадна:
var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
var styles = window.getComputedStyle( elem );
marginTop = styles.marginTop;
}
Кодыг сайжруулсны үр дүн
Дээрх манай 3 шийдэл нь маш их судалгаа, туршилт, бодит хэрэгжилтийг шаардсан (мөн манай Fonts боломжийг гаргах хугацааг хойшлуулсан) боловч эцэст нь энэ бүхэн үнэ цэнтэй байлаа.
Дэлгэц дээгүүр хулганаа 2 секунд хөдөлгөсөн анхны туршилт руугаа буцъя. Хуучин муухай/гацалттай цагийн хуваарь энд байна:
Гүйцэтгэлийг сайжруулсны дараа манай шинэ цагийн хуваарь ямар харагдаж байгааг энд харуулав:
Харж байгаачлан график дээр нил ягаан зүйлс багассан байна. Мөн хөтчийн гүйцэтгэж буй процессуудын тоо хэмжээнд мэдэгдэхүйц ялгаа гарчээ. Доод талын тэгш өнцөгтүүд нь илүү нарийн болсон байгаа нь процессууд өмнөхөөсөө илүү хурдан ажиллаж байна гэсэн үг юм.
Хуучин болон шинэ график дээрх өндөр/өргөн нил ягаан хэсгүүдийн хэмжээг харьцуулж үзвэл бид гүйцэтгэл даруй 200% өссөнийг харж байна! Мөн бид нийт хэмжээнд гүйцэтгэл нэмэгдсэнийг харж байна. Огт муугүй шүү!
Дүгнэлт ба сургамж
Үнэнийг хэлэхэд, Энн энэ талаар дурдахаас өмнө бид PBS-ийг гүйцэтгэлийн асуудалтай гэж бодоогүй байсан. Учир нь бид хөгжүүлэлтийн өндөр хүчин чадалтай компьютер ашиглаж байсан тул гүйцэтгэлийн гацаа тийм ч тод анзаарагдахгүй байв. Эргээд харахад гүйцэтгэлийг шалгах нь хамгийн чухал асуудлуудын нэг байх ёстой байжээ. Гэхдээ ядаж одоо 4.3 хувилбараас эхлэн үүнийг шийдсэн. 
Өөрсдийн WordPress нэмэлт өргөтгөлийн (plugin) гүйцэтгэлийг сайжруулахыг хүсэж буй хүмүүст, ялангуяа хүнд Javascript ашиглаж байгаа бол энэхүү туршлагаас авсан миний дүгнэлтүүдийг хуваалцъя:
- Reflow өдөөдөг шинж чанар/функцүүдийн дуудлагыг хязгаарлах/кэшлэх. Үүнийг гарын авлага болгон ашиглаарай: What forces layout/reflow. The comprehensive list.
- DOM стиль рүү хийх “унших” ба “бичих” үйлдлүүдийг хамтад нь багцлах. Үүнийг өөрийнхөө өмнөөс хийлгэхийн тулд FastDom-ийг ашиглаарай.
- Chrome-ийн Timeline profiler-ийг ашиглаж, reflow (нил ягаан хэсгүүд)-ийг аль болох бага байлгахыг баталгаажуулах.