kód splityngTakédělení kódu, lazy loading modulů, dynamický importPokročilý

Definice

Code splitting je technika, při které se JavaScriptový balík aplikace rozdělí na menší soubory (chunky), jež prohlížeč stahuje až ve chvíli, kdy jsou skutečně potřeba. Místo jednoho velkého bundlu se při prvním načtení přenese jen kód nutný pro zobrazenou stránku, zbytek dorazí později na vyžádání.

Kategorie: Webové technologieAktualizováno
<h2>Proč se bundle vůbec dělí</h2> <p>Bundlery jako Webpack, Vite, Rollup nebo esbuild sestaví z desítek modulů jeden soubor, aby prohlížeč nemusel řešit stovky HTTP požadavků. U rostoucí aplikace ale tenhle soubor naroste do stovek kilobajtů a uživatel čeká na stažení, parsování a spuštění kódu, ze kterého na úvodní obrazovce použije zlomek. Code splitting tenhle kompromis rozvolní: bundler vytvoří víc menších chunků a runtime si je dotahuje podle potřeby.</p> <h2>Podle čeho se určují dělicí body</h2> <p>Hranice chunku vzniká tam, kde se v kódu objeví dynamický <code>import()</code>. Bundler ho pozná staticky při buildu a vše, co za ním visí, přesune do samostatného souboru. Nejčastější hranice v praxi:</p> <ul> <li><strong>Route</strong>: každá stránka routeru má vlastní chunk. Nejlepší poměr přínosu a práce.</li> <li><strong>Komponenta</strong>: modál, editor formátovaného textu, graf, mapa. Kód, který většina návštěvníků nikdy neotevře.</li> <li><strong>Vendor</strong>: knihovny třetích stran zvlášť, aby jejich chunk zůstal v cache i po nasazení nové verze aplikačního kódu.</li> <li><strong>Podmíněná větev</strong>: polyfill, admin sekce, lokalizační data pro jazyk, který uživatel zvolil.</li> </ul> <h2>Co dělení stojí</h2> <p>Cena za menší první load je latence později. Když si uživatel klikne na sekci, jejíž chunk se teprve stahuje, uvidí spinner nebo prázdný prostor. Proti tomu se pracuje s prefetchem: chunk se stáhne s nízkou prioritou, jakmile je hlavní vlákno volné, nebo při najetí myší na odkaz. Frameworky jako Next.js to u routovaných stránek dělají samy.</p> <p>Druhá cena je duplikace. Když dva chunky sdílejí stejnou knihovnu a bundler ji nevytáhne do společného chunku, stáhne se dvakrát. Nastavení sdílených chunků (ve Webpacku <code>splitChunks</code>) tohle řeší, ale příliš agresivní dělení zase vyrobí desítky drobných souborů, u kterých režie požadavku převáží nad ušetřenými bajty. Rozumný cíl je pár chunků v řádu desítek kilobajtů, ne padesát chunků po jednom kilobajtu.</p> <p>Třetí položkou je složitost stavu: každý líně načítaný celek potřebuje ošetřit načítací stav i chybu (výpadek sítě mezi nasazeními znamená 404 na starý chunk). Bez toho vzniká <a href="/slovnik/technicky-dluh">technický dluh</a>, který se projeví až v produkci.</p> <h2>Jak poznat, že dělení dává smysl</h2> <p>Rozhoduje se podle dat, ne podle pocitu. Analyzátor bundlu ukáže, které moduly zabírají nejvíc místa; typickými kandidáty jsou grafové knihovny, WYSIWYG editory, mapové SDK, PDF generátory a moment-like knihovny s daty o časových zónách. Pokud celý bundle váží 80 kB po kompresi, dělení nepřinese nic měřitelného a jen zkomplikuje kód. Metrikou úspěchu je Largest Contentful Paint a doba do interaktivity na pomalém mobilním připojení, nikoli počet vygenerovaných souborů.</p> <h2>Vztah k dalším optimalizacím</h2> <p>Code splitting se často zaměňuje za tree shaking. Tree shaking odstraní kód, který se nikde nepoužívá, code splitting odloží kód, který se používá, ale ne hned. Obě techniky se doplňují a nasazují společně s kompresí (Brotli), dlouhodobou cache podle hashe v názvu souboru a serverovým renderováním.</p>

Příklady z praxe

  1. Odložení editoru textu do vlastního chunku

    Administrace e-shopu používá bohatý WYSIWYG editor, který sám o sobě váží přes 200 kB. Editor se otevře jen na stránce úpravy produktu, takže se importuje dynamicky. Úvodní bundle se zmenší a editor se stáhne až při prvním otevření detailu, ideálně s prefetchem po dokončení hydratace.

    const Editor = React.lazy(() => import('./RichTextEditor'));
    
    function ProductForm() {
      return (
        <React.Suspense fallback={<p>Načítám editor…</p>}>
          <Editor />
        </React.Suspense>
      );
    }
  2. Chunk pro jazykovou mutaci

    Vícejazyčná aplikace má překlady pro osm jazyků, dohromady stovky kilobajtů JSONu. Statický import by dostal všechny do hlavního bundlu. Dynamický import podle zvoleného locale stáhne jen jeden soubor a bundler z každé větve udělá samostatný chunk.

    async function loadMessages(locale) {
      const module = await import(`./locales/${locale}.json`);
      return module.default;
    }

Časté omyly

MýtusČím víc chunků, tím rychlejší aplikace.
Ve skutečnostiKaždý chunk znamená vlastní požadavek, vlastní hlavičky a vlastní kolo zpracování. Nad určitou hranicí režie převáží úsporu bajtů a stránka se načítá pomaleji než s jedním rozumně velkým bundlem.
MýtusCode splitting a tree shaking je totéž.
Ve skutečnostiTree shaking při buildu vyhodí nepoužitý kód úplně. Code splitting nic nemaže, jen přesune používaný kód do souboru, který se stáhne později. Techniky se používají současně.
MýtusStačí dynamický import a je hotovo.
Ve skutečnostiLíné načítání zavádí nový stav aplikace: čekání a selhání. Bez fallbacku, ošetření chyby a strategie prefetchu se zpomalí odezva na klik a po nasazení nové verze mohou staré chunky vracet 404.

Časté dotazy

Od jaké velikosti bundlu má code splitting smysl?
Code splitting začíná dávat měřitelný smysl u aplikací, kde JavaScript po kompresi přesahuje zhruba dvě až tři stovky kilobajtů, nebo kde jedna konkrétní knihovna tvoří výraznou část balíku. U malých webů s několika desítkami kilobajtů skriptu je přínos v řádu milisekund, zatímco kód se zesložití o načítací stavy a ošetření chyb. Rozhodujte podle analyzátoru bundlu a podle naměřeného Largest Contentful Paint na pomalém mobilním připojení, ne podle obecného doporučení.
Jak zabránit blikání spinneru při přechodu mezi stránkami?
Blikání spinneru řeší prefetch: chunk pro cílovou stránku se stáhne dřív, než na ni uživatel klikne. Běžné spouštěče jsou najetí myší nebo dotyk na odkaz, viditelnost odkazu ve výřezu a nečinnost hlavního vlákna. Next.js a podobné frameworky prefetch odkazů v routeru dělají automaticky. Pomáhá také ponechat starou obrazovku vykreslenou po dobu krátkého načítání a spinner zobrazit až po zpoždění zhruba dvou set milisekund, aby u rychlého připojení nikdy neproblikl.
Proč se po nasazení nové verze objevují chyby při načítání chunku?
Chyby při načítání chunku vznikají tím, že uživatel má v prohlížeči otevřenou starou verzi aplikace, která odkazuje na soubory s hashem staré sestavy. Po nasazení a smazání starých souborů z CDN vrátí požadavek 404 a dynamický import selže. Řešením je ponechat několik předchozích sestav na serveru po dobu jejich životnosti, zachytit chybu importu a nabídnout znovunačtení stránky, případně automaticky detekovat novou verzi a stránku obnovit.
Řeší HTTP/2 potřebu code splittingu?
HTTP/2 zlevnil paralelní požadavky, takže velký počet chunků není tak drahý jako dřív, ale samotnou příčinu neodstraňuje. Hlavní náklad velkého bundlu není přenos, nýbrž parsování a spouštění JavaScriptu na CPU mobilního zařízení. Ten se multiplexováním nezmenší. Code splitting proto zůstává relevantní i na HTTP/2 a HTTP/3, jen se posouvá optimální velikost chunku a je únosnější mít jich víc.

Zdroje

  1. JavaScript modules(otevře se v novém okně)MDN Web Docs
  2. import()(otevře se v novém okně)MDN Web Docs
  3. Lazy Loading Components(otevře se v novém okně)React
  4. ECMAScript Language Specification(otevře se v novém okně)Ecma International

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.