При внедрении Universal Router в существующий JavaScript-проект основной сложностью становится несовместимость архитектурных подходов. Legacy-код, как правило, строится вокруг императивной логики, жёстко связанных модулей и устаревших API, тогда как Universal Router предполагает декларативное описание маршрутов и централизованное управление навигацией.
Ключевые проблемы:
if/else,
switch)window.locationРезкий переход на Universal Router практически всегда приводит к регрессиям. Эффективнее применять стратегию поэтапной замены.
Первый этап — внедрение роутера как обёртки над существующими обработчиками:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/users',
action: () => legacyUsersHandler()
},
{
path: '/products',
action: () => legacyProductsHandler()
}
];
const router = new UniversalRouter(routes);
function navigate(path) {
return router.resolve({ pathname: path });
}
В этом случае legacy-функции продолжают выполнять основную работу, но управление маршрутом уже централизовано.
В старых проектах часто используются прямые переходы:
window.location.href = '/users';
Для интеграции необходимо перехватывать такие вызовы:
function redirect(path) {
history.pushState(null, '', path);
router.resolve({ pathname: path });
}
И постепенно заменять все прямые обращения на единый метод.
Legacy-код редко имеет явную структуру маршрутов. Universal Router требует иерархического описания:
const routes = [
{
path: '/',
children: [
{
path: '/users',
action: usersHandler
},
{
path: '/users/:id',
action: userDetailHandler
}
]
}
];
Типичный подход:
Legacy-код часто использует callbacks:
function getUser(id, callback) {
api.fetchUser(id, function(data) {
callback(data);
});
}
Universal Router работает с Promise, поэтому необходим адаптер:
function getUserAsync(id) {
return new Promise((resolve) => {
getUser(id, resolve);
});
}
const routes = [
{
path: '/users/:id',
async action({ params }) {
const user = await getUserAsync(params.id);
return renderUser(user);
}
}
];
Universal Router передаёт контекст в каждый маршрут. Это позволяет заменить глобальные зависимости legacy-кода.
Legacy:
window.currentUser = { id: 1 };
Современный подход:
const router = new UniversalRouter(routes, {
context: {
currentUser: { id: 1 }
}
});
const routes = [
{
path: '/profile',
action({ context }) {
return renderProfile(context.currentUser);
}
}
];
В старых проектах UI часто не разделён на компоненты. Universal Router не требует конкретного фреймворка, поэтому можно адаптировать существующий рендеринг.
function renderPage(content) {
document.getElementById('app').innerHTML = content;
}
const routes = [
{
path: '/about',
action() {
return renderPage('<h1>About</h1>');
}
}
];
При постепенной модернизации можно внедрять компонентный подход без полного переписывания.
Legacy-системы часто игнорируют ошибки маршрутизации. Universal Router позволяет централизовать обработку.
router.resolve({ pathname: '/unknown' })
.catch(error => {
renderPage('<h1>404 Not Found</h1>');
});
Или через middleware:
const router = new UniversalRouter(routes, {
errorHandler: (error) => {
return renderErrorPage(error);
}
});
Middleware — мощный инструмент для интеграции legacy-логики.
async function legacyMiddleware(context, next) {
console.log('Before route');
const result = await next();
console.log('After route');
return result;
}
const router = new UniversalRouter(routes, {
resolveRoute(context, params) {
return legacyMiddleware(context, () => params.route.action(context));
}
});
Это позволяет внедрять:
Часто требуется одновременная поддержка двух систем.
function handleRequest(path) {
if (path.startsWith('/new')) {
return router.resolve({ pathname: path });
} else {
return legacyRouter(path);
}
}
Такой подход позволяет постепенно переносить маршруты.
Legacy-код может содержать тяжёлые синхронные операции. В связке с Universal Router это приводит к блокировке интерфейса.
const routes = [
{
path: '/heavy',
async action() {
const module = await import('./heavyModule.js');
return module.default();
}
}
];
const cache = new Map();
const routes = [
{
path: '/data',
async action() {
if (cache.has('data')) {
return cache.get('data');
}
const result = await fetchData();
cache.set('data', result);
return result;
}
}
];
В legacy-проектах состояние часто хранится неявно (в DOM или глобальных переменных). Universal Router упрощает передачу состояния через контекст.
const router = new UniversalRouter(routes, {
context: {
store: createStore()
}
});
const routes = [
{
path: '/cart',
action({ context }) {
return renderCart(context.store.getState());
}
}
];
Важно проверять поведение маршрутов независимо от UI.
test('users route', async () => {
const result = await router.resolve({ pathname: '/users' });
expect(result).toBeDefined();
});
При работе с legacy-кодом особое внимание уделяется:
Дублирование маршрутов Оставшиеся в legacy-коде обработчики могут конфликтовать с Universal Router.
Смешение подходов Использование одновременно
window.location и router приводит к рассинхронизации.
Игнорирование асинхронности Необработанные Promise вызывают неожиданные ошибки.
Отсутствие централизованного контекста Продолжение использования глобальных переменных снижает преимущества новой архитектуры.
Грамотная интеграция Universal Router в legacy-код позволяет не только улучшить навигацию, но и постепенно модернизировать всю архитектуру приложения без радикального переписывания.