JavaScript bundle optimalizálás 2026: tree shaking, code splitting és dinamikus import

Hogyan csökkentsd a JavaScript bundle méretét 2026-ban: tree shaking, code splitting és dinamikus import a gyakorlatban, konkrét példákkal és mérési adatokkal.

JS Bundle Optimalizálás 2026: Tree Shaking

Frissítve: 2026. június 4.

A JavaScript bundle optimalizálás 2026-ban három pillérre épül: a tree shaking eltávolítja a holt kódot a függőségi gráfból, a code splitting útvonal- és komponens-szinten darabolja a kötegeket, a dinamikus import() pedig csak akkor tölti be a kódot, amikor a felhasználónak ténylegesen szüksége van rá. Egy tipikus React vagy Vue alkalmazás kezdeti bundle-je 2 MB-ról 200–400 KB-ra csökkenthető, ha ezt a hármast helyesen alkalmazzuk, és ezzel együtt az LCP és INP mutatók is mérhetően javulnak.

Őszintén szólva, a legtöbb projektnél ahol méréseket végeztem, a bundle nagyobbik fele „véletlenül" került oda. Egyetlen rossz import, egy elfelejtett konfig sor, és máris ott van a teljes moment.js a kezdeti payload-ban. Erről szól ez a cikk.

  • A tree shaking csak ES modulokkal (ESM) működik megbízhatóan; minden CommonJS require() hívás megöli a statikus elemzést.
  • A sideEffects: false mező a package.json-ban az egyik legnagyobb hatású beállítás. Gyakran 30–40%-kal csökkenti a végső bundle méretét.
  • Az útvonal-szintű (route-based) code splitting a legmagasabb megtérülésű első lépés: minden modern keretrendszer támogatja, és azonnal harmadolja a kezdeti payload-ot.
  • A dinamikus import() Promise-t ad vissza, és webpack, Vite, Rollup, esbuild, valamint Turbopack is automatikusan külön chunk-ot készít belőle.
  • A Brotli tömörítés a Gzip-hez képest átlagosan 17–20%-kal kisebb átviteli méretet ad, és minden CDN-en be kell kapcsolni.
  • Az 50 KB-os performance budget chunk-onként reális 2026-ban; ezen felül Lighthouse CI-vel automatikusan blokkolni kell a regressziót.

Mi az a tree shaking és hogyan működik?

A tree shaking egy statikus elemzésen alapuló technika, amely a bundler dependency graph-ját végigjárva azonosítja és kivágja azokat az export-okat, amelyeket sehol nem hívnak meg az alkalmazásban. A „fa rázása" metafora arra utal, hogy a használt kód a törzs és az ágak, az érintetlen kód pedig a száraz levél, amit lerázunk. A módszer kizárólag ES modulokkal működik megbízhatóan, mert az import és export deklarációk értéke fordítási időben ismert. A CommonJS require() ezzel szemben futásidejű függvényhívás, amit a bundler nem tud megbízhatóan elemezni.

A gyakorlatban három feltétel együttes teljesülése kell ahhoz, hogy működjön. Először, a forrás- és a függőségi kódnak ESM formátumúnak kell lennie. Ezt a package.json-ban a "type": "module" vagy a "exports" mező "import" kulcsa jelzi. Másodszor, a könyvtárakat nevesített importtal kell behúzni (import { debounce } from 'lodash-es'), nem pedig namespace importtal (import * as _ from 'lodash-es'), mert utóbbi az egész modult megőrzi. Harmadszor, a build módnak production-nek kell lennie, hogy a minifier (Terser, esbuild minify) ténylegesen kivágja a megjelölt kódot.

Egy tipikus mérési adat: egy 47 KB-os UI könyvtárból, amelyből csak két komponenst használ az alkalmazás, helyes tree shaking-gel 6–8 KB marad. Helytelen konfigurációval (például "sideEffects": true) a teljes 47 KB bekerül a végső bundle-be. Pontosan ebbe a hibába futottam bele tavaly egy ügyfél projektjén: a fejlesztők hetekig kerestek 60 KB „ismeretlen" kódot, ami egyetlen rossz package.json mező miatt nem rázódott le.

Code splitting stratégiák: útvonal, komponens, vendor

A code splitting a monolitikus bundle-t több kisebb chunk-ra bontja, amelyeket a böngésző csak akkor tölt le, amikor szüksége van rájuk. Három fő stratégia létezik 2026-ban, és a legtöbb projekt mindhármat egyszerre alkalmazza.

Az útvonal-szintű splitting minden route-hoz külön chunk-ot generál. Egy 12 oldalas alkalmazásnál ez azt jelenti, hogy a kezdeti betöltés csak az adott URL kódját kéri le, nem mindet. A komponens-szintű splitting a nehéz, ritkán használt UI elemeket (modal-ok, rich text editor, charting könyvtár) külön chunk-ba szervezi. A vendor splitting pedig a stabil, ritkán változó harmadik feles függőségeket egy hosszabb cache-elhető chunk-ba teszi, hogy a saját kódunk változása ne érvénytelenítse a böngésző cache-ét.

React esetében az útvonal-szintű splitting a React.lazy és Suspense kombinációjával történik. Egy konkrét példa:

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Minden route külön chunk-ban tölt be.
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings = lazy(() => import('./routes/Settings'));
const Reports = lazy(() => import('./routes/Reports'));

export function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/reports" element={<Reports />} />
      </Routes>
    </Suspense>
  );
}

Vue 3-ban a defineAsyncComponent, Angular-ban a loadChildren, SvelteKit-ben pedig a fájl-alapú routing automatikusan biztosítja ugyanezt. A harmadik fél által beágyazott szkriptek külön optimalizálási problémakör, erről részletesen a harmadik fél szkriptek optimalizálása cikkben írtam.

Dinamikus import() a gyakorlatban

A dinamikus import() egy beépített JavaScript függvényhívás, amely Promise-t ad vissza a betöltött modullal. Ez az alap, amire minden modern code splitting épül: a bundler automatikusan külön chunk-ot generál minden import() kifejezéshez, és a hash-elt fájlnevet beleírja a runtime manifest-be. A funkció támogatva van minden 2018 utáni böngészőben, és a build idejű bundler-ek (Webpack 5+, Vite 5+, Rollup 4+, esbuild 0.20+, Turbopack) is natív szinten kezelik.

A leggyakoribb felhasználási minta nehéz UI feature betöltése interakcióra. Például egy chart könyvtár (50–150 KB), amit csak akkor van értelme letölteni, ha a felhasználó tényleg megnyit egy „Statisztikák" panelt:

// A gomb kattintásakor töltjük be a chart kódot, nem a kezdeti betöltéssel.
button.addEventListener('click', async () => {
  const { renderChart } = await import('./chart-module.js');
  renderChart(document.getElementById('chart'), data);
});

Egy fejlettebb minta a feltételes betöltés: ha a felhasználó eszköze nem támogatja a IntersectionObserver-t, betöltünk egy polyfillt; ellenkező esetben semmit nem tölt le. Az MDN dinamikus import dokumentáció részletesen kifejti a szintaktikai szabályokat és a Promise-alapú hibakezelést.

Bundler összehasonlítás: Webpack, Vite, Rollup, esbuild, Turbopack

A 2026-os bundler piacon öt fő szereplő dominál, mindegyik más erősséggel. A Webpack még mindig a legkonfigurálhatóbb és a legtöbb plugin-nel rendelkezik, de a build sebesség jelentősen elmarad a fiatalabb riválisaitól. A Vite (Rollup-alapú production build-del, esbuild-alapú dev szerverrel) a React, Vue és Svelte ökoszisztémák de facto választása lett. A Rollup tisztább tree shaking eredményt ad, és könyvtár-publikálásra ideális. Az esbuild a leggyorsabb (Go-ban írva), és kis-közepes projektekhez kiváló. A Turbopack a Next.js 15+ alapértelmezett bundlere; Rust-ban íródott, és cache-alapú inkrementális build-ekkel a Webpack 10–20-szorosát produkálja.

TulajdonságWebpack 5Vite 6Rollup 4esbuildTurbopack
NyelvJavaScriptJS + Go (esbuild)JavaScriptGoRust
Dev build (közepes projekt)15–25 s200–500 ms5–10 s50–200 ms100–400 ms
Tree shaking minőségKiváló (Rollup)KiválóKözepes
HMR sebességLassúAzonnali (ESM)n.a.GyorsAzonnali
Plugin ökoszisztémaHatalmasNagy (Rollup-kompatibilis)NagyKicsiNövekvő
Production output méretOptimálisOptimálisLegoptimálisabbOptimális
Tanulási görbeMeredekLaposKözepesLaposLapos

A választás során a legfontosabb szempont nem a build sebesség, hanem a tree shaking minősége és a production output mérete. Egy gyors build, amely 30%-kal nagyobb bundle-t ad, hosszú távon rosszabb felhasználói élményt eredményez.

A sideEffects mező és a holt kód eltávolítása

A sideEffects mező a package.json-ban az egyik legnagyobb, de leggyakrabban elfelejtett tree shaking optimalizáció. Alapértelmezetten a bundler azt feltételezi, hogy minden modul végrehajthat mellékhatásokat (CSS import, globális prototípus-bővítés, polyfill regisztráció), és emiatt nem meri kidobni — még akkor sem, ha egyetlen exportját sem használjuk. A "sideEffects": false deklaráció explicit ígéret a bundler felé, hogy a csomag tisztán importálható.

{
  "name": "my-ui-library",
  "version": "2.4.0",
  "type": "module",
  "main": "./dist/index.js",
  "module": "./dist/index.js",
  "sideEffects": false
}

Ha mégis vannak mellékhatásos fájljaid (például global CSS), pontosan azokat soroljuk fel:

{
  "sideEffects": [
    "./dist/styles/global.css",
    "./dist/polyfills/*.js"
  ]
}

A profilozó munkámban ez az egyetlen sor 30–40%-os bundle méret csökkenést hozott olyan alkalmazásoknál, amelyek korábban moment.js-t vagy lodash (nem ES) variánsokat húztak be. Egyszerűen azért, mert a sideEffects: true miatt minden használatlan locale és segédfüggvény bekerült. A Webpack hivatalos tree shaking guide-ja ezt a mezőt központi elemnek nevezi.

Bundle elemzés és a legnagyobb fogyasztók azonosítása

Optimalizálni csak azt érdemes, amit mérünk. Ezért minden bundle optimalizálási projekt első lépése egy bundle analizátor futtatása. Webpack-hez a webpack-bundle-analyzer, Vite és Rollup esetén a rollup-plugin-visualizer, esbuild-hez az esbuild-visualizer, Turbopack-hez pedig a Next.js beépített @next/bundle-analyzer a választás.

// vite.config.ts
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      filename: './dist/stats.html',
      gzipSize: true,
      brotliSize: true,
      template: 'treemap'  // 'sunburst' | 'network' | 'raw-data'
    })
  ]
});

A treemap nézet azonnal megmutatja, mely chunk-ok foglalják a legtöbb helyet. A tapasztalatom szerint a top 3 függőség mindig az alkalmazás kód 50–70%-át teszi ki. Tipikus „bűnösök": régi moment.js (67 KB), teljes lodash import (24 KB), chart.js minden adapterrel (180 KB), polyfill libraryk modern target-tel (15–40 KB feleslegesen). A trace ezeknél a chunk-oknál mindig elárulja, melyik konkrét export felel a méretért.

Nehéz könyvtárak cseréje könnyebb alternatívákra

A bundle optimalizálás leggyorsabb nyeresége gyakran nem a build konfigurációban van, hanem a függőségi listában. A legtöbb projekt 30–50%-os bundle csökkenést érhet el azzal, ha a 2014–2018 között népszerűvé vált nehéz könyvtárakat lecseréli modern, tree shaking-kompatibilis alternatívákra. Volt olyan refactor, ahol pusztán a moment.js kidobása több mint 60 KB-ot vágott le. Egy fél napos munkáért nem rossz hozam.

Régi könyvtárMéret (min)Modern alternatívaMéret (min+tree-shaken)Megtakarítás
moment.js67 KBdate-fns vagy Temporal API5–15 KB~52 KB
lodash (full)72 KBlodash-es nevesített import2–8 KB~65 KB
jQuery87 KBVanilla DOM API0 KB~87 KB
axios32 KBfetch + ky vagy wretch0–4 KB~28 KB
chart.js180 KBuPlot vagy Apache ECharts (modular)40–60 KB~120 KB
uuid5 KBcrypto.randomUUID()0 KB~5 KB

A Bundlephobia minden npm csomaghoz megmutatja a gzip-elt és brotli-elt méretet, a tree-shaken változatot, valamint az alternatívákat. Mielőtt új függőséget adsz a projekthez, érdemes ott ellenőrizni. Az LCP gyorsításához egyébként nemcsak a JS-t, hanem a képeket is érdemes felülvizsgálni; erről bővebben a képoptimalizálás 2026 (AVIF, WebP) cikkben írok.

Brotli tömörítés és CDN beállítások

A legjobb tree shaking és code splitting sem ér semmit, ha a CDN gzip-pel szolgálja ki a fájlokat 2026-ban. A Brotli tömörítés, amelyet minden modern böngésző támogat (96%+ globális lefedettség), átlagosan 17–20%-kal kisebb átviteli méretet ad a Gzip-hez képest azonos CPU költségen. A legtöbb edge CDN (Cloudflare, Fastly, Vercel, AWS CloudFront) alapértelmezetten támogatja, de gyakran csak akkor használja, ha az origin server is brotli encoding-ot ad vissza, vagy ha explicit be van kapcsolva a kompresszió szint 11-re (statikus assetekhez).

A statikus JavaScript chunkokat ideális esetben build időben tömörítjük (Vite-ban a vite-plugin-compression-nel, Webpack-ben a compression-webpack-plugin-nel), nem futási időben, mert a BROTLI_QUALITY_11 szint CPU-igényes. A pre-tömörített .br fájlokat a CDN ezután közvetlenül kiszolgálja a Content-Encoding: br headerrel. Ez a megközelítés és a tartalom-szintű cache stratégia részletesebben szerepel a TTFB optimalizálás és gyorsítótárazási stratégiák cikkben.

Performance budget és Lighthouse CI integráció

Egy optimalizált bundle pár hét alatt visszanőhet eredeti méretére, ha a regressziót nem akadályozzuk meg automatikusan. A performance budget egy explicit méret-limit, amit a CI pipeline minden PR-on érvényesít: ha túllépjük, a build elbukik. A reális 2026-os budget egy SPA-hoz: kezdeti JavaScript chunk < 170 KB (gzip), per-route lazy chunk < 50 KB, total transfer size < 1 MB.

// lighthouserc.js — Lighthouse CI konfiguráció
module.exports = {
  ci: {
    collect: {
      url: ['https://staging.example.com/', 'https://staging.example.com/dashboard'],
      numberOfRuns: 3
    },
    assert: {
      assertions: {
        'resource-summary:script:size': ['error', { maxNumericValue: 170000 }],
        'resource-summary:total:size':  ['error', { maxNumericValue: 1048576 }],
        'largest-contentful-paint':     ['error', { maxNumericValue: 2500 }],
        'interaction-to-next-paint':    ['error', { maxNumericValue: 200 }]
      }
    },
    upload: { target: 'temporary-public-storage' }
  }
};

A budget-eket egy GitHub Actions, GitLab CI vagy Vercel preview build során futtatjuk, így minden PR azonnal visszajelzést kap a méret hatásáról. A Lighthouse CI hivatalos repo kiterjedt példákat ad különböző CI rendszerekre. Az interaktivitási regressziók megfogásához érdemes ezt párosítani az INP optimalizálás 2026 cikkben leírt mérési mintákkal. A bundle méret és az INP között szoros korreláció van, mert a parse és compile fázis főszálon történik.

Gyakran ismételt kérdések

Mi a különbség a tree shaking és a code splitting között?

A tree shaking a használaton kívüli kódot távolítja el a végső bundle-ből statikus elemzés alapján, akár egyetlen fájlon belül is. A code splitting ezzel szemben több külön fájlra bontja a kódot, amelyeket a böngésző csak akkor tölt le, amikor szükség van rájuk. A két technika egymást kiegészíti: együtt alkalmazva tipikusan 50–70%-os bundle csökkenést érnek el.

Miért nem működik a tree shaking az alkalmazásomban?

A leggyakoribb okok: a függőség CommonJS formátumban publikál (nem ESM), a sideEffects mező hiányzik vagy true, namespace importot használsz nevesített helyett, vagy a build mód nem production. Ellenőrizd a package.json "type": "module" és "exports" mezőit, és futtass egy bundle analizátort.

Mekkora a maximális JavaScript bundle méret 2026-ban?

A Google PageSpeed Insights ajánlása szerint az első betöltött JavaScript ne haladja meg a 170 KB-ot gzip után, ami nagyjából 500 KB-os feldolgozott kódnak felel meg. Mobil eszközökön ez a Core Web Vitals mutatókhoz szükséges 2,5 másodperces LCP eléréséhez gyakorlatilag szükséges felső határ.

Melyik bundler ajánlott új projekthez 2026-ban?

Új React, Vue vagy Svelte SPA-hoz a Vite a legjobb választás a gyors HMR és Rollup-alapú production build miatt. Next.js projekthez a Turbopack a beépített megoldás. Könyvtár publikálásra a Rollup ad a legtisztább tree shaking eredményt, mert ESM-first és kevesebb runtime kódot generál.

Hogyan tudom méretben elemezni a Next.js bundle-emet?

Telepítsd a @next/bundle-analyzer csomagot, és csomagold be a next.config.js-t a withBundleAnalyzer wrapperrel. A ANALYZE=true npm run build parancs egy interaktív treemap HTML-t generál a .next/analyze/ mappába, ahol látható minden client és server chunk pontos mérete.

Alex Petrov
A Szerzőről Alex Petrov

Web performance engineer who treats every millisecond as a personal challenge. Has profiled more sites than he can count.