Անցյալ 2017 թվականի փետրվարի 2-ին Էնն Թեյլորը (Ann Taylor) 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-ի «Ովքեր են դա ճիշտ արել» հոդվածում: Սակայն չհայտնվեց արտադրողականության պատճառով:
Ուստի մենք հարցրինք Էննին այդ մասին, և նա պատասխանեց.
…Ես մի փոքր փորձարկեցի անվճար տարբերակը: Այն շատ խոստումնալից է. բազմաթիվ մոդուլներ, բավականին ինտուիտիվ միջերես, հիանալի WYSIWYG փորձառություն, ամեն ինչ հեշտությամբ է տեղաշարժվում: Այնուամենայնիվ, երբ էջում ունեցա մոտ 10 մոդուլ, խմբագրման ցածր արագության պատճառով դժվարացավ դրանով աշխատանքը շարունակելը…
Այսպիսով, նրա խոսքով, Page Builder Sandwich-ն ունի արտադրողականության խնդիրներ: Եվ քանի որ մենք միշտ լսում ենք կարծիքները, մենք ստուգեցինք դա:
Արտադրողականության թեստավորում
Մեզ անհրաժեշտ էր PBS-ի արտադրողականությունը ստուգելու եղանակ. քանի որ PBS-ը հիմնականում աշխատում է բրաուզերում Javascript-ի միջոցով, Chrome-ի Timeline պրոֆայլերը ճիշտ գործիք էր այդ աշխատանքի համար: Timeline պրոֆայլերը կարող է նույնականացնել և չափել, թե ինչ գործընթացներ են տեղի ունենում վեբ էջում աշխատանքի ընթացքում, ինչը հիանալի է արագության խոչընդոտները գտնելու համար:
Մենք որոշեցինք, որ նախ պետք է փորձարկենք ամենապարզ քայլերը՝ մկնիկը շարժելով բազմաթիվ տարրերի և տողերի վրայով: Պարզ է, չէ՞: Մենք վստահ էինք, որ այսպիսի պարզ բանի հետ կապված արտադրողականության խնդիրներ չենք ունենա: Ցավոք, սխալվեցինք:
Հենց սեղմեցինք ձայնագրման կոճակը, մոտ 2 վայրկյան մկնիկը շարժեցինք էկրանի վրայով, ապա սեղմեցինք կանգնեցնելու կոճակը: Ահա պրոֆայլերից ստացված ժամանակային գրաֆիկը.
Եվ ահա, տեսեք: Այն լի է արտադրողականության խոչընդոտներով:
Գրաֆիկում շատ բան է կատարվում, բայց ամենաակնհայտ խնդիրն այն է, որ դրանում շատ են մանուշակագույն հատվածները (ինչը չպետք է լիներ): Բացի այդ, այս մանուշակագույն հատվածները տեսանելիորեն շատ լայն են, և բոլորն էլ, կարծես, հասնում են գրաֆիկի գագաթնակետին: Կնկատեք նաև, որ գրաֆիկը միշտ լիքն է, և մանուշակագույն հատվածները գրեթե երբեք չեն իջնում զրոյի: Կարծես բրաուզերը միշտ ինչ-որ բանով զբաղված է և գրեթե երբեք չի հանգստանում:
Բայց ի՞նչ են իրականում նշանակում այդ բոլոր մանուշակագույն հատվածները:
Պրոֆայլերը բարեհաճորեն հուշեց մեզ, որ դրանք կոչվում են reflow-ներ: Ահա, թե ինչ է reflow-ն՝ ըստ Google Developer կայքի:
Reflow-ն վեբ բրաուզերի գործընթացի անվանումն է, որը վերահաշվարկում է փաստաթղթի տարրերի դիրքերն ու երկրաչափությունը… reflow-ն բրաուզերում օգտատիրոջ գործողությունները արգելափակող գործողություն է:
Այսպիսով, դա նշանակում է, որ PBS-ում մկնիկը շարժելը հարուցում է հսկայական քանակությամբ reflow-ներ: Ավելի վատն այն է, որ յուրաքանչյուր reflow ակնթարթորեն կանգնեցնում է բրաուզերը, որպեսզի այն կարողանա ավարտել իր հաշվարկները: Ավելի շատ reflow-ները նշանակում են բրաուզերի ավելի շատ դադարներ, իսկ օգտատիրոջ փորձառությունը դառնում է էլ ավելի դանդաղաշարժ ու ընդհատումներով: Ավելի պարզ ասած՝ շատ մանուշակագույն հատվածներ = վատ է:
Այսպիսով, պարզվեց, որ Էննը ճիշտ էր. մենք իսկապես ունենք արտադրողականության լուրջ խոչընդոտ, ուստի մեր թիմն անմիջապես գործի անցավ այն շտկելու համար:
Մեր Reflow / Jank խնդրի շտկումը
Ամեն ինչից առաջ մենք պետք է հետազոտություն կատարեինք՝ պարզելու, թե հատկապես ինչն է առաջին հերթին առաջացնում այս reflow-ն: Մենք պարզեցինք, որ, ըստ երևույթին, Javascript-ում շատ տարածված բաներ կարող են դրդել բրաուզերին reflow առաջացնել:
Գրեթե ցանկացած ֆունկցիա, որը կարող եք օգտագործել DOM տարրի լայնությունը, բարձրությունը կամ դիրքը ստանալու համար, կարող է reflow հարուցել: Փոլ Այրիշը (Paul Irish) գրել է ընդարձակ ցանկ 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-ում՝ մենք հանդիպեցինք Ուիլսոն Փեյջի (Wilson Page) «Շարվածքի ծանրաբեռնվածության (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-ում մեզ սովորաբար անհրաժեշտ է լինում ստանալ տարրի որոշակի ոճի ընթացիկ արժեքը: Օրինակ՝ վերևի լուսանցքը ստանալու համար մենք սովորաբար օգտագործում ենք սա.
var styles = window.getComputedStyle( elem ); var marginTop = styles.marginTop;
Բայց մենք պարզեցինք, որ սա անելն անհամեմատ ավելի արագ է.
var marginTop = elem.style.marginTop;
Այնուամենայնիվ, elem.style.marginTop -ը և getComputedStyle -ը շատ տարբեր են: Առաջինը մեզ տալիս է միայն տարրի ներկառուցված (inline) ոճավորումը և հաշվի չի առնում որևէ ոճաթերթ. իսկ երկրորդը հաշվի է առնում ինչպես inline ոճերը, այնպես էլ ոճաթերթերը:
Ավելին, նույնիսկ երբ getComputedStyle -ի արդյունքը քեշավորված էր, այն միևնույն է շատ դանդաղ էր: Ինչպես պարզվեց, հենց ոճի օբյեկտին դիմելն է դանդաղ գործընթացը:
Բարեբախտաբար, PBS-ը տարրերին կցում է inline ոճեր, և քանի որ inline ոճերը (մեծ մասամբ) գերակայություն ունեն ոճաթերթերի նկատմամբ, կլինեն դեպքեր, երբ inline ոճի արժեքը ստանալը լիովին բավարար կլինի: Այսպիսով, եթե միավորենք վերը նշված երկու մեթոդները, կարող ենք ստանալ շատ ավելի արագ աշխատող կոդ.
var marginTop = elem.style.marginTop;
if ( ! marginTop ) {
var styles = window.getComputedStyle( elem );
marginTop = styles.marginTop;
}
Կոդի բարելավումների արդյունքները
Վերոնշյալ մեր 3 շտկումները պահանջեցին շատ հետազոտություններ, թեստավորում և փաստացի ներդրում (և հետաձգեցին մեր Fonts ֆունկցիայի թողարկումը), բայց ի վերջո այդ ամենն արժեր դրան:
Վերադառնալով մեր նախնական թեստին՝ մկնիկը 2 վայրկյան էկրանով շարժելուն: Ահա հին տգեղ/դանդաղաշարժ ժամանակային գրաֆիկը.
Եվ ահա, թե ինչպիսի տեսք ունի մեր նոր ժամանակային գրաֆիկը արտադրողականության բարելավումներից հետո.
Ինչպես տեսնում եք, գրաֆիկում մանուշակագույն հատվածներն ավելի քիչ են: Եվ նկատելի տարբերություն կա բրաուզերի կողմից իրականացվող գործընթացների քանակի մեջ: Ներքևի ուղղանկյուն հատվածները նույնպես ավելի բարակ են, ինչը նշանակում է, որ գործընթացներն ավելի արագ են կատարվում, քան նախկինում:
Եթե համեմատեք հին և նոր գրաֆիկներում բարձր/լայն մանուշակագույն հատվածների քանակը, ապա կտեսնեք ապշեցուցիչ՝ արտադրողականության 200% աճ: Մենք տեսնում ենք նաև արտադրողականության համատարած աճ: Ամենևին վատ չէ:
Հիմնական եզրահանգումներն ու դասերը
Անկեղծ ասած, մենք չէինք կարծում, որ PBS-ն ունի արտադրողականության խնդիրներ, քանի դեռ Էննը չէր բարձրաձայնել դրա մասին: Հիմնականում այն պատճառով, որ մենք օգտագործում էինք մշակողների համար նախատեսված համակարգիչներ, և արտադրողականության խոչընդոտներն այնքան էլ ակնհայտ չէին: Հետադարձ հայացք գցելով՝ արտադրողականության ստուգումը պետք է լիներ գլխավոր առաջնահերթություններից մեկը: Բայց գոնե հիմա՝ սկսած 4.3 տարբերակից, այդ հարցը լուծված է:
Նրանց համար, ովքեր ցանկանում են բարելավել իրենց WordPress փլագինի արտադրողականությունը, հատկապես եթե դուք նույնպես օգտագործում եք ծավալուն Javascript, ահա այս փորձից քաղված իմ եզրահանգումները.
- Սահմանափակեք/քեշավորեք ձեր կանչերը դեպի reflow առաջացնող հատկություններ/ֆունկցիաներ: Որպես ուղեցույց օգտագործեք սա. What forces layout/reflow. The comprehensive list.
- Խմբավորեք DOM ոճերի «ընթերցումները» և «գրառումները»: Դրա համար օգտագործեք FastDom-ը:
- Օգտագործեք Chrome-ի Timeline պրոֆայլերը և համոզվեք, որ ունեք հնարավորինս քիչ reflow-ներ (մանուշակագույն հատվածներ):