Code splitting u React

. . .
image

Šta je code splitting ?

Code splitting je odvajanje upakovanog koda u manje komade koda da bi ih mogli download odvojeno i koristiti samo dijelove koda koji nam treba. U React i Javascript frameworcima generalo kode je upoakovan u jedan veliki fajl. I to olasava da se doda u HTML koristeći samo jedan link. Ovo također umanjue pozive prema serveru, i u teoriji bi trebalo da ubrza otvaranje aplikacije i umanji saobračaj za tu aplikaciju. Ali ako radimo na toj aplikaciji i dodajemo nove fajlove taj jedan Javascript fajl vremenom postaje veći i veći, što znači da aplikacija postaje sporija i sporija. Inicijalno, jedan fajl je bio idealan, ali nakon što dodamo više fajlova, aplikacija usporava. Tako da moramo nađemo riješenje da ubrzamo otvaranje aplikacije i to nam omogučava Code splittin.

Zašto nam treba code splitting ?

Mi koristimo Code splitting da razdvojimo kode i dependencies u više fajlova tako da možemo koristiti samo kod koji nam treba u određenom trenutku. Kada razmislite vidite da nam ustvari ne trebaju sve komponente odjednom, jer imamo više stranica, imamo više komponenti i download ih sve kada otvorimo našu aplikaciju, čak i ako korisnik neče posjetiti admin stranicu ili mozda report stranicu, ali ćemo ih ipak download sa tim jednim Javascript fajlom. Tako da kad aplikacije dođe do trenutka kada počne da usporava trebamo početi razmišljati o code splitting, ili razmišljati od početka. Možemo dinamički importovati komponente koje nam ne trebaju u određenom trenutku, kada npr. otvorimo home stranicu, download samo komponente i kode koji nam je potreban za home stranice. Tako da ne moramo download sve da bi prikazali samo par njih.

NOTE: Više studija su pokazale da stranice koje imaju brz odaziv ostaljaju bolje impresije na korisnike, i da su veće šanse da će se taj korisnik ponovo vratiti na našu aplikaciju Link studija

Kako možemo koristiti code splitting ?

Ukoliko koristimo create-react-app, Next.js ili Gatsby, ili neke slične alate, imamo code-splitting već namješten koristeći webpack. Ako koristite neki custom alat, ili ste sami kreirali svoj setup, možete dodati sami code-splitting u svoju aplikaciju. Ovdje je webpack link koji objašnjava kako postaviti code-splitting i lazy loading u vaš projekat. Ali može se postaviti koristeći bilo koji bundler kao npr. Rollup, Browersify ili Parcel ili bilo koji drugi bundler.

Imamo par načina da dodamo code-splitting u naš React projekat:

Dynamic import()

Dynamic import je dodan u Javascript ES&, i podržava nativno code-splitting u našem Javascript kodu. Sa dynamic import je lako dodati code-splitting:

Prije:

import { Header } from './Header';

<Header />;

Poslije:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    import('./Header')
      .then((component) => {
        setHeader({ Component: component.default });
      })
      .catch((err) => {
        // Handle failure
      });
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Ili sa async / await:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    (async () => {
      const component = await import('./Header');
      setHeader({ Component: component.default });
    })();
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Ili ako želite koristiti Named Export umjesto Default Export kao u primjeru iznad, možete uraditi ovako:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    import('./Header')
      .then(({ Header }) => {
        setHeader({ Component: Header });
      })
      .catch((err) => {
        // Handle failure
      });
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Ili sa async / await:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    (async () => {
      const { Header } = await import('./Header');
      console.log(Header);
      setHeader({ Component: Header });
    })();
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Kako što možete vidjeti veoma je lako, samo je sintaksa malo čudna. I imamo također drugi način za code-splitting u React:

React.lazy() and React.Suspense

React v16.6.0 je dodao mogučnost da koristimo code-splitting u React, dodali su dva nova featura React.lazy() i React.Suspense koji nam omogučavaju da koristimo code-splittin direktno u React.

React.lazy()

NOTE: U vrijeme pisanja ovoga bloga, Rreact.lazy() i Suspense nisu dostupni za server-side rendering, ali bi trebali biti nekad u budućnosti. Za sada možemo koristiti Loadable Components da imamo code-splitting opcju za server-side rendering.

React.lazy na omogućava da dinamički importamo komponente, npr:

Prije:

import React from 'react';
import Header from './Header';

export default function App() {
  return (
    <div className='App'>
      <Header />
    </div>
  );
}

Poslije:

import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./Header'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
      </Suspense>
    </div>
  );
}

Ovdje možete vidjeti koliko je lakše ovo koristiti nego Dynamic import, samo importamo lazy i Suspense iz React i koristimo ih da lazy importujemo komponente, i onda koristimo Suspense da prikažemo neku poruku dok čekamo da se load naše komponente. Nemojte zaboraviti staviti lazy importe unutar Suspense, jer React očekuje da ima neku pporuku dok se čeka. Također možemo koristit jedan Suspense za vise lazy loading komponenti:

import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./Header'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
        <Footer />
      </Suspense>
    </div>
  );
}

NOTE: ⚠️ U vrijeme kada se pisao ovaj post, React.lazy podržava samo Default Exports, i ako želite koristiti and Named Exports možete kreirati intermediate module koji moze da reexporta kao defaultnu komponentnu. Ovo omogučava da tree shaking radi kao što je očekivano i da se ne povuku nekorištene komponente.

import React from 'react';

export const Header = () => {
  return <div>Header</div>;
};
export { Header as default } from './Header';
import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./ExportDefault'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
        <Footer />
      </Suspense>
    </div>
  );
}

Ovo postane malo naporno jer morate kreirati dodatnu komponentu samo da bi je export, moje mišljenje je da je bolje koristiti default export za komponente koje će se lazily loadirati.

Error boundaries

Kada koristimo lazy i Suspense postoji mogućnost da dođe do greške zbog greške u konekciji ili nekog drugog razloga i tada će lazy i Suspense baciti grešku. Tako da moramo da pokrijemo taj slučaj da nebi korisničko iskustvo išpastalo. Da bi ti spriječili koristimo ErrorBoundaries koji obuhvata Suspense i lazy, i riješava greške koje bi se mogle desiti dok se komponenta loada lazily. Ako želite da kreirate svoj ErrorBOundary možete posjetiti React oficijelnu dokumentaciju i koristiti njihove primjere, ali trenutno nema ekvivalentne opcije ErrorBoundary za Hooks, tako da se moraju pisati u class komponentama. Ja ću da koristim library koji se zove ,jer nisam ljubitelj class komponenti 😅.

import React, { lazy, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
const Header = lazy(() => import('./ExportDefault'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  function ErrorFallback({ error, resetErrorBoundary }) {
    return (
      <div role='alert'>
        <p>Something went wrong:</p>
        <pre>{error.message}</pre>
        <button onClick={resetErrorBoundary}>Try again</button>
      </div>
    );
  }

  return (
    <div className='App'>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Suspense fallback={<div>Loading stuff...</div>}>
          <Header />
          <Footer />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

Mnogo lakše nego da se piše svoj ErrorBoundary koristeći class, ali to bi se trebalo promijeniti kada se napravi verzija koja koristi Hooks.

Gdje koristimo code splitting 🤔

Jedan od načina gdje bi vjerovatno trebali koristiti code-splittin je u routama aplikacije. Kada razmislite o ovome, mnogi korisnici ne posječuju sve naše rute kada koriste našu aplikaciju, tako da nema razloga da usporavamo aplikaciju i povečavamo vrijeme čekanja samo da bi download kode koji nam vjerovatno neće trebati. Možemo odlućiti koje stranice se najčesće koriste i na osnovu tih informacija kreiramo code-splitting da bi napravili najbolje korisničko iskustvo.

import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const Reports = lazy(() => import('./Reports'));

export default function App() {
  return (
    <div className='App'>
      <Router>
        <Suspense fallback={<div>Loading stuff...</div>}>
          <Switch>
            <Route exact path='/' component={Home} />
            <Route path='/reports' component={Reports} />
          </Switch>
        </Suspense>
      </Router>
    </div>
  );
}

Ovako izgleda Javscript code brije code-splitting:

main.chunk.js
main.12381831283.hot-update.js
bundle.js
0.chunk.js

A ovako izgleda nakon što dodamo code-splitting:

main.chunk.js
main.12381831283.hot-update.js
bundle.js
0.chunk.js
2.chunk.js

Kao što možete vidjeti ovdje imamo dodatni fajl 2.chunk.js, koji se kreirao kada smo otvorili npr. /home rutu i download se samo Home.js a ne sve ostalo komponente. Ako želite da provjerite kako izgledaju vaši fajlovi prije i poslije dodavanja code-splitting, možete otvoriti Inspect u Chrome i onda otići na Network tab, i iz filter toolbar, izabrati JS tako da nam se prikažu samo Javascript fajlovi. Još jedna korisna stvar u Chrome je Coverage tab, koji nam prikazuje sve Javascript fajlove na stranici koji su download i koliki procenat tog koda se ustvari koristi. Da bi otvorili Coverage tab u Chrome prvo otvorite Inspect i onda pritisnete Ctrl+Shift+P i ukucate Show Coverage i pritisnete enter, i trebalo bi vam prikazati Coverage tab. Ovdje je je link koji govori o tome kako se koristi Coverage tab da se vidi nekorišteni kode. Ja neću ulaziti dublje u ovu mogućnost jer je post o code-splitting.

Također, još jedan od primjera kada bi mogli koristiti code-splittin je u aplikacijama gdje imamo role admin, user itd. Npr. mi se logujemo u naššu aplikaciju kao normalni user, i kada se logujemo download kod od cijele aplikacije, uključujući i admin panel kojem mi nemamo pristup. Tako da mi download sav taj kod i usporimo aplikaciju a uopšte nemamo mogućnost da koristimo admin panel. Ovo je odličan razlog da se doda code-splitting i da se download samo dijelovi aplikacije koje možemo koristitii koji nam trebaju za rendanje te stranice. Ovo će nam ubrzati stranicu, a i zaustaviti radoznale ljude da istražuju po aplikaciji i razgledaju naš admin panel kode 🧐

Ako želite da testirate brzinu svoje aplikacije možete koristiti tool u Chrome koji se zove Lighthouse, možete pristupiti tako što će te otvoriti Inspect i otići na Lighthouse tab. Ima opcija da se testiraju mnoge stvari na Mobile i Desktop verziji aplikacije

NOTE: Kada testiramo brzinu nase aplikacije, bitno je da znamo da je development verzija aplikacije je mnogo sporija od produkcijske verzije jer se u development verziji > ne koriste metode sa čisćenja i smanjivanja koda kao npr. minification, uglification, itd. a i da bi nam olakšali debugging

Zaključak

Da sumiramo, prošli smo kroz to šta je code-splitting, kako se koristi, i primjeri gdje bi ga možda mogli koristiti. Ovo bi trebao biti dobar početak za vas da počnete koristiti code-splitting u vašoj aplikaciji i pratiti kako se brizine pokretanja mijenjaju. Ako mislite da je ovaj post informativan i korista, molim vas podijelite i sa drugima da bi i oni mogli imati koristi od ovoga. Također nemojte stati na ovom postu, otvorite linkove koje sam dodao u ovom postu i posjetite i oficijelnu React dokumentatciju za više informacija