Lad os lære, hvordan modulbundlere fungerer, og derefter skrive en selv

Hej! Velkommen, velkommen, det er dejligt at have dig her! I dag skal vi bygge en rigtig enkel JavaScript-modulbundter.

Inden vi starter, vil jeg give nogle få anerkendelser. Denne artikel trækker stærkt på følgende ressourcer:

  • Opdeling af JavaScript-modulbundter - Luciano Mammino
  • Minipack - Ronen Amiel

Okay, lad os komme i gang med, hvad en modulbundler faktisk er.

Hvad er en modulbundler?

En modulbundler er et værktøj, der tager stykker af JavaScript og deres afhængigheder og bundter dem i en enkelt fil, normalt til brug i browseren. Du har muligvis brugt værktøjer som Browserify, Webpack, Rollup eller en af ​​mange andre.

Det starter normalt med en indgangsfil, og derfra samler den al den kode, der er nødvendig til denne indgangsfil.

Der er to hovedfaser i en bundler:

  1. Afhængighedsopløsning
  2. Pakning

Fra et indgangspunkt (som app.jsovenfor) er målet med afhængighedsopløsning at kigge efter alle afhængighederne i din kode (andre kodestykker, som den skal fungere) og konstruere en graf (kaldet en afhængighedsgraf).

Når dette er gjort, kan du derefter pakke eller konvertere din afhængighedsgraf til en enkelt fil, som applikationen kan bruge.

Lad os starte vores kode med nogle importer (jeg vil uddybe årsagen senere).

Afhængighedsopløsning

Den første ting, vi skal gøre, er at tænke over, hvordan vi vil repræsentere et modul i afhængighedsopløsningsfasen.

Modulrepræsentation

Vi har brug for fire ting:

  • Filens navn og identifikator
  • Hvor filen kom fra (i filsystemet)
  • Koden i filen
  • Hvilke afhængigheder den fil har brug for

Grafstrukturen bliver bygget op gennem rekursiv kontrol af afhængigheder inden for hver fil.

I JavaScript ville den nemmeste måde at repræsentere et sådant datasæt være et objekt.

Når man ser på createModuleObjectfunktionen ovenfor, er den bemærkelsesværdige del opkaldet til en kaldet funktion detective.

Detektiv er et bibliotek, der kan " finde alle opkald, der kræves () uanset hvor dybt indlejret " , og brug af det betyder, at vi kan undgå at gøre vores egen AST-gennemgang!

En ting at bemærke (og dette er det samme i næsten alle modulbundlere) er, at hvis du prøver at gøre noget underligt som:

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

Det vil ikke være i stand til at finde det (fordi det ville betyde at udføre koden).

Så hvad giver det at køre denne funktion fra stien til et modul?

Hvad er det næste? Afhængighedsopløsning.

Okay, ikke helt endnu. Først vil jeg tale om en ting, der hedder et modulkort.

Modulkort

Når du importerer moduler i Node, kan du foretage relativ import, f.eks require('./utils'). Så når din kode kalder dette, hvordan ved bundleren, hvad der er den rigtige ./utilsfil, når alt er pakket?

Det er problemet, modulkortet løser.

Vores modulobjekt har en unik idnøgle, som vil være vores 'sandhedskilde'. Så når vi laver vores afhængighedsopløsning for hvert modul, holder vi en liste over navnene på, hvad der kræves sammen med deres id. På denne måde kan vi få det korrekte modul ved kørselstid.

Dette betyder også, at vi kan gemme alle modulerne i et ikke-indlejret objekt ved hjælp af id'et som en nøgle.

Afhængighedsopløsning

Okay, så der foregår en hel del i getModulesfunktionen. Dets hovedformål er at starte ved rod / indgangsmodulet og søge efter og løse afhængigheder rekursivt.

Hvad mener jeg med 'løse afhængigheder'? I Node er der en ting, der kaldes require.resolve, og det er, hvordan Node finder ud af, hvor den fil, du har brug for, er. Dette skyldes, at vi kan importere relativt eller fra en node_modulesmappe.

Heldig for os er der et npm-modul, resolveder implementerer denne algoritme for os. Vi er bare nødt til at videregive afhængigheds- og basis-URL-argumenter, og det vil gøre alt det hårde arbejde for os.

Vi har brug for at udføre denne opløsning for hver afhængighed af hvert modul i projektet.

Vi opretter også modulkortet, mapsom jeg nævnte tidligere.

I slutningen af ​​funktionen står vi tilbage med en matrix navngivet, modulesder indeholder modulobjekter til hvert modul / afhængighed i vores projekt.

Nu hvor vi har det, kan vi gå videre til det sidste trin: pakning!

Pakning

I browseren findes der ikke moduler (slags). Men det betyder, at der ikke er behov for funktion, og nej module.exports. Så selvom vi har alle vores afhængigheder, har vi i øjeblikket ingen måde at bruge dem som moduler.

Modulfabriksfunktion

Indtast fabriksfunktionen.

En fabriksfunktion er en funktion (det er ikke en konstruktør), der returnerer et objekt. Det er et mønster fra objektorienteret programmering, og en af ​​dens anvendelser er at foretage indkapsling og afhængighedsinjektion.

Lyder godt?

Ved hjælp af en fabriksfunktion kan vi både indsprøjte vores egen requirefunktion og et module.exportsobjekt, der kan bruges i vores medfølgende kode og give modulet sit eget anvendelsesområde.

Pakning

Følgende er pakningsfunktionen, der bruges til pakning.

Det meste af det er bare skabelonbogstaver af JavaScript, så lad os diskutere, hvad det laver.

Først op er modulesSource. Her gennemgår vi hvert af modulerne og omdanner dem til en række kilder.

Så hvordan er output for et modulobjekt?

Nu er det lidt svært at læse, men du kan se, at kilden er indkapslet. Vi leverer modulesog requirebruger fabriksfunktionen som jeg nævnte før.

Vi inkluderer også modulkortet, som vi konstruerede under afhængighedsopløsningen.

Dernæst i funktionen slutter vi os til alle disse for at skabe et stort objekt med alle afhængigheder.

Den næste kode kode er en IIFE, hvilket betyder, at når du kører denne kode i browseren (eller andre steder), kører funktionen med det samme. IIFE er et andet mønster til indkapsling af omfang og bruges her, så vi ikke forurener det globale omfang med vores requireog moduler.

Du kan se, at vi definerer to behovsfunktioner, requireog localRequire.

Require accepterer id for et modulobjekt, men kildekoden er naturligvis ikke skrevet ved hjælp af id'er. I stedet bruger vi den anden funktion localRequiretil at tage de argumenter, modulerne kræver, og konvertere dem til det korrekte id. Dette bruger disse modulkort.

Efter dette definerer vi en module object, som modulet kan udfylde, og sender begge funktioner til fabrikken, hvorefter vi vender tilbage module.exports.

Endelig kalder vi for require(0)at kræve modulet med en id på 0, som er vores indgangsfil.

Og det er det! Vores modulbundler er 100% komplet!

Tillykke! ?

Så vi har nu en fungerende modulbundter.

Dette bør sandsynligvis ikke bruges i produktionen, fordi det mangler masser af funktioner (som at administrere cirkulære afhængigheder, sørge for at hver fil kun bliver parset en gang, es-moduler osv.), Men dette har forhåbentlig givet dig en god idé om, hvordan modulbundlere fungerer faktisk.

Faktisk fungerer denne i omkring 60 linjer, hvis du fjerner al kildekoden.

Tak for læsningen, og jeg håber, du har haft et kig på, hvordan vores enkle modulbundter fungerer. Hvis du gjorde det, skal du sørge for at klappe? og del.

Denne artikel blev oprindeligt sendt på min blog.

Tjek kilden //github.com/adamisntdead/wbpck-bundler