Разделение кода — это метод, используемый для значительного сокращения времени начального рендеринга. Вы также загружаете столько ресурсов (css, js, html), сколько вам нужно, если это реализовано правильно.

Одним из самых популярных сборщиков является Webpack, он позволяет разбивать код тремя разными способами:

1. Точки входа

Это самый простой и интуитивно понятный способ разделения кода. Однако он ручной и имеет свои подводные камни.

module.exports = {
   entry: {
     index: './src/index.js',
     another: './src/another-module.js',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

Это будет иметь следующие результаты:

asset index.bundle.js
asset another.bundle.js
cacheable modules
  ./src/index.js
  ./src/another-module.js

У него есть некоторые подводные камни, как я уже упоминал:

  • Если в нескольких чанках входа используются одни и те же модули, они будут включены в оба пакета — продублированы.
  • Он не очень гибкий и не может использоваться для автоматического разделения фрагментов.

2. Предотвратить дублирование

Опция dependOn позволяет разделить модули между чанками:

module.exports = {
   entry: {
     index: {
       import: './src/index.js',
       dependOn: 'shared',
     },
     another: {
       import: './src/another-module.js',
       dependOn: 'shared',
     },
    shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

Результат:

asset shared.bundle.js
asset runtime.bundle.js
asset index.bundle.js
asset another.bundle.js 1.65 KiB
Entrypoint index 1.77 KiB = index.bundle.js
Entrypoint another 1.65 KiB = another.bundle.js
Entrypoint shared 557 KiB = runtime.bundle.js 7.79 KiB shared.bundle.js 549 KiB
cacheable modules 530 KiB
  ./node_modules/lodash/lodash.js
  ./src/another-module.js
  ./src/index.js

Как видите, кроме shared.bundle.js, index.bundle.js и another.bundle.js, сгенерирован еще один файл runtime.bundle.js.

Хотя в веб-пакете разрешено использование нескольких точек входа на страницу, по возможности этого следует избегать в пользу точки входа с несколькими импортами:
entry: { page: ['./analytics', './app'] }
Это приводит к лучшей оптимизации и согласованному порядку выполнения при использовании тегов скрипта async.

SplitChunksPlugin

SplitChunksPlugin позволяет нам извлекать часто используемые зависимости в существующий фрагмент записи или совершенно новый фрагмент. Вот пример, дублирующийlodash зависимость из предыдущего примера:

module.exports = {
    entry: {
      index: './src/index.js',
      another: './src/another-module.js',
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
    optimization: {
      splitChunks: {
        chunks: 'all',
      },
    },
  };

Optimization.splitChunks удаляет повторяющиеся зависимости из наших index.bundle.js и another.bundle.js. Плагин замечает, что мы выделили lodash в отдельный фрагмент и удаляем мертвый груз из нашего основного пакета.

Результат сборки:

asset vendors-node_modules_lodash_lodash_js.bundle.js
asset index.bundle.js
asset another.bundle.js
Entrypoint index 558 KiB = vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB index.bundle.js 8.92 KiB
Entrypoint another 558 KiB = vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB another.bundle.js 8.8 KiB
cacheable modules
  ./src/index.js
  ./src/another-module.js
  ./node_modules/lodash/lodash.js

Еще один полезный плагин для разделения кода — mini-css-extract-plugin.. Он полезен для отделения CSS от основного приложения.

3. Динамический импорт

Рекомендуемый подход — использовать динамический синтаксис import().

module.exports = {
   entry: {
     index: './src/index.js',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

Вместо статического импорта lodash мы будем использовать динамический импорт для разделения куска:

function getComponent() {
 async function getComponent() {
   const element = document.createElement('div');
   const { default: _ } = await import('lodash');

   element.innerHTML = _.join(['Hello', 'webpack'], ' ');

   return element;
 }

 getComponent().then((component) => {
   document.body.appendChild(component);
 });

Результат сборки показывает, что uslodash выделен в отдельный пакет:

asset vendors-node_modules_lodash_lodash_js.bundle.js
asset index.bundle.js
cacheable modules
  ./src/index.js
  ./node_modules/lodash/lodash.js

Дополнительное замечание

Если вы используете create-react-app, оно по умолчанию поддерживает разделение кода. Наиболее распространенное место, где вы можете разделить свой код и сократить время загрузки, — это то, где вы определяете свои маршруты.

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

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

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Компонент Приостановка — это запасной вариант, который отрисовывается, пока ваш маршрут еще не загружен.

Спасибо за чтение!