Teknologian jakaminen

Webpack: Kolmen Chunk-tuotteen pakkauslogiikka

2024-07-08

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Yleiskatsaus

  • edellisessä artikkelissa Webpack: Dependency Graph hallitsee moduulien välisiä riippuvuuksia , olemme selittäneet yksityiskohtaisesti, kuinka "koonti"-vaihe alkaa Entrystä ja lukee ja jäsentää asteittain rekursiivisesti moduulin sisällön ja muodostaa lopuksi moduulin riippuvuusgraafin - ModuleGraph-objektin. Tässä artikkelissa selitämme edelleen, kuinka ModuleGraph-sisältöön perustuvia paloja järjestetään seuraavassa "kapselointi"-vaiheessa, ja kehitämme edelleen ChunkGroup- ja ChunkGraph-riippuvuusobjektien pääprosessia.

Pääprosessin lisäksi selitämme yksityiskohtaisesti useita epämääräisiä käsitteitä:

  • Mitä ovat Chunk-, ChunkGroup- ja ChunGraph-objektit? Mikä on niiden välinen vuorovaikutus?
  • Webpackin oletusalipakkaussäännöt ja sääntöjen ongelmat.

ChunkGraph-rakennusprosessi

edessä Init, Make, Seal"Vuorossa ", olemme esittäneet, että Webpackin taustalla oleva rakennuslogiikka voidaan karkeasti jakaa: "Alustus, rakentaminen, pakkaus"kolme vaihetta:

Lisää kuvan kuvaus tähän

sisään,"Rakentaa"Vaihe on vastuussa moduulien välisten riippuvuuksien analysoinnista ja niiden määrittämisestä Riippuvuuskaavio(ModuleGraph);kapselointi”-vaiheessa riippuvuusgraafin perusteella moduulit kapseloidaan erikseen useiksi Chunk-objekteiksi ja Chunkien väliset ylä-lapsi-riippuvuussuhteet lajitellaan ChunkGraph- ja useisiin ChunkGroup-objekteihin.

"Kapselointi"-vaiheen tärkein tavoite on rakentaa ChunkGraph-suhdekaavio "koonti"-vaiheessa kerätyn ModuleGraph-suhdegraafin perusteella. Tämän prosessin logiikka on suhteellisen monimutkainen:

Lisää kuvan kuvaus

Analysoidaan tässä lyhyesti useiden tärkeiden vaiheiden toteutuslogiikkaa.

Ensimmäinen askel on erittäin kriittinen: siirtääseal() Tee toiminnon jälkeen poikkientry Määritys, luo tyhjä jokaiselle merkinnälleChunk jaSisääntulopiste esine (erityinenChunkGroup) ja määritä ensin perusasetukset ChunkGraph Rakennesuhde, valmistaudu seuraavaan vaiheeseen, avainkoodi:

class Compilation {
  seal(callback) {
    // ...
    const chunkGraphInit = new Map();
    // 遍历入口模块列表
    for (const [name, { dependencies, includeDependencies, options }] of this
      .entries) {
      // 为每一个 entry 创建对应的 Chunk 对象
      const chunk = this.addChunk(name);
      // 为每一个 entry 创建对应的 ChunkGroup 对象
      const entrypoint = new Entrypoint(options);
      // 关联 Chunk 与 ChunkGroup
      connectChunkGroupAndChunk(entrypoint, chunk);

      // 遍历 entry Dependency 列表
      for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {
        // 为每一个 EntryPoint 关联入口依赖对象,以便下一步从入口依赖开始遍历其它模块
        entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request);

        const module = this.moduleGraph.getModule(dep);
        if (module) {
          // 在 ChunkGraph 中记录入口模块与 Chunk 关系
          chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
          // ...
        }
      }
    }
    // 调用 buildChunkGraph 方法,开始构建 ChunkGraph
    buildChunkGraph(this, chunkGraphInit);
    // 触发各种优化钩子
    // ...
  }
}

Kun suoritus on valmis, muodostetaan seuraava tietorakenne:
Lisää kuvan kuvaus tähän

Toiseksi, jos se on määritetty tällä hetkellä entry.runtime, Webpack tarjoaa myös ajonaikaisen koodin tässä vaiheessa luoda Vastaava pala ja suoraanjakaa Antaaentry vastaavaChunkGroup esine.Soitetaan kun kaikki on valmistabuildChunkGraph toiminto, siirry seuraavaan vaiheeseen.

Vaihe kaksi: olla olemassabuildChunkGraph toiminnon sisälläsiirtää visitModules Funktio, kulje ModuleGraph ja osoita kaikki moduulit eri moduuleille niiden riippuvuuksien mukaan.Chunk Objekti, jos se havaitaan tämän prosessin aikanaasync-moduuli, sitten moduuli luodaUusi ChunkGroup jaChunk Objekti, joka lopulta muodostaa seuraavan tietorakenteen:
Lisää kuvan kuvaus

kolmas vaihe: olla olemassabuildChunkGraph toiminnassasiirtää connectChunkGroups menetelmä, perustaaChunkGroup välillä,Chunk toistensa välisiä riippuvuuksia luodakseen täydellisenChunkGraph Objekti, joka lopulta muodostaa seuraavan tietorakenteen:
Lisää kuvan kuvaus

neljäs vaihe: olla olemassabuildChunkGraph toiminnassasiirtää cleanupUnconnectedGroups Menetelmä, puhdistus on virheellinenChunkGroup, vaikuttaa pääasiassa suorituskyvyn optimointiin.

Kun olet käynyt nämä neljä vaihetta ylhäältä alas,ModuleGraph Kohteeseen tallennetut moduulit määritetään kolmelle eri Chunk-objektille: Entry, Async ja Runtime moduulin luonteen mukaan, ja Chunks-riippuvuudet tallennetaan ChunkGraph- ja ChunkGroup-kokoelmiin. Voit jatkaa näiden objektien perusteella muuttaa myöhemmin alihankintakäytäntöjä (esim.SplitChunksPlugin), toteuttaa alihankintaoptimoinnin uudelleenorganisoimalla ja jakamalla Moduuli- ja Chunk-objektien omistajuutta.

Chunk vs ChunkGroup vs ChunkGraph

Yllä oleva rakennusprosessi sisältää kolme avainobjektia: Chunk, ChunkGroup ja ChunkGraph. Tehdään ensin yhteenveto niiden käsitteistä ja toiminnoista ymmärryksemme syventämiseksi:

  • Chunk: Moduulia käytetään lukemaan moduulien sisältöä, tallentamaan moduulien välisiä riippuvuuksia jne., kun taas Chunk yhdistää useita moduuleja moduuliriippuvuuksien perusteella ja tulostaa ne omaisuustiedostoiksi (tuotteiden yhdistämisen ja tulostamisen logiikka selitetään seuraavassa luvussa):

Lisää kuvan kuvaus

  • ChunkGroup:yksi ChunkGroup sisältää yhden tai useammanChunk esine;ChunkGroup jaChunkGroup Vanhemman ja lapsen riippuvuussuhde muodostuu:

Lisää kuvan kuvaus

  • ChunkGraph: Lopuksi Webpack tallentaa Chunks- ja ChunkGroupsien väliset riippuvuudet sisään compilation.chunkGraph Objektissa muodostetaan seuraavat tyyppisuhteet:
    Lisää kuvan kuvaus

Oletusalihankintasäännöt

Ylempi ChunkGraph Rakennusprosessi järjestää lopulta moduulin kolmeen eri tyyppiin:

  • Sisäänpääsyosa: sama entry Alemmalla kosketuksella saavutetut moduulit on järjestetty osaksi;
  • Asynkroninen osa: Asynkroninen moduuli on järjestetty erikseen osaksi;
  • Kestoaika:entry.runtime Kun ajonaikainen moduuli ei ole tyhjä, se järjestetään osaksi.

Tämä on sisäänrakennettu Webpackiin, jos sitä ei käytetä splitChunks Muiden lisäosien tapauksessa oletussäännöt moduulin syötteen ja tulosteen yhdistämiselle ovat yksi Webpackin tärkeimmistä perusperiaatteista, joten on tarpeen ottaa käyttöön kunkin Chunkin erityiset säännöt.

Sisäänpääsyosa:

Alkaen Entry Chunkista, Webpack ensin entry luodaChunk Objektoi esimerkiksi seuraava kokoonpano:

module.exports = {
  entry: {
    main: "./src/main",
    home: "./src/home",
  }
};

Traverse entry objektin ominaisuudet ja luochunk[main]chunk[home] Kaksi esinettä, jotka tällä hetkellä sisältävät vastaavasti kaksi kappalettamainhome Moduuli:
Lisää kuvan kuvaus tähän

Kun alustus on valmis, Webpack tekee sen ModuleGraph riippuvuustiedot, testamenttientry Kaikki moduulit, joihin seuraavat koskettavat, on täytetty Chunkiin (estyyvisitModules menetelmä), esimerkiksi seuraaville tiedostoriippuvuuksille:
Lisää kuvan kuvaus tähän

main.js Jos neljään tiedostoon a/b/c/d viitataan suoraan tai epäsuorasti synkronisesti, Webpackmain.js Moduuli luo Chunk- ja EntryPoint-objekteja ja lisää sitten vähitellen a/b/c/d-moduulejachunk[main] , lopulta muodostuu:
Lisää kuvan kuvaus tähän

Asynkroninen osa:

Toiseksi Webpack kääntää jokaisen asynkronisen tuontilausekkeen (import(xxx) jarequire.ensure ) käsitellään erillisenä Chunk-objektina, ja kaikki sen alimoduulit lisätään tähän Chunkiin - kutsumme sitä Async Chunkiksi. Esimerkiksi seuraavaa esimerkkiä varten:

// index.js
import './sync-a.js'
import './sync-b.js'

import('./async-a.js')

// async-a.js
import './sync-c.js'

Sisääntulomoduulissa index.js Synkron-a ja sync-b otetaan käyttöön synkronisesti, samalla kun async-a-moduuli otetaan käyttöön synkronisesti;sync-c moduuli, muodostaen seuraavan moduuliriippuvuuskaavion:
Lisää kuvan kuvaus

Tässä vaiheessa Webpack on sisääntulopiste index.js, asynkroninen moduuli async-a.js Luo alipaketit erikseen muodostaaksesi seuraavan palarakenteen:
Lisää kuvan kuvaus tähän

ja chunk[index] jachunk[async-a] Niiden välille muodostuu yksisuuntainen riippuvuus, ja Webpack tallentaa tämän riippuvuudenChunkGroup._parentsChunkGroup._children kiinteistöissä.

Ajonaikainen osa:

Lopuksi paitsi entry , Asynkronisten moduulien lisäksi Webpack5 tukee myös ajonaikaisen koodin purkamista erikseen paloiksi. Tässä mainittu ajonaikainen koodi viittaa joukkoon Webpackin syöttämiä peruskehyskoodeja varmistaakseen, että pakattu tuote voi toimia normaalisti. Esimerkiksi yleinen Webpack-pakatun tuotteen rakenne on seuraava:
Lisää kuvan kuvaus
Yllä olevan kuvan punaisessa laatikossa ympyröity suuri koodiosa on Webpackin dynaamisesti luoma ajonaikainen koodiDependency alaluokka), esimerkiksi:

  • tarve __webpack_require__.f__webpack_require__.r ja muut toiminnot modulaarisen vähimmäistuen saavuttamiseksi;
  • Jos käytät dynaamista latausominaisuutta, sinun on kirjoitettava __webpack_require__.e toiminto;
  • Jos käytät Module Federation -ominaisuutta, sinun on kirjoitettava __webpack_require__.o toiminto;
  • jne.

Vaikka jokainen ajonaikaisen koodin osa voi olla pieni, ominaisuuksien lisääntyessä lopputuloksesta tulee suurempi ja suurempi Varsinkin usean merkinnän sovelluksissa on vähän turhaa pakata toistuvasti samanlainen ajonaika jokaiselle merkinnälle entry.runtime Määrityskohteita käytetään määrittämään, kuinka ajonaikainen koodi on pakattu.Käyttöä varten vain käytäentry Lisää merkkijonomuoto kohteeseenruntime arvo, esimerkiksi:

module.exports = {
  entry: {
    index: { import: "./src/index", runtime: "solid-runtime" },
  }
};

olla olemassa compilation.seal Toiminnossa Webpack ensin onentry luodaEntryPoint, sitten tuomitse entry Sisältääkö kokoonpanoruntime Jos sellaisia ​​on, luo ne käyttämälläruntime Arvo on kappaleen nimi. Siksi yllä oleva esimerkkikokoonpano luo kaksi kappaletta:chunk[index.js]chunk[solid-runtime], ja tämän perusteella tulostetaan lopulta kaksi tiedostoa:

  • Vastaa sisäänpääsyindeksiä index.js asiakirja;
  • Ajonaikaiset asetukset vastaavat solid-runtime.js asiakirja.

monessa entry Skenaariossa vain jokaiselleentry Kaikki asetettu samaanruntime arvoa, Webpack-ajonaikainen koodi yhdistetään ja kirjoitetaan samaan Runtime Chunkiin, mikä lopulta saavuttaa tuotteen suorituskyvyn optimoinnin. Esimerkiksi seuraavalle kokoonpanolle:

module.exports = {
  entry: {
    index: { import: "./src/index", runtime: "solid-runtime" },
    home: { import: "./src/home", runtime: "solid-runtime" },
  }
};

Sisäänkäynti indexhome jakaa samaaruntime arvoa ja luo lopuksi kolme kappaletta:
Lisää kuvan kuvaus

Sisäänkäynti tähän aikaan chunk[index]chunk[home] suoritusajan kanssachunk[solid-runtime] Muodostuu myös vanhemman ja lapsen huoltosuhde.

Alihankintasääntöjä koskevat ongelmat

Suurin ongelma oletusalipakkaussääntöjen kanssa on, että se ei pysty ratkaisemaan moduulien päällekkäisyyttä. Jos useat osat sisältävät saman moduulin samanaikaisesti, tämä moduuli pakataan toistuvasti näihin osiin ilman rajoituksia. Oletetaan esimerkiksi, että meillä on kaksi merkintää pää/indeksi, jotka molemmat riippuvat samasta moduulista:
Lisää kuvan kuvaus tähän

Oletuksena Webpack ei käsittele tätä lisäkäsittelyä, vaan yksinkertaisesti pakkaa c-moduulin pää-/indeksiosaan samanaikaisesti kahteen osaan, jolloin lopulta muodostuu:

Lisää kuvan kuvaus tähän

voidaan nähdä chunk on eristetty toisistaan, moduuli c pakataan toistuvasti, mikä voi aiheuttaa tarpeettomia suorituskyvyn menetyksiä lopputuotteelle!

Tämän ongelman ratkaisemiseksi esiteltiin Webpack 3 CommonChunkPlugin Plugin yrittää purkaa yhteiset riippuvuudet merkintöjen välillä erillisiksichunk,mutta CommonChunkPlugin Se toteutetaan pääosin yksinkertaisen vanhemman ja lapsen välisen suhdeketjun perusteella. On vaikea päätellä, että purettua kolmatta pakettia tulisi käyttääentry isächunk Vielä lapsichunkCommonChunkPlugin yhtenäinen käsittely vanhempinachunk, joissakin tapauksissa sillä on huomattava negatiivinen vaikutus suorituskykyyn.

Tästä syystä Webpack 4:n jälkeen otettiin käyttöön monimutkaisempia tietorakenteita. ChunkGroup Erikoistunut suhdeketjun hallinnan ja yhteistyön toteuttamiseenSplitChunksPlugin voidaan toteuttaa tehokkaammin ja älykkäämminHeuristinen alihankinta.

Tee yhteenveto

Yhteenvetona voidaan todeta, että "Build"-vaihe vastaa ModuleGraphin rakentamisesta moduulin viitesuhteen perusteella. "Encapsulation"-vaihe on vastuussa ModuleGraphiin perustuvien Chunk-objektien sarjan rakentamisesta ja osien välisten riippuvuuksien järjestämisestä (asynkroniset viittaukset, ajonaika); ) ChunkGraph - Chunk Dependency -graafiobjektiin. Samoin kuin ModuleGraph, ChunkGraph-rakenteen käyttöönotto voi myös irrottaa Chunksien välisten riippuvuuksien hallintalogiikan, mikä tekee kokonaisarkkitehtuurilogiikasta järkevämmän ja helpommin laajennettavan.

Kuitenkin, vaikka se näyttää hyvin monimutkaiselta, "pakkaus"-vaiheen tärkein tavoite on silti määrittää kuinka monta kappaletta on ja mitkä moduulit sisältyvät jokaiseen palaan - nämä ovat avaintekijöitä, jotka todella vaikuttavat lopulliseen pakkaustulokseen.

Tätä varten meidän on ymmärrettävä Webpack5:n kolme sisäänrakennettua alihankintasääntöä: Entry Chunk, Async Chunk ja Runtime Chunk Nämä ovat alkuperäisin alihankintalogiikka Muut laajennukset (kuten splitChunksPlugin) perustuvat kaikki tähän, avulla buildChunkGraph Erilaiset koukut, jotka laukesivat myöhemmin edelleen jakavat, yhdistävät ja optimoivat Chunk-rakennetta laajentaakseen alihankintavaikutuksia.

ajatella Chunk Tuotetaanko vain yksi tuotetiedosto? Miksi?mini-css-extract-pluginfile-loader Miten tämän tyyppinen komponentti, joka voi kirjoittaa lisätiedostoja, on toteutettu alareunassa?