Code-Splitting

Розбиття по модулям

Більшість React додатків мають власні розбиті за “бандлами” файли використовуючи такі інструменти, як Webpack, Rollup або Browserify. Бандлінг – це процес імпортування файлів та об’єднання їх в один файл – бандл (модуль). Цей бандл може потім бути включений до веб-сторінки для завантаження всього додатку одночасно.

Приклад

App:

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

Bundle:

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

Примітка:

Наприкінці ваші бандли будуть значно відрізнятися від наведених прикладів.

Якщо ви використовуєте Create React App, Next.js, Gatsby чи подібний інструмент, ви матимете налаштування Webpack за замовчуванням для бандлінгу вашого додатку.

Якщо ви не використовуєте нічого з наведеного чи подібного, вам доведеться налаштовувати бандлінг самостійно. Для прикладів ознайомтеся зі Встановленням та Початком роботи. Це офіційна документація Webpack.

Розбиття Коду

Розбиття по бандлам – це прекрасно, але з темпом росту вашого додатку, бандли теж зростають. Це особливо помітно, якщо ви встановили та використовуєте великі сторонні бібліотеки. Вам потрібно стежити за кодом, який потрапляє у ваш бандл для того, щоб в один момент не зробити цей бандл настільки великим, що для завантаження вашого додатку знадобиться чимало часу.

Щоб уникнути розростання бандла, варто почати “розділяти” ваш бандл. Поділ коду - це функціонал, який підтримується такими бандлерами, як Webpack, Rollup та Browserify (з factor-bundle), що можуть створювати декілька бандлів та завантажувати їх у разі потреби.

Щоб уникнути проблем з великим бандлом, було б добре почати “розбиття” вашого бандла. Розбиття Коду – це функція, яку підтримують такі бандлери як Webpack та Browserify (за допомогою factor-bundle. Цей інструмент може створити кілька модулів з одного, які можна динамічно завантажувати під час виконання вашого основного бандлу.

Розбиття коду вашого додатку може допомогти поступово завантажити тільки те, що необхідно користувачеві в цей момент. Це може значно покращити продуктивність вашого додатку. Хоча ви не скоротили кількість коду вашого додатку, ви уникнули завантаження коду, який може ніколи не знадобитись користувачеві та скоротили об’єм коду, що необхідний на початку завантаження додатку.

import()

Найращий спосіб впровадження розбиття коду — це синтаксис динамічних import()

До:

import { add } from './math';

console.log(add(16, 26));

Після:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

Примітка:

Синтаксис динамічних import() – це пропозиція ECMAScript (JavaScript), що не є частиною мовного стандарту на цей момент. Очікується, що він буде прийнятий у найближчий час.

В той момент, коли Webpack стикається з таким синтаксисом, він автоматично починає розбивати код вашого додатку. Якщо ви вже користуєтесь Create React App, це вже налаштовано для вас та ви можете одразу почати користуватися цим. Це також підтримується у Next.js за замовчуванням.

Під час самостійного налаштування Webpack, скоріш за все, у вас з’явиться бажання прочитати інструкцію з розбиття коду від Webpack. Конфігурація вашого Webpack повинна мати вигляд, схожий на цей.

Під час використання Babel, ви маєте пересвідчитись в тому, що Babel може парсити синтаксис динамічних import, виключаючи можливість його перетворення. Для цього вам знадобиться babel-plugin-syntax-dynamic-import.

React.lazy

Примітка:

React.lazy та Suspense ще не доступні для рендерингу на стороні сервера. Якщо ви хочете використовувати розбиття коду в додатках відрендерених на сервері, ми рекомендуємо Loadable Components. Він має гарну інструкцію для розбиття на бандли, використовуючи рендеринг на стороні сервера.

Функція React.lazy дозволяє вам рендерити динамічний import, як звичайний компонент

До:

import OtherComponent from './OtherComponent';

Після:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

Цей код автоматично завантажить бандл, що містить OtherComponent коли цей компонент відрендеритися вперше.

React.lazy приймає функцію, яка має викликати динамічний import(). Потім повертається Promise, який при успішному виконанні поверне модуль з default експортом, а у цьому модулі у свою чергу знаходитиметься React-компонент.

Ледачий компонент потім повинен відрендеритися у тілі компонента Suspense. Це дозволить нам показати резервний контент (наприклад, індикатор завантаження), поки ми чекаємо коли ледачий компонент завантажиться.

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Завантаження...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

fallback приймає будь-який React елемент, поки чекає повного завантаження компонента. Компонент Suspense можна розмістити де завгодно над “ледачим” компонентом. Ви навіть можете обернути кілька “ледачих” компонентів за допомогою одного Suspense компонента

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Завантаження...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Запобіжник

Якщо інший модуль не завантажився (наприклад, через виключений інтернет) — це призведе до помилки. Ви можете обробити ці помилки, щоб створити гарний досвід користування і керувати відновленням за допомогою Запобіжника. Після створення запобіжника, його можна використати де завгодно над “ледачим” компонентами для того, щоб показати стан помилки, коли виникає проблема з мережою.

import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Завантаження...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

Розбиття коду на основі маршрутів

Рішення стосовно того, де саме вводити розбиття коду може бути доволі складним. Ви хочете переконатися, що ви обираєте місця, які рівномірно розділять ваші бандли, але в жодному разі не порушуватимуть роботу користувача.

Вдалим місцем для початку може бути ваш файл з маршрутами додатку. Більшість людей звикли до того, що перехід між сторінками займає певний час. Крім того, ви часто перезавантажуєте цілу сторінку одразу, тому користувачі навряд чи будуть взаємодіяти з іншими елементами на сторінці в цей час.

Нижче наведено приклад налаштування розбиття коду файла з маршрутами додатку, використовуючи бібліотеку React Router за допомогою React.lazy.

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Завантаження...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Іменовані Експорти

Наразі React.lazy підтримує тількі експорти за замовчуванням. Якщо модуль, який ви імпортуєте використовуючи іменовані експорти, можна створити проміжний модуль, який повторно експортуватиме його за замовчуванням. Це гарантує, що tree shaking продовжить працювати та ви не підвантажуєте компоненти, які не будуть використовуватись.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));