По мере роста приложения увеличивается объём JavaScript, CSS, изображений и других ресурсов, загружаемых браузером при открытии страницы. Если весь код объединён в один большой пакет, пользователь вынужден ожидать загрузки большого количества данных, даже если значительная часть функциональности не используется сразу.
Стратегия разделения кода (Code Splitting) позволяет разбивать приложение на отдельные части (чанки), которые загружаются только при необходимости. Parcel автоматизирует этот процесс и минимизирует объём ручной настройки.
Основные преимущества:
Parcel рассматривает приложение как граф зависимостей. Каждая точка входа становится корнем дерева, а импортируемые файлы образуют связанные узлы.
Например:
import "./styles.css";
import { initApp } from "./app";
initApp();
Файл app.js может импортировать десятки других
модулей:
import { router } from "./router";
import { store } from "./store";
import { api } from "./api";
Во время сборки Parcel анализирует весь граф зависимостей и определяет:
Одной из сильных сторон Parcel является автоматическое выделение общих частей кода.
Предположим, существует две страницы:
// home.js
import React from "react";
import "./home.css";
// profile.js
import React from "react";
import "./profile.css";
Во время production-сборки Parcel обнаружит, что библиотека React используется обеими страницами.
Вместо дублирования кода будет создан отдельный общий чанк:
dist/
├─ home.js
├─ profile.js
└─ shared.js
Такой подход:
Если пользователь уже посетил одну страницу, общий пакет останется в кэше браузера и будет использован повторно.
Главный механизм разделения кода в современных приложениях — динамический импорт.
Вместо обычного импорта:
import { editor } from "./editor";
используется:
const editor = await import("./editor");
Parcel воспринимает такой импорт как сигнал к созданию отдельного чанка.
Исходный код:
button.addEventListener("click", async () => {
const module = await import("./editor");
module.startEditor();
});
После сборки получается следующая логика:
main.js
|
└── editor.chunk.js
Первоначально загружается только main.js.
Файл редактора будет скачан браузером лишь после нажатия кнопки.
Крупные библиотеки особенно хорошо подходят для ленивой загрузки.
Например, приложение использует редактор Markdown:
async function openEditor() {
const editorModule = await import("./markdown-editor");
editorModule.init();
}
Если редактор требуется лишь части пользователей, нет смысла включать его в основной пакет.
Parcel автоматически:
Структура может выглядеть следующим образом:
main.34f1.js
editor.8ab9.js
Одно из наиболее распространённых применений Code Splitting — маршрутизация.
import "./pages/home";
import "./pages/profile";
import "./pages/admin";
В этом случае весь код загружается сразу.
const routes = {
home: () => import("./pages/home"),
profile: () => import("./pages/profile"),
admin: () => import("./pages/admin")
};
При переходе между страницами происходит подгрузка соответствующего чанка.
Структура сборки:
main.js
home.chunk.js
profile.chunk.js
admin.chunk.js
Пользователь получает только тот код, который необходим в текущий момент.
Parcel поддерживает множественные точки входа.
Пример структуры:
src/
├─ index.html
├─ admin.html
└─ dashboard.html
Сборка:
parcel build src/*.html
Parcel автоматически определит связи между страницами и вынесет общие зависимости.
В результате могут быть сформированы файлы:
index.js
admin.js
dashboard.js
vendor.js
Общий пакет будет использоваться всеми страницами.
Code Splitting распространяется не только на JavaScript.
Если стили импортируются внутри динамически загружаемых модулей:
await import("./dashboard");
а внутри файла:
import "./dashboard.css";
Parcel создаст отдельный CSS-чанк.
Результат:
dashboard.js
dashboard.css
При загрузке JavaScript автоматически будет подключён соответствующий CSS-файл.
Преимущества:
В больших интерфейсах часто используются тяжёлые компоненты.
Пример:
async function showChart() {
const { Chart } = await import("./chart");
new Chart();
}
Parcel сформирует отдельный пакет:
main.js
chart.js
Если пользователь никогда не откроет раздел с графиками, соответствующий код не будет загружен.
В React динамическое разделение часто сочетается с механизмом
React.lazy.
Компонент:
import React, { lazy } from "react";
const AdminPanel = lazy(() => import("./AdminPanel"));
Использование:
<Suspense fallback={<div>Loading...</div>}>
<AdminPanel />
</Suspense>
Parcel автоматически создаёт отдельный чанк для компонента.
Результат:
main.js
AdminPanel.chunk.js
Это позволяет существенно сократить размер стартового пакета приложения.
Иногда модуль понадобится пользователю почти наверняка, но не сразу.
Для подобных ситуаций используется предзагрузка.
Пример логики:
const settingsPromise = import("./settings");
Модуль начинает загружаться заранее:
button.addEventListener("click", async () => {
const settings = await settingsPromise;
});
Такой подход уменьшает задержку при фактическом использовании функциональности.
Предположим, приложение использует библиотеку для построения диаграмм.
Неудачный вариант:
import ChartLibrary from "big-chart-library";
Вся библиотека попадёт в основной пакет.
Лучше использовать:
async function loadCharts() {
const charts = await import("big-chart-library");
return charts.default;
}
В результате:
main.js
big-chart-library.chunk.js
Начальная загрузка становится значительно быстрее.
После production-сборки рекомендуется изучать содержимое каталога
dist.
Пример:
dist/
├─ index.js
├─ vendors.js
├─ editor.js
├─ admin.js
└─ chart.js
По структуре можно определить:
Большое количество кода в основном чанке обычно свидетельствует о необходимости дополнительного использования динамических импортов.
Практика показывает, что наиболее эффективным является разделение по бизнес-модулям.
Например:
features/
├─ auth/
├─ profile/
├─ reports/
└─ analytics/
Каждая область загружается независимо:
const analytics = await import("./features/analytics");
Преимущества такого подхода:
Чрезмерное дробление может ухудшить производительность.
Плохой пример:
main.js
module1.js
module2.js
module3.js
module4.js
module5.js
module6.js
Каждый файл очень маленький, но требует отдельного запроса.
Другой крайний случай:
main.js (5 МБ)
Файл слишком велик для быстрой загрузки.
Оптимальная стратегия предполагает:
Parcel автоматически генерирует хэшированные имена файлов.
Пример:
main.a1b2c3.js
editor.d4e5f6.js
После изменения редактора:
main.a1b2c3.js
editor.z7x8y9.js
Основной пакет остаётся неизменным.
Это позволяет браузеру использовать кэш для файлов, которые не были изменены.
Преимущества:
Для небольших проектов:
Для средних приложений:
Для крупных SPA-приложений:
Грамотно настроенная стратегия разделения позволяет Parcel автоматически создавать эффективную структуру сборки, уменьшать объём стартовой загрузки, ускорять работу приложения и максимально использовать возможности кэширования браузера.