de Adam Kelly

Să învățăm cum funcționează pachetele de module și apoi să le scriem singuri

Sa invatam cum functioneaza pachetele de module si apoi sa

Salut! Bine ați venit, bine ați venit, este minunat să vă avem aici! Astăzi vom construi un pachet de module JavaScript foarte simplu.

Înainte de a începe, vreau să ofer câteva recunoștințe. Acest articol se bazează în mare măsură pe următoarele resurse:

Bine, să începem cu ceea ce este de fapt un pachet de module.

Ce este un pachet de module?

Un pachet de module este un instrument care preia bucăți de JavaScript și dependențele acestora și le grupează într-un singur fișier, de obicei pentru a fi utilizat în browser. Este posibil să fi folosit instrumente precum Navigare, Webpack, Rulează sau una dintre multele altele.

ad-banner

De obicei începe cu un fișier de intrare și, de acolo, include toate codurile necesare pentru fișierul de intrare respectiv.

Sa invatam cum functioneaza pachetele de module si apoi sa

Există două etape principale ale unui pachet:

  1. Rezoluția dependenței
  2. Ambalare

Începând de la un punct de intrare (cum ar fi app.js de mai sus), scopul rezolvării dependenței este de a căuta toate dependențele codului dvs. (alte bucăți de cod pe care trebuie să le funcționeze) și de a construi un grafic (numit grafic de dependență).

După ce ați făcut acest lucru, puteți împacheta sau converti graficul de dependență într-un singur fișier pe care aplicația îl poate utiliza.

Să începem codul nostru cu câteva importuri (voi explica mai departe motivul mai târziu).

Rezoluția dependenței

Primul lucru pe care trebuie să-l facem este să ne gândim cum dorim să reprezentăm un modul în faza de rezoluție a dependenței.

Reprezentarea modulului

Vom avea nevoie de patru lucruri:

  • Numele și un identificator al fișierului
  • De unde a venit fișierul (în sistemul de fișiere)
  • Codul din fișier
  • De ce dependențe are nevoie fișierul respectiv

Structura graficului este construită prin verificarea recursivă a dependențelor din fiecare fișier.

În JavaScript, cel mai simplu mod de a reprezenta un astfel de set de date ar fi un obiect.

Privind la createModuleObject funcția de mai sus, partea notabilă este apelul la o funcție numită detective.

Detectiv este o bibliotecă care poate găsiți toate apelurile de solicitat () indiferent cât de adânc sunt cuibărite, și utilizarea acestuia înseamnă că putem evita să facem propria noastră traversare AST!

Un lucru de remarcat (și acest lucru este același în aproape toate pachetele de module) este că, dacă încercați să faceți ceva ciudat de genul:

const libName="lodash"const lib = require(libName)

Nu îl va putea găsi (deoarece asta ar însemna executarea codului).

Deci, ce oferă rularea acestei funcții din calea unui modul?

1612186509 179 Sa invatam cum functioneaza pachetele de module si apoi sa

Ce urmeaza? Rezoluția dependenței.

Bine, nu chiar încă. În primul rând, vreau să vorbesc despre un lucru numit harta modulului.

Harta modulului

Când importați module în nod, puteți efectua importuri relative, cum ar fi require('./utils'). Deci, atunci când codul dvs. apelează acest lucru, cum știe grupul care este corect ./utils fișier când totul este ambalat?

Aceasta este problema rezolvată de harta modulului.

Obiectul nostru modul are un unic id cheie care va fi „sursa adevărului” nostru. Deci, atunci când facem rezoluția noastră de dependență, pentru fiecare modul, vom păstra o listă cu numele a ceea ce este necesar împreună cu id-ul lor. În acest fel, putem obține modulul corect în timpul rulării.

Acest lucru înseamnă, de asemenea, că putem stoca toate modulele într-un obiect non-imbricat, folosind id-ul ca cheie.

1612186509 598 Sa invatam cum functioneaza pachetele de module si apoi sa

Rezoluția dependenței

Bine, deci se întâmplă o sumă corectă în getModules funcţie. Scopul său principal este să înceapă de la modulul root / entry și să caute și să rezolve recursiv dependențele.

Ce vreau să spun prin „rezolvarea dependențelor”? În Node există un lucru numit require.resolve, și este modul în care Node își dă seama unde este fișierul pe care îl solicitați. Acest lucru se datorează faptului că putem importa relativ sau dintr-un node_modules pliant.

Din fericire pentru noi, există un modul npm numit resolve care implementează acest algoritm pentru noi. Trebuie doar să trecem în argumentele dependenței și URL-urilor de bază și va face toată munca grea pentru noi.

Trebuie să realizăm această rezoluție pentru fiecare dependență a fiecărui modul din proiect.

De asemenea, creăm harta modulului denumită map pe care le-am menționat mai devreme.

La sfârșitul funcției, rămânem cu o matrice numită modules care va conține obiecte modul pentru fiecare modul / dependență din proiectul nostru.

Acum că avem asta, putem trece la pasul final: ambalarea!

Ambalare

În browser, nu există module (un fel). Dar asta înseamnă că nu există nici o funcție necesară și nu module.exports. Deci, chiar dacă avem toate dependențele noastre, în prezent nu avem cum să le folosim ca module.

Funcția de fabrică a modulului

Introduceți funcția din fabrică.

O funcție din fabrică este o funcție (care nu este un constructor) care returnează un obiect. Este un model din programarea orientată obiect și una dintre utilizările sale este de a face încapsularea și injectarea dependenței.

Suna bine?

Folosind o funcție din fabrică, amândoi ne putem injecta propria noastră require funcția și module.exports obiect care poate fi utilizat în codul nostru grupat și care oferă modulului propriul domeniu de aplicare.

Ambalare

Următoarea este funcția de ambalare care este utilizată pentru ambalare.

Cele mai multe dintre acestea sunt doar șabloane literale ale JavaScript-ului, așa că hai să discutăm ce face.

În primul rând este modulesSource. Aici, parcurgem fiecare dintre module și le transformăm într-un șir de surse.

Deci, cum este ieșirea pentru un obiect modul?

1612186509 156 Sa invatam cum functioneaza pachetele de module si apoi sa

Acum este puțin greu de citit, dar puteți vedea că sursa este încapsulată. Noi oferim modules și require folosind funcția din fabrică așa cum am menționat anterior.

Includem și harta modulelor pe care le-am construit în timpul rezoluției dependenței.

Apoi în funcție, ne alăturăm tuturor pentru a crea un obiect mare al tuturor dependențelor.

Următorul șir de cod este un IIFE, ceea ce înseamnă că atunci când rulați codul respectiv în browser (sau oriunde altundeva), funcția va rula imediat. IIFE este un alt model pentru încapsularea domeniului de aplicare și este utilizat aici, astfel încât să nu poluăm domeniul de aplicare global cu al nostru require și module.

Puteți vedea că definim două funcții de solicitare, require și localRequire.

Require acceptă ID-ul unui obiect modul, dar, desigur, codul sursă nu este scris folosind id-uri. În schimb, folosim cealaltă funcție localRequire pentru a lua orice argumente de solicitat de către module și a le converti în id-ul corect. Aceasta utilizează acele hărți ale modulelor.

După aceasta, definim un module object că modulul poate fi completat și trece ambele funcții în fabrică, după care ne întoarcem module.exports.

În sfârșit, sunăm require(0) pentru a solicita modulul cu un ID de 0, care este fișierul nostru de intrare.

Si asta e! Pachetul nostru de module este complet 100%!

Felicitări! ?

Deci, acum avem un pachet de module de lucru.

Probabil că acest lucru nu ar trebui utilizat în producție, deoarece lipsește o mulțime de caracteristici (cum ar fi gestionarea dependențelor circulare, asigurarea faptului că fiecare fișier este analizat doar o dată, es-module și așa mai departe), dar sperăm că vă oferă o idee bună despre cum pachetele de module funcționează de fapt.

De fapt, acesta funcționează în aproximativ 60 de linii dacă eliminați tot codul sursă.

Vă mulțumim pentru lectură și sper că v-a plăcut să aruncați o privire asupra funcționării pachetului nostru simplu de module. Dacă ai făcut-o, asigură-te că bat din palme? și împărtășește.

Acest articol a fost inițial postat pe site-ul meu blog.
Verificați sursa https://github.com/adamisntdead/wbpck-bundler