TL;DR: Миграция с Nx на Turborepo для Next.js 15 и React 19 может быть сложной из-за скрытых проблем с Jest, SVG и конфигурацией. В статье подробно разбираем шаги по переходу, включая настройку turbo.json, исправление проблем с viewBox в SVG и адаптацию Jest-конфигураций.
Введение
Monorepo — это мощный инструмент, но иногда его абстракции становятся слишком сложными. Наша команда столкнулась с этим при использовании Nx. Несмотря на его мощь, “Nx way” с кастомными исполнителями, скрытой логикой сборки и сложным кэшированием начал восприниматься как черный ящик, ограничивающий автономию команды. Мы решили перейти на Turborepo для упрощения ментальной модели: это просто раннер задач, который не мешает.
Основная часть
Проблемы с Nx
Наш setup страдал от:
- Сложности единого репозитория: Все приложения находились в одном репозитории, что затрудняло независимые деплои.
- Жесткой связности: Изменение в общей библиотеке вызывало ненужные сборки по всему проекту.
- Накладных расходов Nx: Кастомные исполнители и слои абстракции были сложны для отладки.
- Ограниченной автономии команд: Разные команды не могли работать независимо над своими приложениями.
Преимущества Turborepo
- Упрощенная ментальная модель: Turborepo — это раннер задач, а не фреймворк.
- Поддержка полирепозиториев: Каждое приложение может жить в своем репозитории, используя общую конфигурацию Turborepo.
- Быстрые сборки: Кэширование Turborepo проще в настройке и работает из коробки.
- Отсутствие vendor lock-in: Turborepo не оборачивает стандартные инструменты кастомными исполнителями.
Практическое применение
Шаги миграции
-
Добавление turbo.json в корень:
{ "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "test": { "outputs": ["coverage/**"], "cache": false }, "lint": { "outputs": [] }, "lint:fix": { "cache": false, "outputs": [] }, "typecheck": { "outputs": [] }, "prettier": { "outputs": [] }, "dev": { "cache": false, "persistent": true } } } -
Обновление next.config.js:
const path = require('path'); const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); const nextConfig = { webpack(config) { config.module.rules.push({ test: /\.svg$/i, issuer: /\.[jt]sx?$/, use: [{ loader: '@svgr/webpack', options: { exportType: 'named', namedExport: 'ReactComponent', svgoConfig: { plugins: [{ name: 'preset-default', params: { overrides: { removeViewBox: false, cleanupIds: false, }, }, }], }, }, }], }); return config; }, }; module.exports = withBundleAnalyzer(nextConfig); -
Исправление проблем с Jest:
const nextJest = require('next/jest'); const createJestConfig = nextJest({ dir: __dirname }); module.exports = async () => { const config = await createJestConfig(customJestConfig)(); config.transform = { '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': `${repoRoot}/mocks/svgTransformer.js`, ...config.transform, }; config.moduleNameMapper = { ...config.moduleNameMapper, '^.+\\.(svg)$': `${repoRoot}/mocks/svgTransformer.js`, }; return config; };
Заключение
Миграция с Nx на Turborepo была сложной, но оправданной. Мы получили более простую и прозрачную систему сборки, независимые деплои и ускоренный CI. Ключевые моменты: правильная настройка turbo.json, исправление проблем с SVG и адаптация Jest-конфигураций. Опыт нашей команды может быть полезен для других разработчиков, сталкивающихся с аналогичными вызовами.