Handlebars har blitt stadig mer populær med vedtaket i rammer som Meteor og Ember.js, men hva skjer egentlig bak kulissene i denne spennende templerende motoren?
I denne artikkelen vil vi se nærmere på den underliggende prosessen. Håndtak går gjennom for å kompilere maler.
Denne artikkelen forventer at du har lest min forrige introduksjon til Håndtak og som sådan forutsetter at du vet grunnleggende om å opprette Handlebar-maler.
Når du bruker en håndteringsmal, vet du sikkert at du starter ved å kompilere malens kilde til en funksjon ved hjelp av Handlebars.compile ()
og da bruker du den funksjonen til å generere den endelige HTML-en, som sender inn verdier for egenskaper og plassholdere.
Men den tilsynelatende enkle kompileringsfunksjonen gjør faktisk et par skritt bak kulissene, og det er hva denne artikkelen egentlig handler om; la oss ta en titt på en rask nedbryting av prosessen:
I denne artikkelen vil vi bygge et verktøy for å analysere håndtaksmaler i hvert av disse trinnene, for å vise resultatene litt bedre på skjermen, vil jeg bruke prism.js syntaxstifter laget av den eneste Lea Verou. Last ned den godkjente kilden, og husk å sjekke JavaScript i språkavsnittet.
Det neste trinnet er å lage en tom HTML-fil og fylle den med følgende:
Handlebars.js tokens:
operasjoner:
Produksjon:
Funksjon:
Det er bare noen boilerplate-koden som inneholder styrer og prisme, og deretter setter du opp noen divs for de forskjellige trinnene. Nederst kan du se to skriptblokker: den første er for mal og den andre er for vår JS-kode.
Jeg skrev også en liten CSS for å ordne alt litt bedre, som du gratis kan legge til:
kropp margin: 0; polstring: 0; font-familie: "opensans", Arial, sans-serif; bakgrunn: # F5F2F0; skriftstørrelse: 13px; #analyse topp: 0; venstre: 0; posisjon: absolutt; bredde: 100%; høyde: 100%; margin: 0; polstring: 0; #analysis div width: 33.33%; høyde: 50%; flyte: venstre; polstring: 10px 20px; boks-størrelse: border-box; overløp: auto; #function width: 100%! important;
Deretter trenger vi en mal, så la oss begynne med den enkleste malen mulig, bare noen statisk tekst:
Å åpne denne siden i nettleseren din skal føre til at malmen vises i utmatingsboksen som forventet, ingenting annet ennå, vi må nå skrive koden for å analysere prosessen ved hvert av de tre andre trinnene.
Det første trinnet som utføres på malen, er å tokenisere kilden, og det betyr at vi må kutte kilden fra hverandre til de enkelte komponentene, slik at vi kan håndtere hvert stykke på riktig måte. For eksempel, hvis det var noen tekst med en plassholder i midten, så ville Handlebars skille teksten før plassholderen plasserte den i ett token, da plassholderen selv ville bli plassert i et annet token, og til slutt all teksten etter plassholderen ville bli plassert i et tredje token. Dette skyldes at disse delene må beholdes rekkefølgen på malen, men de må også behandles annerledes.
Denne prosessen er gjort ved hjelp av Handlebars.parse ()
funksjon, og hva du kommer tilbake er et objekt som inneholder alle segmentene eller setningene '.
For bedre å illustrere hva jeg snakker om, la oss lage en liste med avsnitt for hver av tokenene tatt ut:
// Display Tokens var tokenizer = Handlebars.parse (src); var tokenStr = ""; for (var jeg i tokenizer.statements) var token = tokenizer.statements [i]; tokenStr + = ""+ (parseInt (i) +1) +") "; bytt (token.type) tilfelle« innhold »: tokenStr + =" [streng] - \ "" + token.string + "\" "; case "mustache": tokenStr + = "[placeholder] -" + token.id.string; break; case "blokk": tokenStr + = "[blokk] -" + token.mustache.id.string; dokument. getElementById ("tokens"). innerHTML + = tokenStr;
Så begynner vi ved å kjøre malene kilde inn i Handlebars.parse
for å få listen over tokens. Vi sykler gjennom alle de enkelte komponentene og bygger opp et sett med menneskelige lesbare strenger basert på segmentets type. Vanlig tekst vil ha en type "innhold" som vi da bare kan sende strengen innpakket i sitater for å vise hva den tilsvarer. Plassholderne vil ha en type "mustache" som vi så kan vise sammen med deres "id" (plassholdernavn). Og sist men ikke minst, vil blokkeringshjelpere ha en type "blokk", som vi da også bare kan vise blokkene intern "id" (blokknavn).
Forfriskende dette nå i nettleseren, bør du bare se en enkelt "streng" token, med vår maltekst.
Når styret har samlingen av tokens, sykler det gjennom hver og "genererer" en liste over forhåndsdefinerte operasjoner som må utføres for malen som skal kompileres. Denne prosessen er gjort ved hjelp av Handlebars.Compiler ()
objekt, passerer i token-objektet fra trinn 1:
// Display Operations var opSequence = new Handlebars.Compiler (). Compile (tokenizer, ); var opStr = ""; for (var jeg i opSequence.opcodes) var op = opSequence.opcodes [i]; opStr + = ""+ (parseInt (i) +1) +") - "+ op.opcode; document.getElementById (" operasjoner "). innerHTML + = opStr;
Her samler vi tokenene i operasjonssekvensen jeg snakket om, og så sykler vi gjennom hverandre og lager en lignende liste som i første trinn, bortsett fra her trenger vi bare å skrive ut opcode. Opcode er "operasjonens" eller funksjonens "navn" som må kjøres for hvert element i sekvensen.
Tilbake i nettleseren, bør du bare se en enkelt operasjon kalt "appendContent" som vil legge til verdien til gjeldende "buffer" eller "tekststreng". Det er mange forskjellige opkoder, og jeg tror ikke jeg er kvalifisert til å forklare noen av dem, men å gjøre et raskt søk i kildekoden for en gitt opcode vil vise deg hvilken funksjon som skal kjøres for det.
Det siste trinnet er å ta opp listen over opkoder og konvertere dem til en funksjon, det gjør dette ved å lese listen over operasjoner og smart sammenkoblingskode for hver enkelt. Her er koden som kreves for å komme til funksjonen for dette trinnet:
// Display Funksjon var outputFunction = new Handlebars.JavaScriptCompiler (). Kompilere (opSequence, , undefined, true); document.getElementById ("kilde"). innerHTML = outputFunction.toString (); Prism.highlightAll ();
Den første linjen lager kompilatoren som passerer i op-sekvensen, og denne linjen vil returnere den endelige funksjonen som brukes til å generere malen. Vi konverterer deretter funksjonen til en streng og forteller Prism å syntax markere den.
Med denne siste koden, bør siden din se ut slik som:
Denne funksjonen er utrolig enkel, siden det bare var en operasjon, returnerer den bare den oppgitte strengen; la oss se på å redigere malen og se hvordan disse individuelt rett fremover trinnene, grupperer sammen for å danne en svært kraftig abstraksjon.
La oss starte med noe enkelt, og la oss bare erstatte ordet 'World' med en plassholder; Din nye mal skal se ut som følgende:
Og ikke glem å passere variabelen slik at utgangen ser OK ut:
// Display Output var t = Handlebars.compile (src); document.getElementById ("output"). innerHTML + = t (navn: "Gabriel");
Kjører dette, vil du finne at ved å legge til bare en enkel plassholder, kompliserer prosessen ganske mye.
Den kompliserte hvis / ellers delen er fordi den ikke vet om stedholderen faktisk er en plassholder eller en hjelpemetode
Hvis du fortsatt var usikker på hva tokens er, bør du få en bedre ide nå; Som du kan se på bildet, delte den ut plassholderen fra strengene og opprettet tre individuelle komponenter.
Deretter er det ganske mange tillegg i operasjonsseksjonen. Hvis du husker fra før, for å bare sende ut noen tekst, bruker Handlebars 'appendContent' -operasjonen, som du nå kan se øverst og nederst på listen (for både "Hello" og "!"). Resten i midten er alle operasjonene som trengs for å behandle plassholderen og legge til det rømte innholdet.
Til slutt, i bunnvinduet, i stedet for å bare returnere en streng, skapes denne gangen en buffervariabel, og håndterer ett token om gangen. Den kompliserte hvis / ellers delen er fordi den ikke vet om stedholderen faktisk er en plassholder eller en hjelpemetode. Så det prøver å se om en hjelpemetode med det oppgitte navnet eksisterer, i så fall vil det kalle hjelpemetoden og sette 'stack1' til verdien. I tilfelle det er en plassholder, vil den tildele verdien fra konteksten som er sendt inn (her kalt 'depth0'), og hvis en funksjon ble sendt inn, vil den plassere resultatet av funksjonen i variabelen 'stack1'. Når alt er gjort, rømmer det det som vi så i operasjonene og legger det til bufferen.
For vår neste endring, la oss bare prøve den samme malen, unntatt denne gangen uten å rømme resultatene (for å gjøre dette, legg til en annen krøllete brace "Navn"
)
Forfriskende siden, nå vil du se at den fjernet operasjonen for å unnslippe variabelen, og i stedet bare legger den til, dette bobler ned i funksjonen som nå bare kontrollerer for at verdien ikke er en falsk verdi (i tillegg til 0) og deretter legger til det uten å rømme den.
Så jeg tror at plassholdere er ganske rett frem, kan vi nå se på hjelpefunksjonene.
Det er ikke noe poeng å gjøre dette mer komplisert da det må være, la oss bare lage en enkel funksjon som vil returnere duplikatet av et nummer som er sendt inn, så erstatt malen og legg til en ny skriptblokk for hjelperen (før den andre koden ):
Jeg har besluttet å ikke unnslippe det, da det gjør den endelige funksjonen litt enklere å lese, men du kan prøve begge om du vil. Anyways, kjører dette bør produsere følgende:
Her kan du se at den vet at det er en hjelper, så i stedet for å si 'invokeAmbiguous' står det nå 'invokeHelper', og derfor er det ikke lenger en om / else-blokk i funksjonen. Det sørger imidlertid fortsatt for at hjelperen eksisterer og prøver å falle tilbake til konteksten for en funksjon med samme navn i tilfelle det ikke gjør det.
En annen ting som er verdt å nevne er at du kan se parametrene for at hjelpere blir sendt inn direkte, og er faktisk hardkodede i, hvis det er mulig, når funksjonen blir generert (nummer 3 i den doble funksjonen).
Det siste eksemplet jeg vil dekke er om blokkhjelpere.
Blokkhjelpere tillater deg å pakke inn andre tokens i en funksjon som kan sette sin egen kontekst og alternativer. La oss ta en titt på et eksempel ved hjelp av standard "if" -blokkerassistent:
Her kontrollerer vi om "navn" er satt i gjeldende kontekst, i så fall vil vi vise det, ellers sender vi ut "World!". Kjører dette i vår analysator, du vil se bare to tokens selv om det er flere; Dette er fordi hver blokk kjøres som sin egen 'mal', så alle tokens inne i den (som Navn
) vil ikke være en del av det ytre samtalen, og du vil trenge å trekke den ut fra blokkens knutepunkt selv.
Dessuten, hvis du tar en titt på funksjonen:
Du kan se at den faktisk samler blokkhjelpens funksjoner i malens funksjon. Det er to fordi en er hovedfunksjonen og den andre er den inverse funksjonen (for når parameteren ikke eksisterer eller er feil). Hovedfunksjonen: "program1" er akkurat det vi hadde før da vi bare hadde litt tekst og en enkelt plassholder, for som jeg nevnte, er hver blokkfunksjonsfunksjon bygget opp og behandlet akkurat som en vanlig mal. De kjører deretter gjennom "if" -hjelperen for å motta den riktige funksjonen som den deretter vil legge til den ytre bufferen.
Som tidligere er det verdt å nevne at den første parameteren til en blokkhjelp er nøkkelen selv, mens parameteren "denne" er satt til hele passet i konteksten, noe som kan komme til nytte når du bygger dine egne blokkhjelpere.
I denne artikkelen har vi kanskje ikke tatt et praktisk blikk på hvordan du oppnår noe i håndtak, men jeg håper du får en bedre forståelse av hva som skjer akkurat bak kulissene som skal tillate deg å bygge bedre maler og hjelpere med denne nye funnet kunnskap.
Jeg håper du likte å lese, som alltid hvis du har spørsmål, kan du kontakte meg på Twitter (@GabrielManricks) eller på Nettuts + IRC (#nettuts på freenode).