Handlebars a câștigat popularitate prin adoptarea sa în cadre ca Meteor și Ember.js, dar ceea ce se întâmplă cu adevărat în spatele scenei acestui motor templant captivant?
În acest articol vom analiza adânc procesul prin care stau la baza Handlebars merge pentru a compila șabloanele.
Acest articol vă așteaptă să citiți introducerea mea anterioară Handlebars și, ca atare, presupune că știți elementele de bază ale creării șabloanelor Handlebar.
Când utilizați un șablon Handlebars, probabil știți că începeți prin a compila sursa șablonului într-o funcție utilizând Handlebars.compile ()
și apoi utilizați acea funcție pentru a genera codul HTML final, trecând în valori pentru proprietăți și substituenți.
Dar această aparent simplă funcție de compilare face de fapt câțiva pași în spatele scenei, și acesta este exact ceea ce va face acest articol; să aruncăm o privire la o defalcare rapidă a procesului:
În acest articol vom construi un instrument pentru a analiza șabloanele Handlebars la fiecare dintre acești pași, astfel încât pentru a afișa rezultatele puțin mai bine pe ecran, voi folosi prism.js marcator de sintaxă creat de unul și numai Lea Verou. Descărcați sursa miniatură, amintindu-vă că verificați JavaScript în secțiunea limbi.
Următorul pas este să creați un fișier HTML gol și să îl completați cu următoarele:
Handlebars.js Jetoane:
Operații:
ieşire:
Funcţie:
Este doar un cod de boilerplate care include ghidon și prisma și apoi set de sus unele divs pentru pașii diferiți. În partea de jos, puteți vedea două blocuri de script-uri: prima este pentru șablon, iar cea de-a doua este pentru codul nostru JS.
Am scris, de asemenea, un mic CSS pentru a aranja totul un pic mai bine, pe care sunteți liber să adăugați:
corp marja: 0; umplutura: 0; font-family: "opensans", Arial, sans-serif; fundal: # F5F2F0; font-size: 13px; #analiza top: 0; stânga: 0; poziția: absolută; lățime: 100%; înălțime: 100%; marja: 0; umplutura: 0; #analysis div lățime: 33,33%; înălțime: 50%; plutește la stânga; padding: 10px 20px; box-size: caseta de margine; overflow: auto; #function width: 100% important;
Apoi avem nevoie de un șablon, deci începeți cu cel mai simplu șablon posibil, doar un text static:
Deschiderea acestei pagini în browserul dvs. ar trebui să ducă la afișarea șablonului în caseta de ieșire așa cum era de așteptat, nimic diferit încă, acum trebuie să scriem codul pentru a analiza procesul la fiecare din celelalte trei etape.
Primul ghidon de pași pe șablon dvs. are rolul de a tokenize sursa, ceea ce înseamnă că este necesar să rupem sursa separat în componentele sale individuale, astfel încât să putem gestiona fiecare piesă în mod corespunzător. De exemplu, dacă a existat un text cu un substituent în mijloc, atunci Handlebars ar separa textul înainte ca substituentul să îl plaseze într-un singur simbol, apoi substituentul propriu-zis ar fi plasat într-un alt simbol și, în sfârșit, tot textul după substituent ar fi plasat într-un al treilea simbol. Acest lucru se datorează faptului că piesele respective trebuie să păstreze ambele ordinea șablonului, dar trebuie, de asemenea, să fie procesate diferit.
Acest proces se face folosind Handlebars.parse ()
funcția și ceea ce primiți înapoi este un obiect care conține toate segmentele sau "declarațiile".
Pentru a ilustra mai bine ceea ce vorbesc, să formăm o listă cu paragrafe pentru fiecare jetoanele scoase:
// Afișați Tokens var tokenizer = Handlebars.parse (src); var tokenStr = ""; pentru (var i în tokenizer.statements) var token = tokenizer.statements [i]; tokenStr + = ""+ (parseInt (i) +1) +"); comutare (token.type) caz "conținut": tokenStr + = "[string] caz "mustă": tokenStr + = "[placeholder] -" + token.id.string; pauză; bloc "caz: tokenStr + =" [block] - "+ token.mustache.id.string; document. getElementById ("token-uri") innerHTML + = tokenStr;
Așa că începem prin rularea sursei șabloanelor Handlebars.parse
pentru a obține lista de jetoane. Apoi trasăm prin toate componentele individuale și construim un set de șiruri de citire umane pe baza tipului de segment. Textul obișnuit va avea un tip de "conținut" pe care apoi îl putem trimite șirul înfășurat în ghilimele pentru a arăta ceea ce este egal. Înlocuitorii vor avea un tip de "mustață" pe care să-l afișăm împreună cu id-ul lor (denumirea substituentului). Și nu în ultimul rând, ajutoarele bloc vor avea un tip de "bloc", pe care apoi putem afișa doar blocurile interne "id" (numele blocului).
Actualizând acest lucru acum în browser, ar trebui să vedeți doar un singur simbol "șir", cu textul șablonului nostru.
Odată ce farurile au colecțiile de jetoane, acestea cicluesc prin fiecare și "generează" o listă de operații predefinite care trebuie efectuate pentru șablonul care trebuie compilat. Acest proces se face folosind Handlebars.Compiler ()
obiect, trecând în obiectul token din pasul 1:
// Afișează operațiile var opSequence = new Handlebars.Compiler () compile (tokenizer, ); var opStr = ""; pentru (var i în opSequence.opcodes) var op = opSequence.opcodes [i]; opStr + = ""+ (parseInt (i) +1) +") - "+ op.opcode; document.getElementById (" operații ") innerHTML + = opStr;
Aici compilam jetoanele în secvența de operații pe care am vorbit-o și apoi ne plimbăm prin fiecare și creăm o listă similară ca în primul pas, cu excepția cazului în care trebuie doar să tipărim opcodul. Opcode este "operațiunea" sau numele "funcției" care trebuie executat pentru fiecare element din secvență.
Înapoi în browser, trebuie să vedeți acum doar o singură operație numită "appendContent", care va adăuga valoarea la "bufferul" curent sau "șirul de text". Există o mulțime de opcode diferite și nu cred că sunt calificat să explic câteva dintre ele, dar căutarea rapidă a codului sursă pentru un opcode dat vă va arăta funcția care va fi rulată pentru aceasta.
Ultima etapă este de a lua lista de opcode și de a le converti într-o funcție, face acest lucru prin citirea listei de operațiuni și codul de concatenare inteligent pentru fiecare. Aici este codul necesar pentru a ajunge la funcția pentru acest pas:
// Afișaj Funcție var outputFunction = new Handlebars.JavaScriptCompiler () compilați (opSequence, , undefined, true); document.getElementById ("sursă"). innerHTML = outputFunction.toString (); Prism.highlightAll ();
Prima linie creează compilatorul care trece în secvența op și această linie va returna funcția finală utilizată pentru generarea șablonului. Apoi convertim funcția într-un șir și îi spunem lui Prism sintaxei să o evidențieze.
Cu acest cod final, pagina dvs. ar trebui să arate cam așa:
Această funcție este incredibil de simplă, deoarece a fost o singură operație, ea returnează șirul dat; haideți să aruncăm o privire la editarea șablonului și să vedem cum acești pași individuali în mod direct, grupați împreună pentru a forma o abstracție foarte puternică.
Să începem cu ceva simplu și să înlocuim pur și simplu cuvântul "Lumea" cu un substituent; noul dvs. șablon ar trebui să arate după cum urmează:
Și nu uitați să treceți variabila astfel încât rezultatul să fie OK:
// Afișaj de ieșire var t = Handlebars.compile (src); document.getElementById ("ieșire"). innerHTML + = t (nume: "Gabriel");
Făcând acest lucru, veți găsi că adăugând doar un simplu substituent, complică procesul destul de puțin.
Secțiunea complicată dacă / else este pentru că nu știe dacă substituentul este de fapt un substituent sau o metodă de ajutor
Dacă încă nu v-ați dat seama ce înseamnă jetoanele, ar trebui să aveți o idee mai bună acum; după cum puteți vedea în imagine, a împărțit substituentul din șiruri și a creat trei componente individuale.
Apoi, în secțiunea de operații, există câteva adăugiri. Dacă vă amintiți din când în când, pentru a scoate un text simplu, Handlebars utilizează operația "appendContent", ceea ce puteți vedea acum în partea de sus și de jos a listei (atât pentru "Hello" cât și pentru!). Restul în mijloc sunt toate operațiunile necesare procesării substituentului și adăugarea conținutului escapat.
În cele din urmă, în fereastra de jos, în loc să întoarcem doar un șir, de această dată creează o variabilă tampon și manipulează câte un token la un moment dat. Secțiunea complicată dacă / else este pentru că nu știe dacă substituentul este de fapt un substituent sau o metodă de ajutor. Așa că încearcă să vadă dacă există o metodă de ajutor cu numele dat, caz în care va apela metoda helper și va stabili "stack1" la valoare. În cazul în care este un substituent, acesta va atribui valoarea din contextul trecut (denumit aici 'depth0') și dacă o funcție a fost trecută în ea va plasa rezultatul funcției în variabila 'stack1'. Odată ce totul este realizat, acesta scapă, așa cum am văzut în operațiuni și îl atașăm la tampon.
Pentru următoarea schimbare, să încercăm pur și simplu același șablon, cu excepția acestui moment, fără a scăpa de rezultate (pentru a face acest lucru, adăugați o altă bretonă curată "Nume"
)
Actualizând pagina, acum veți vedea că a fost eliminată operația pentru a scăpa de variabila și, în schimb, ea doar o adaugă, acest bule coboară în funcția care acum doar verifică pentru a vă asigura că valoarea nu este o valoare falsă (în afară de 0) și apoi o adaugă fără să o scape.
Deci, cred că substituenții sunt destul de drepți în față, lasă acum să aruncăm o privire la utilizarea funcțiilor de ajutor.
Nu are rost să facem acest lucru mai complicat, atunci trebuie să fie, să creăm doar o funcție simplă care va returna duplicatul unui număr trecut, deci înlocuiți șablonul și adăugați un nou bloc de script pentru helper (înainte de celălalt cod ):
Am decis să nu scap de ea, deoarece face ca funcția finală să fie ușor mai ușor de citit, dar puteți încerca atât dacă doriți. Oricum, alergatul ar trebui să producă următoarele:
Aici puteți vedea că știe că este un ajutor, așa că, în loc să spună "invokeAmbiguous", el spune acum "invokeHelper" și, prin urmare, și în funcția nu mai există un bloc if / else. Cu toate acestea, el se asigură că ajutătorul există și încearcă să revină la context pentru o funcție cu același nume, în cazul în care nu.
Un alt lucru care merită menționat este că puteți vedea parametrii pentru care ajutoarele sunt transmise în mod direct și de fapt sunt codificate greu, dacă este posibil, atunci când funcția get este generată (numărul 3 în funcția dublată).
Ultimul exemplu pe care vreau să-l acordez este de ajutoratori blocați.
Ajutoarele bloc vă permit să înfășurați alte jetoane în interiorul unei funcții care este capabilă să își stabilească propriul context și opțiuni. Să aruncăm o privire la un exemplu folosind ajutorul blocului implicit "if":
Aici verificăm dacă "name" este setat în contextul curent, caz în care îl vom afișa, altfel vom emite "World!". Făcând acest lucru în analizorul nostru, veți vedea doar două jetoane, chiar dacă există mai multe; acest lucru se datorează faptului că fiecare bloc este executat ca propriul "șablon", astfel încât toate jetoanele din interiorul acestuia (cum ar fi Nume
) nu va face parte din apelul extern și va trebui să îl extrageți din nodul blocului în sine.
În afară de asta, dacă aruncați o privire la această funcție:
Puteți vedea că, de fapt, compilează funcțiile de ajutor al blocului în funcția șablonului. Există două deoarece una este funcția principală, iar cealaltă este funcția inversă (atunci când parametrul nu există sau este fals). Funcția principală: "program1" este exact ceea ce am avut înainte, când am avut doar un text și un singur substituent, pentru că, așa cum am menționat, fiecare dintre funcțiile blocului helper sunt construite și tratate exact ca un șablon regulat. Acestea sunt apoi executați prin ajutorul "if" pentru a primi funcția corespunzătoare pe care o va adăuga apoi la tamponul extern.
Ca și înainte, merită menționat faptul că primul parametru al unui ajutor de bloc este cheia în sine, în timp ce parametrul "acest" este setat la întregul conținut transmis în context, care poate fi util la construirea propriilor ajutoare bloc.
În acest articol s-ar putea să nu avem o privire practică asupra modului de a realiza ceva în Handlebars, dar sper că aveți o mai bună înțelegere a ceea ce se întâmplă exact în spatele scenei, ceea ce ar trebui să vă permită să construiți șabloane și ajutoare mai bune cu acest nou găsit cunoştinţe.
Sper că te-ai bucurat de lectură, ca de obicei dacă ai orice întrebări, nu ezitați să mă contactezi pe Twitter (@GabrielManricks) sau pe Nettuts + IRC (#nettuts on freenode).