Работа с legacy кодом

При внедрении Universal Router в существующий JavaScript-проект основной сложностью становится несовместимость архитектурных подходов. Legacy-код, как правило, строится вокруг императивной логики, жёстко связанных модулей и устаревших API, тогда как Universal Router предполагает декларативное описание маршрутов и централизованное управление навигацией.

Ключевые проблемы:

  • разрозненная логика навигации (ручные if/else, switch)
  • прямое обращение к window.location
  • отсутствие единой точки управления переходами
  • устаревшие callback-паттерны вместо Promise/async

Постепенная миграция маршрутизации

Резкий переход на 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
      }
    ]
  }
];

Выделение маршрутов из существующего кода

Типичный подход:

  1. Найти все точки входа (URL)
  2. Определить соответствующие обработчики
  3. Перенести их в конфигурацию маршрутов
  4. Удалить дублирующую логику

Работа с устаревшими асинхронными паттернами

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 для адаптации поведения

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-кодом особое внимание уделяется:

  • сохранению обратной совместимости
  • корректной обработке старых URL
  • идентичности поведения до и после миграции

Типичные ошибки

Дублирование маршрутов Оставшиеся в legacy-коде обработчики могут конфликтовать с Universal Router.

Смешение подходов Использование одновременно window.location и router приводит к рассинхронизации.

Игнорирование асинхронности Необработанные Promise вызывают неожиданные ошибки.

Отсутствие централизованного контекста Продолжение использования глобальных переменных снижает преимущества новой архитектуры.

Рекомендации по рефакторингу

  • выносить маршруты в отдельные модули
  • устранять прямые переходы через URL
  • унифицировать работу с асинхронным кодом
  • постепенно внедрять middleware
  • минимизировать глобальное состояние
  • покрывать маршруты тестами

Грамотная интеграция Universal Router в legacy-код позволяет не только улучшить навигацию, но и постепенно модернизировать всю архитектуру приложения без радикального переписывания.