Series: JavaScript Basics Lesson 76

Code Splitting

Code splitting strategiyasi: dynamic import() orqali bundle'ni chunk'larga bo'lish, route-based splitting (React.lazy/Suspense), bundler'ning split-point va shared-vendor optimallashtirishi, va bu strategiyaning Tree Shaking'dan farqi.

76-dars

Code Splitting

Tree shaking va minifikatsiyadan keyin ham, katta zamonaviy dastur (React/Vue kabi framework + barcha sahifalar + barcha kutubxonalar) bitta bundle.js faylga yig'ilsa, bu fayl osongina 1-2 MB yoki undan katta bo'lib qolishi mumkin.

Muammo shunda: foydalanuvchi saytga kirganda, hatto u faqat bosh sahifani ko'rmoqchi bo'lsa ham, brauzer butun 2 MB'ni yuklab olishi, parse qilishi va bajarishi kerak bo'ladi — shu jumladan /admin, /settings, /checkout kabi sahifalar uchun kerak bo'lgan kod ham, garchi foydalanuvchi ularga hech qachon kirmasligi mumkin.

Code splitting — bitta katta bundle'ni kichikroq bo'laklarga (chunk) bo'lib, ularni faqat kerak bo'lganda yuklash strategiyasi.


1. Qanday ishlaydi — asosiy mexanizm: Dynamic Import

Bu yerda ESM'ning statik importidan farqli, dinamik import() funksiyasi ishlatiladi (bu — ESM ichidagi yagona dinamik mexanizm):

// Statik import — bundle qurilishida DARHOL bitta chunk'ga qo'shiladi
import { heavyChart } from './chart.js';

// Dinamik import — ALOHIDA fayl (chunk) sifatida ajratiladi,
// va faqat chaqirilganda tarmoqdan yuklab olinadi
button.addEventListener('click', async () => {
  const { heavyChart } = await import('./chart.js');
  heavyChart();
});

import('./chart.js') — bu oddiy funksiya chaqiruvi, Promise qaytaradi. Bundler (webpack/Vite) buni ko'rganda: "Aha, bu yerda dinamik import bor — demak, chart.js va uning barcha bog'liqliklarini alohida faylga (masalan, chunk-3f2a1.js) chiqarib qo'yaman, va asosiy bundle'ga faqat uni so'rov yuborish kodini qoldiraman."


2. Natijada nima hosil bo'ladi — fayl darajasida

Build'dan keyin siz papkada shunga o'xshash fayllarni ko'rasiz:

dist/
  main.js         ← asosiy kod (kichik, tez yuklanadi)
  vendor.js       ← React/Vue kabi katta kutubxonalar (alohida keshlanadi)
  chunk-a1b2.js   ← faqat /admin sahifasi uchun kod
  chunk-c3d4.js   ← faqat chart komponenti uchun kod

Brauzer boshida faqat main.jsni yuklaydi. Foydalanuvchi /adminga o'tganda yoki chart ko'rsatiladigan tugmani bosganda, brauzer yangi <script> tegini dinamik ravishda DOM'ga qo'shadi (bu ishni bundler'ning runtime kodi avtomatik bajaradi) va tegishli chunk'ni tarmoqdan yuklab oladi.


3. Route-based splitting — eng ko'p ishlatiladigan amaliyot

React'da odatda sahifalar (route) bo'yicha bo'linadi:

import { lazy, Suspense } from 'react';

const AdminPage = lazy(() => import('./AdminPage.jsx'));
const SettingsPage = lazy(() => import('./SettingsPage.jsx'));

function App() {
  return (
    <Suspense fallback={<div>Yuklanmoqda...</div>}>
      <Routes>
        <Route path="/admin" element={<AdminPage />} />
        <Route path="/settings" element={<SettingsPage />} />
      </Routes>
    </Suspense>
  );
}

React.lazy() — ichida import() chaqiruvi bo'lgan komponentni "kechiktirilgan" qilib beradi. Suspense — bu komponent hali yuklanayotgan paytda (tarmoq so'rovi tugamaguncha) nima ko'rsatishni belgilaydi (fallback).

Bu — oldingi bosqichda o'tgan Lazy loading g'oyasining komponent/route darajasidagi ko'rinishi (o'sha safar rasm/elementlar uchun Intersection Observer orqali edi, bu yerda esa butun JS moduli uchun).


4. Bundler ichida qanday algoritm ishlaydi (Webpack misolida)

  1. Grafni qurish — bundler avval statik va dinamik importlarning barchasini o'qib, module graph'ni tuzadi (tree shaking'dagi bilan bir xil bosqich).
  2. "Split point"larni aniqlash — har bir import() chaqiruvi — bu grafda "bo'linish nuqtasi". Bundler shu nuqtadan boshlab kerak bo'ladigan barcha modullarni alohida chunk'ka ajratadi.
  3. Umumiy bog'liqliklarni optimallashtirish (SplitChunksPlugin) — agar ikkita turli chunk bir xil kutubxonani (masalan, ikkalasi ham lodash ishlatsa) import qilsa, bundler buni aqlli tarzda aniqlab, alohida umumiy chunk'ka chiqaradi — aks holda lodash ikki marta (ikkala chunk'da) takrorlanib, umumiy hajm ortib ketardi.
Agar chunk-A va chunk-B ikkalasi ham lodash ishlatsa:
  ❌ Yomon: chunk-A (lodash + A kodi), chunk-B (lodash + B kodi) — lodash 2x
  ✅ Yaxshi: chunk-A (faqat A), chunk-B (faqat B), shared-vendor (lodash, bitta marta)

5. Brauzer/V8 darajasida nima sodir bo'ladi

Bu qism sof build-time strategiya bo'lsa-da, uning ishlashi to'g'ridan-to'g'ri brauzerning tarmoq va parsing xatti-harakatiga bog'liq:

  1. Dinamik import('./chart.js') chaqirilganda, bundler runtime kodi (masalan, Webpack'ning __webpack_require__.e()) yangi <script> elementini yaratib, uni document.headga qo'shadi.
  2. Brauzer bu faylni alohida HTTP so'rov orqali yuklaydi (agar u allaqachon keshda bo'lmasa).
  3. Fayl yuklangach, V8 uni parse qiladi va bajaradi — bu yerda V8 lazy parsing deb ataladigan optimallashtirishni qo'llaydi: katta fayl kelganda, V8 darhol ichki funksiyalarning to'liq tanasini parse qilmaydi, faqat ularning "shakli" (nomi, parametrlari)ni saqlab, haqiqiy chaqirilganda to'liq parse qiladi. Bu — Code splitting bilan bevosita bog'liq bo'lmasa-da, kichikroq chunk kelganda umumiy parse+compile vaqti kamayishiga qo'shimcha yordam beradi.
  4. Promise resolve bo'lgach (await import(...)), modul obyekti qaytariladi va sizning kodingiz davom etadi.

Nega bu Tree shaking'dan farq qiladi (chalkashtirmaslik uchun)

Tree Shaking Code Splitting
Nima qiladi Ishlatilmagan kodni butunlay o'chiradi Ishlatiladigan kodni guruhlarga bo'ladi, hammasi saqlanadi
Maqsad Umumiy hajmni kamaytirish Boshlang'ich yuklash vaqtini kamaytirish (kerak bo'lmagan qismni keyinga qoldirish)
Natija Kod umuman yo'q bo'ladi Kod bor, lekin boshqa faylda, kerak bo'lganda yuklanadi

Ikkalasi ham birga ishlatiladi va bir-birini to'ldiradi — avval tree shaking ortiqcha narsani o'chiradi, keyin qolgan kerakli kod code splitting orqali oqilona bo'laklarga bo'linadi.