Code splitting — это техника, позволяющая разбивать
приложение на отдельные модули, которые загружаются только по мере
необходимости. В контексте Universal Router это особенно
важно для крупных приложений с множеством маршрутов, поскольку уменьшает
размер начального бандла и ускоряет загрузку страниц.
В Universal Router маршруты описываются как массив
объектов, каждый из которых содержит путь и обработчик:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/home',
action: () => import('./pages/Home.js').then(module => module.default)
},
{
path: '/about',
action: () => import('./pages/About.js').then(module => module.default)
}
];
const router = new UniversalRouter(routes);
В примере выше используется динамический import(),
который возвращает Promise. Это позволяет отложенно
загружать код страницы только при переходе на соответствующий
маршрут.
При использовании code splitting рекомендуется придерживаться следующих принципов:
// pages/Home.js
export default function Home() {
return `<h1>Главная страница</h1>`;
}
{
path: '/contact',
action: () => import('./pages/Contact.js').then(module => module.default)
}
{
path: '/dashboard',
children: [
{
path: '/stats',
action: () => import('./pages/dashboard/Stats.js').then(m => m.default)
},
{
path: '/settings',
action: () => import('./pages/dashboard/Settings.js').then(m => m.default)
}
]
}
Поскольку динамический импорт возвращает промис, важно правильно обрабатывать ошибки загрузки. Например:
{
path: '/profile',
action: async () => {
try {
const module = await import('./pages/Profile.js');
return module.default;
} catch (err) {
console.error('Ошибка загрузки страницы Profile', err);
return '<h1>Не удалось загрузить страницу</h1>';
}
}
}
Это предотвращает падение всего приложения, если модуль по какой-то причине не загрузился.
Universal Router поддерживает серверный рендеринг, и для
него необходимо учитывать, что import() работает
асинхронно. В Node.js это тоже промис, и нужно корректно обрабатывать
его внутри серверного маршрутизатора:
import express from 'express';
import UniversalRouter from 'universal-router';
const app = express();
app.get('*', async (req, res) => {
try {
const routeResult = await router.resolve({ pathname: req.path });
res.send(routeResult);
} catch (err) {
res.status(500).send('Ошибка сервера');
}
});
Такой подход обеспечивает одинаковое использование ленивых модулей на клиенте и сервере, сохраняя преимущества code splitting.
Для эффективного code splitting важно правильно настроить сборщик. В
Webpack динамический импорт автоматически создает отдельные
чанки:
{
path: '/blog',
action: () => import(/* webpackChunkName: "blog" */ './pages/Blog.js').then(m => m.default)
}
webpackChunkName позволяет давать понятные имена
создаваемым бандлам./* webpackPrefetch: true */ для
предзагрузки модулей, если известно, что пользователь может перейти на
страницу вскоре.Пример с предзагрузкой:
{
path: '/shop',
action: () => import(/* webpackChunkName: "shop", webpackPrefetch: true */ './pages/Shop.js')
.then(m => m.default)
}
{
path: '/lazy',
action: async () => {
const Loading = '<div>Загрузка...</div>';
const promise = import('./pages/LazyPage.js').then(m => m.default);
return promise.catch(() => '<h1>Ошибка загрузки</h1>') || Loading;
}
}
const routes = [
{
path: '/',
action: () => import('./pages/Home.js').then(m => m.default)
},
{
path: '/dashboard',
children: [
{
path: '/stats',
action: () => import('./pages/dashboard/Stats.js').then(m => m.default)
},
{
path: '/settings',
action: () => import('./pages/dashboard/Settings.js').then(m => m.default)
}
]
},
{
path: '/blog/:id',
action: ({ params }) => import('./pages/BlogPost.js')
.then(m => m.default(params))
}
];
const router = new UniversalRouter(routes);
В этом примере:
dashboard загружаются отдельные модули для
статистики и настроек./blog/:id динамически передает параметры в
ленивый модуль.Эта организация маршрутов с динамическими import()
позволяет достичь максимальной оптимизации загрузки и
отзывчивости приложения, полностью используя возможности
Universal Router для Code Splitting.