Universal Router — это минималистичная библиотека маршрутизации для JavaScript, позволяющая организовать маршруты без привязки к конкретному фреймворку. В сочетании с Lit она обеспечивает легкую навигацию в одностраничных приложениях, сохраняя реактивность компонентов и оптимизируя обновление DOM.
Установка Universal Router производится через npm:
npm install universal-router
Импорт в проект с Lit выглядит так:
import UniversalRouter from 'universal-router';
import { html, render } from 'lit';
Создается объект маршрутизатора с массивом маршрутов:
const routes = [
{
path: '/',
action: () => html`<h1>Главная страница</h1>`
},
{
path: '/about',
action: () => html`<h1>О проекте</h1>`
},
{
path: '/user/:id',
action: ({ params }) => html`<h1>Профиль пользователя: ${params.id}</h1>`
}
];
const router = new UniversalRouter(routes);
Для интеграции с Lit создается корневой компонент, который
отслеживает изменения маршрута и обновляет DOM с помощью
render:
class AppRouter extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({ mode: 'open' });
this.currentView = html`<h1>Загрузка...</h1>`;
}
connectedCallback() {
window.addEventListener('popstate', () => this.handleRoute());
this.handleRoute();
}
async handleRoute() {
const pathname = window.location.pathname;
this.currentView = await router.resolve({ pathname });
render(this.currentView, this.root);
}
}
customElements.define('app-router', AppRouter);
Использование popstate позволяет корректно обрабатывать
навигацию через кнопки браузера.
Для изменения маршрута программно применяется метод
history.pushState:
function navigateTo(path) {
history.pushState({}, '', path);
window.dispatchEvent(new Event('popstate'));
}
С этим подходом можно создавать ссылки:
html`
<nav>
<a @click="${() => navigateTo('/')}">Главная</a>
<a @click="${() => navigateTo('/about')}">О проекте</a>
<a @click="${() => navigateTo('/user/42')}">Профиль</a>
</nav>
`
Universal Router поддерживает вложенные маршруты через вложенные
объекты в children:
const routes = [
{
path: '/dashboard',
action: () => html`<h1>Панель</h1>`,
children: [
{
path: '/stats',
action: () => html`<h2>Статистика</h2>`
},
{
path: '/settings',
action: () => html`<h2>Настройки</h2>`
}
]
}
];
Для рендеринга вложенных маршрутов достаточно передавать
pathname соответствующей дочерней части.
Universal Router позволяет использовать асинхронные функции в
action, что удобно для ленивой загрузки компонентов:
const routes = [
{
path: '/profile/:id',
action: async ({ params }) => {
const module = await import('./components/profile.js');
return html`<${module.Profile} userId="${params.id}"></${module.Profile}>`;
}
}
];
Это позволяет загружать только необходимые модули, сокращая размер основного бандла.
Universal Router поддерживает контекст, который передается каждому маршруту:
const context = { api: new ApiService() };
router.resolve({ pathname: '/user/42', context }).then(view => {
render(view, document.body);
});
Внутри action можно обращаться к
context.api, что удобно для централизованного управления
состоянием и асинхронными запросами.
Для перехвата ошибок используется глобальный обработчик
catch:
router.resolve({ pathname: '/unknown' })
.then(view => render(view, document.body))
.catch(error => render(html`<h1>Страница не найдена</h1>`, document.body));
Это обеспечивает удобный fallback при неправильных URL или ошибках асинхронных действий.
Если проект построен на LitElement, маршруты можно интегрировать напрямую в компоненты:
import { LitElement, html } from 'lit';
class MyApp extends LitElement {
static properties = { view: {} };
constructor() {
super();
this.view = html`<h1>Загрузка...</h1>`;
window.addEventListener('popstate', () => this.updateRoute());
}
connectedCallback() {
super.connectedCallback();
this.updateRoute();
}
async updateRoute() {
const pathname = window.location.pathname;
this.view = await router.resolve({ pathname });
}
render() {
return html`
<nav>
<button @click="${() => navigateTo('/')}">Главная</button>
<button @click="${() => navigateTo('/about')}">О проекте</button>
</nav>
<main>${this.view}</main>
`;
}
}
customElements.define('my-app', MyApp);
Такой подход позволяет полностью сохранить реактивность Lit и динамически обновлять контент без перезагрузки страницы.
Сочетание Universal Router и Lit обеспечивает легкую, модульную маршрутизацию, поддержку динамических и асинхронных компонентов, а также контекстное управление зависимостями, что делает возможным построение SPA с высокой производительностью и чистой архитектурой.