Найти в Дзене
Кирилл Ледовский

🎨 Пошаговое руководство по Micro Frontends для начинающего разработчика ИИ-агентов

Простое объяснение: Представьте, что вместо одного огромного веб-сайта у вас есть несколько маленьких сайтов-лего, которые собираются вместе в браузере: Каждый блок — это отдельная программа, которую можно: bash # 1. Проверяем, что Node.js установлен
node --version # Должна быть версия 16 или выше
# 2. Проверяем npm
npm --version
# 3. Если не установлено, скачиваем с https://nodejs.org/
# Выбираем LTS версию (рекомендованная)
# 4. Устанавливаем Vite - современный инструмент для сборки
npm create vite@latest text my-microfrontend-project/
├── container/ # Главное приложение-контейнер
├── header-app/ # Микрофронтенд: шапка
├── dashboard-app/ # Микрофронтенд: дашборд ИИ
├── chat-app/ # Микрофронтенд: чат с ИИ
└── shared/ # Общие ресурсы ✅ Node.js и npm установлены
✅ Можем создавать новые проекты
✅ Готова структура папок Это как LEGO-деталька. У неё есть: bash # Переходим в папку проекта
cd my-microfrontend-project
# Создаём приложение шапки
Оглавление

🧩 Что такое Micro Frontends (Микрофронтенды)?

Простое объяснение: Представьте, что вместо одного огромного веб-сайта у вас есть несколько маленьких сайтов-лего, которые собираются вместе в браузере:

  • Один блок — меню навигации
  • Второй — список товаров
  • Третий — корзина покупок
  • Четвёртый — рекомендации от ИИ

Каждый блок — это отдельная программа, которую можно:

  • Разрабатывать независимо
  • Обновлять отдельно
  • Писать на разных технологиях
  • Запускать разными командами

🚀 Шаг 1: Подготовка среды разработки

Цель шага: Установить всё необходимое для создания микрофронтендов.

Что нужно установить:

  1. Node.js и npm — как "двигатель" для JavaScript программ
  2. Visual Studio Code — редактор кода (или любой другой)
  3. Git — для контроля версий

Пошаговая установка:

bash

# 1. Проверяем, что Node.js установлен
node --version
# Должна быть версия 16 или выше

# 2. Проверяем npm
npm --version

# 3. Если не установлено, скачиваем с https://nodejs.org/
# Выбираем LTS версию (рекомендованная)

# 4. Устанавливаем Vite - современный инструмент для сборки
npm create vite@latest

Создаём структуру проекта:

text

my-microfrontend-project/
├── container/ # Главное приложение-контейнер
├── header-app/ # Микрофронтенд: шапка
├── dashboard-app/ # Микрофронтенд: дашборд ИИ
├── chat-app/ # Микрофронтенд: чат с ИИ
└── shared/ # Общие ресурсы

Что должно получиться:

✅ Node.js и npm установлены
✅ Можем создавать новые проекты
✅ Готова структура папок

На что обратить внимание:

  • Node.js версия должна быть актуальной
  • Vite работает быстрее, чем старый create-react-app
  • Можно использовать любую папку для проекта

📝 Шаг 2: Создаём первый микрофронтенд - "Шапка"

Цель шага: Создать независимый компонент, который будет работать отдельно.

Что такое "компонент":

Это как LEGO-деталька. У неё есть:

  • Внешний вид (HTML/CSS)
  • Логика (JavaScript)
  • Возможность принимать параметры
  • Возможность генерировать события

Создаём приложение шапки:

bash

# Переходим в папку проекта
cd my-microfrontend-project

# Создаём приложение шапки с помощью Vite + React
npm create vite@latest header-app -- --template react
cd header-app
npm install

Смотрим структуру созданного приложения:

text

header-app/
├── public/ # Статические файлы
├── src/
│ ├── App.jsx # Главный компонент
│ ├── main.jsx # Точка входа
│ └── App.css # Стили
├── index.html # Основной HTML
├── package.json # Настройки проекта
└── vite.config.js # Конфигурация сборки

Редактируем компонент шапки:

jsx

// header-app/src/App.jsx
import './App.css';

// Компонент — это функция, которая возвращает JSX (HTML + JavaScript)
function App() {
return (
<div className="header-app">
<header className="app-header">
{
/* Логотип */}
<div className="logo">
<img src="/vite.svg" alt="Лого" />
<span>ИИ-Ассистент 2024</span>
</div>

{
/* Навигация */}
<nav className="navigation">
<button className="nav-btn active">
<span>🏠</span> Главная
</button>
<button className="nav-btn">
<span>🤖</span> Агенты
</button>
<button className="nav-btn">
<span>📊</span> Аналитика
</button>
<button className="nav-btn">
<span>⚙️</span> Настройки
</button>
</nav>

{
/* Пользователь */}
<div className="user-info">
<div className="avatar">АИ</div>
<span className="username">ИИ-Разработчик</span>
<button className="logout-btn">🚪</button>
</div>
</header>
</div>
);
}

export default App;

Добавляем стили:

css

/* header-app/src/App.css */
.header-app {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
border-bottom: 2px solid #4a90e2;
}

.app-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}

.logo {
display: flex;
align-items: center;
gap: 1rem;
font-size: 1.5rem;
font-weight: bold;
}

.logo img {
width: 40px;
height: 40px;
animation: pulse 2s infinite;
}

@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}

.navigation {
display: flex;
gap: 1rem;
}

.nav-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
}

.nav-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}

.nav-btn.active {
background: rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.user-info {
display: flex;
align-items: center;
gap: 1rem;
}

.avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}

.logout-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}

.logout-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: rotate(90deg);
}

Запускаем приложение отдельно:

bash

# В папке header-app
npm run dev

Открываем в браузере: http://localhost:5173

Что должно получиться:

✅ Красивая шапка с логотипом, навигацией и пользователем
✅ Анимации и hover-эффекты
✅ Можно кликать по кнопкам
✅ Приложение работает на отдельном порту

На что обратить внимание:

  • Каждый микрофронтенд запускается на своём порту
  • Можно разрабатывать независимо от других частей
  • Стили изолированы в этом компоненте
  • В реальном проекте нужна навигация между микрофронтендами

📊 Шаг 3: Создаём ИИ-дашборд как микрофронтенд

Цель шага: Создать сложный компонент с ИИ-логикой, который можно переиспользовать.

Что такое "дашборд":

Это панель управления с графиками, статистикой и кнопками. В нашем случае — панель управления ИИ-агентами.

Создаём приложение дашборда:

bash

# Возвращаемся в папку проекта
cd ..
npm create vite@latest dashboard-app -- --template react
cd dashboard-app
npm install

# Устанавливаем библиотеки для графиков
npm install recharts axios

Создаём дашборд с ИИ-метриками:

jsx

// dashboard-app/src/App.jsx
import { useState, useEffect } from 'react';
import {
LineChart, Line, BarChart, Bar,
PieChart, Pie, Cell,
XAxis, YAxis, CartesianGrid,
Tooltip, Legend, ResponsiveContainer
} from 'recharts';
import './App.css';

// ИИ-агенты (имитация данных)
const initialAgents = [
{ id: 1, name: 'Аналитик данных', status: 'active', accuracy: 94, cpu: 45 },
{ id: 2, name: 'Чат-бот поддержки', status: 'active', accuracy: 87, cpu: 32 },
{ id: 3, name: 'Прогноз продаж', status: 'warning', accuracy: 76, cpu: 68 },
{ id: 4, name: 'Классификатор изображений', status: 'inactive', accuracy: 92, cpu: 12 },
];

function Dashboard() {
// Состояние — переменные, которые могут меняться
const [agents, setAgents] = useState(initialAgents);
const [selectedAgent, setSelectedAgent] = useState(null);

// Данные для графиков
const performanceData = [
{ day: 'Пн', accuracy: 88, response: 120 },
{ day: 'Вт', accuracy: 92, response: 110 },
{ day: 'Ср', accuracy: 85, response: 130 },
{ day: 'Чт', accuracy: 94, response: 105 },
{ day: 'Пт', accuracy: 90, response: 115 },
{ day: 'Сб', accuracy: 87, response: 125 },
{ day: 'Вс', accuracy: 91, response: 118 },
];

const agentDistribution = [
{ name: 'Active', value: 2, color: '#4CAF50' },
{ name: 'Warning', value: 1, color: '#FFC107' },
{ name: 'Inactive', value: 1, color: '#F44336' },
];

// Имитация обновления данных каждые 5 секунд
useEffect(() => {
const interval = setInterval(() => {
setAgents(prev => prev.map(agent => ({
...agent,
cpu: Math.min(100, Math.max(0, agent.cpu + (Math.random() * 10 - 5))),
})));
}, 5000);

return () => clearInterval(interval);
}, []);

// Обработчик выбора агента
const handleAgentClick = (agent) => {
setSelectedAgent(agent);
};

// Обработчик запуска/остановки
const toggleAgentStatus = (id) => {
setAgents(prev => prev.map(agent =>
agent.id === id
? {
...agent,
status: agent.status === 'active' ? 'inactive' : 'active',
cpu: agent.status === 'active' ? 0 : 45
}
: agent
));
};

return (
<div className="dashboard-app">
<h1 className="dashboard-title">📊 Панель управления ИИ-агентами</h1>

<div className="dashboard-grid">
{
/* Левая колонка: список агентов */}
<div className="agents-list">
<h2>🤖 Мои ИИ-агенты</h2>
{agents.map(agent => (
<div
key={agent.id}
className={`agent-card ${agent.status} ${selectedAgent?.id === agent.id ? 'selected' : ''}`}
onClick={() => handleAgentClick(agent)}
>
<div className="agent-info">
<h3>{agent.name}</h3>
<div className="agent-metrics">
<span className={`status-badge ${agent.status}`}>
{agent.status === 'active' ? '🟢 Активен' :
agent.status === 'warning' ? '🟡 Внимание' : '🔴 Неактивен'}
</span>
<span className="metric">Точность: {agent.accuracy}%</span>
<span className="metric">CPU: {agent.cpu.toFixed(1)}%</span>
</div>
</div>
<button
className={`toggle-btn ${agent.status === 'active' ? 'stop' : 'start'}`}
onClick={(e) => {
e.stopPropagation();
toggleAgentStatus(agent.id);
}}
>
{agent.status === 'active' ? '⏹️ Стоп' : '▶️ Запуск'}
</button>
</div>
))}
</div>

{
/* Правая колонка: графики и детали */}
<div className="charts-section">
{
/* График точности */}
<div className="chart-container">
<h3>📈 Точность работы за неделю</h3>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={performanceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="day" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="accuracy" stroke="#8884d8" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
</div>

{
/* Распределение по статусам */}
<div className="chart-container">
<h3>🍩 Статусы агентов</h3>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie
data={agentDistribution}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{agentDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</div>

{
/* Детали выбранного агента */}
{selectedAgent && (
<div className="agent-details">
<h3>🔍 Детали: {selectedAgent.name}</h3>
<div className="details-grid">
<div className="detail">
<span className="label">ID:</span>
<span className="value">{selectedAgent.id}</span>
</div>
<div className="detail">
<span className="label">Статус:</span>
<span className={`value status-${selectedAgent.status}`}>
{selectedAgent.status === 'active' ? 'Активен 🟢' :
selectedAgent.status === 'warning' ? 'Внимание 🟡' : 'Неактивен 🔴'}
</span>
</div>
<div className="detail">
<span className="label">Точность:</span>
<span className="value">{selectedAgent.accuracy}%</span>
</div>
<div className="detail">
<span className="label">Загрузка CPU:</span>
<div className="cpu-bar">
<div
className="cpu-fill"
style={{ width: `${selectedAgent.cpu}%` }}
></div>
<span>{selectedAgent.cpu.toFixed(1)}%</span>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
);
}

export default Dashboard;

Добавляем стили дашборда:

css

/* dashboard-app/src/App.css */
.dashboard-app {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 2rem;
background: #f5f5f5;
min-height: 100vh;
}

.dashboard-title {
color: #333;
margin-bottom: 2rem;
font-size: 2rem;
display: flex;
align-items: center;
gap: 1rem;
}

.dashboard-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
height: calc(100vh - 120px);
}

/* Список агентов */
.agents-list {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
overflow-y: auto;
}

.agents-list h2 {
margin-bottom: 1.5rem;
color: #444;
}

.agent-card {
background: #f8f9fa;
border-radius: 10px;
padding: 1rem;
margin-bottom: 1rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4px solid #ccc;
}

.agent-card:hover {
transform: translateX(5px);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.agent-card.selected {
border-left-color: #4a90e2;
background: #e3f2fd;
}

.agent-card.active {
border-left-color: #4CAF50;
}

.agent-card.warning {
border-left-color: #FFC107;
}

.agent-card.inactive {
border-left-color: #F44336;
opacity: 0.7;
}

.agent-info h3 {
margin: 0 0 0.5rem 0;
color: #333;
}

.agent-metrics {
display: flex;
gap: 1rem;
font-size: 0.9rem;
}

.status-badge {
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
}

.status-badge.active {
background: #e8f5e9;
color: #2e7d32;
}

.status-badge.warning {
background: #fff8e1;
color: #f57c00;
}

.status-badge.inactive {
background: #ffebee;
color: #c62828;
}

.metric {
color: #666;
}

.toggle-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}

.toggle-btn.start {
background: #4CAF50;
color: white;
}

.toggle-btn.stop {
background: #F44336;
color: white;
}

.toggle-btn:hover {
opacity: 0.9;
transform: scale(1.05);
}

/* Графики */
.charts-section {
display: flex;
flex-direction: column;
gap: 2rem;
}

.chart-container {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

.chart-container h3 {
margin-top: 0;
margin-bottom: 1rem;
color: #444;
}

/* Детали агента */
.agent-details {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

.details-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1rem;
}

.detail {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.label {
font-weight: bold;
color: #666;
font-size: 0.9rem;
}

.value {
font-size: 1.1rem;
color: #333;
}

.status-active {
color: #4CAF50;
}

.status-warning {
color: #FFC107;
}

.status-inactive {
color: #F44336;
}

.cpu-bar {
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
position: relative;
}

.cpu-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #8BC34A);
transition: width 0.5s ease;
}

.cpu-bar span {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 0.8rem;
color: white;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
}

Запускаем дашборд отдельно:

bash

# В папке dashboard-app
npm run dev

Открываем в браузере: http://localhost:5174 (следующий порт)

Что должно получиться:

✅ Полноценный дашборд управления ИИ-агентами
✅ Интерактивные графики с анимациями
✅ Можно запускать/останавливать агентов
✅ Данные обновляются в реальном времени
✅ Адаптивная сетка (grid)

На что обратить внимание:

  • Данные имитируются, в реальном проекте будут с API
  • Графики используют библиотеку recharts
  • Состояние управляется через React hooks (useState, useEffect)
  • Стили полностью изолированы

💬 Шаг 4: Создаём ИИ-чат как микрофронтенд

Цель шага: Создать интерактивный компонент, который общается с бэкендом.

Что такое "чат с ИИ":

Компонент, где пользователь пишет сообщения, а ИИ-агент отвечает. Это отдельное приложение, которое можно встроить куда угодно.

Создаём приложение чата:

bash

# Возвращаемся в папку проекта
cd ..
npm create vite@latest chat-app -- --template react
cd chat-app
npm install

# Устанавливаем иконки и утилиты
npm install react-icons date-fns

Создаём чат-компонент:

jsx

// chat-app/src/App.jsx
import { useState, useRef, useEffect } from 'react';
import { FiSend, FiUser, FiBot, FiTrash2 } from 'react-icons/fi';
import { format } from 'date-fns';
import { ru } from 'date-fns/locale';
import './App.css';

// Типы сообщений
const MESSAGE_TYPES = {
USER: 'user',
BOT: 'bot',
SYSTEM: 'system'
};

// ИИ-агенты для выбора
const AI_AGENTS = [
{ id: 'general', name: '🤖 Общий ИИ-ассистент', description: 'Отвечает на общие вопросы' },
{ id: 'code', name: '💻 ИИ-разработчик', description: 'Помогает с кодом' },
{ id: 'analytics', name: '📊 Аналитик данных', description: 'Анализирует данные' },
{ id: 'creative', name: '🎨 Креативный ИИ', description: 'Генерирует идеи' },
];

function ChatApp() {
// Состояния
const [messages, setMessages] = useState([
{
id: 1,
type: MESSAGE_TYPES.BOT,
text: 'Привет! Я ваш ИИ-ассистент. Чем могу помочь?',
timestamp: new Date(),
agent: 'general'
}
]);

const [inputText, setInputText] = useState('');
const [selectedAgent, setSelectedAgent] = useState(AI_AGENTS[0]);
const [isTyping, setIsTyping] = useState(false);

// Референс для автоматической прокрутки
const messagesEndRef = useRef(null);

// Автопрокрутка при новых сообщениях
useEffect(() => {
scrollToBottom();
}, [messages]);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};

// Отправка сообщения
const handleSendMessage = async () => {
if (!inputText.trim()) return;

// Добавляем сообщение пользователя
const userMessage = {
id: messages.length + 1,
type: MESSAGE_TYPES.USER,
text: inputText,
timestamp: new Date(),
agent: selectedAgent.id
};

setMessages(prev => [...prev, userMessage]);
setInputText('');
setIsTyping(true);

// Имитация ответа ИИ
setTimeout(() => {
const responses = {
general: [
'Интересный вопрос! Давайте разберемся...',
'Я понимаю ваш интерес. Вот что я могу сказать...',
'Хороший вопрос! На основе моих данных...',
'Позвольте мне подумать над этим...'
],
code: [
'Для решения этой задачи рекомендую использовать...',
'Вот пример кода на Python: ```python\nprint("Hello")\n```',
'Обратите внимание на архитектурный паттерн...',
'Для оптимизации производительности попробуйте...'
],
analytics: [
'Судя по данным, наблюдается тенденция...',
'Статистический анализ показывает...',
'Вот визуализация этих данных...',
'Корреляционный анализ указывает на...'
],
creative: [
'Как насчет такой идеи...',
'Представьте себе...',
'Что если мы попробуем...',
'Вдохновляясь этим, можно создать...'
]
};

const botResponse = {
id: messages.length + 2,
type: MESSAGE_TYPES.BOT,
text: responses[selectedAgent.id][Math.floor(Math.random() * responses[selectedAgent.id].length)],
timestamp: new Date(),
agent: selectedAgent.id
};

setMessages(prev => [...prev, botResponse]);
setIsTyping(false);
}, 1500 + Math.random() * 1000);
};

// Очистка истории
const clearChat = () => {
setMessages([
{
id: 1,
type: MESSAGE_TYPES.BOT,
text: 'История очищена. Чем могу помочь теперь?',
timestamp: new Date(),
agent: selectedAgent.id
}
]);
};

// Обработка нажатия Enter
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};

// Форматирование времени
const formatTime = (date) => {
return format(date, 'HH:mm', { locale: ru });
};

return (
<div className="chat-app">
{
/* Заголовок */}
<div className="chat-header">
<div className="chat-title">
<h1>💬 ИИ-Чат ассистент</h1>
<p>Общайтесь с искусственным интеллектом</p>
</div>
<button className="clear-btn" onClick={clearChat} title="Очистить историю">
<FiTrash2 /> Очистить
</button>
</div>

<div className="chat-container">
{
/* Панель выбора агента */}
<div className="agent-selector">
<h3>🎭 Выберите ИИ-агента:</h3>
<div className="agent-options">
{AI_AGENTS.map(agent => (
<div
key={agent.id}
className={`agent-option ${selectedAgent.id === agent.id ? 'selected' : ''}`}
onClick={() => setSelectedAgent(agent)}
>
<div className="agent-icon">{agent.name.split(' ')[0]}</div>
<div className="agent-info">
<h4>{agent.name}</h4>
<p>{agent.description}</p>
</div>
</div>
))}
</div>

<div className="current-agent-info">
<h4>Текущий агент:</h4>
<div className="current-agent">
<span className="agent-badge">{selectedAgent.name.split(' ')[0]}</span>
<span>{selectedAgent.name}</span>
</div>
</div>
</div>

{
/* Окно чата */}
<div className="chat-window">
<div className="messages-container">
{messages.map(message => (
<div
key={message.id}
className={`message ${message.type}`}
>
<div className="message-header">
<div className="message-sender">
{message.type === MESSAGE_TYPES.USER ? (
<>
<FiUser /> Вы
</>
) : (
<>
<FiBot /> {AI_AGENTS.find(a => a.id === message.agent)?.name.split(' ')[0]}
</>
)}
</div>
<div className="message-time">
{formatTime(message.timestamp)}
</div>
</div>

<div className="message-content">
{message.text}
</div>

{message.type === MESSAGE_TYPES.BOT && (
<div className="message-footer">
<span className="agent-tag">
{AI_AGENTS.find(a => a.id === message.agent)?.name}
</span>
</div>
)}
</div>
))}

{isTyping && (
<div className="message bot typing">
<div className="message-header">
<div className="message-sender">
<FiBot /> {selectedAgent.name.split(' ')[0]}
</div>
</div>
<div className="message-content typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
)}

<div ref={messagesEndRef} />
</div>

{
/* Поле ввода */}
<div className="input-area">
<textarea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={`Введите сообщение для ${selectedAgent.name}...`}
rows="3"
className="message-input"
/>
<button
onClick={handleSendMessage}
disabled={!inputText.trim() || isTyping}
className="send-button"
>
<FiSend /> Отправить
</button>
</div>
</div>
</div>

{
/* Статистика */}
<div className="chat-stats">
<div className="stat">
<span className="stat-label">Сообщений:</span>
<span className="stat-value">{messages.length}</span>
</div>
<div className="stat">
<span className="stat-label">Текущий агент:</span>
<span className="stat-value">{selectedAgent.name}</span>
</div>
<div className="stat">
<span className="stat-label">Статус:</span>
<span className="stat-value">
{isTyping ? 'ИИ печатает...' : 'Готов к общению'}
</span>
</div>
</div>
</div>
);
}

export default ChatApp;

Добавляем стили чата:

css

/* chat-app/src/App.css */
.chat-app {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}

/* Заголовок */
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
background: white;
padding: 1.5rem;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

.chat-title h1 {
margin: 0;
color: #333;
font-size: 2rem;
}

.chat-title p {
margin: 0.5rem 0 0 0;
color: #666;
}

.clear-btn {
background: #ff4757;
color: white;
border: none;
padding: 0.8rem 1.5rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: bold;
transition: all 0.3s ease;
}

.clear-btn:hover {
background: #ff3742;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 71, 87, 0.3);
}

/* Контейнер чата */
.chat-container {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
margin-bottom: 2rem;
}

/* Селектор агента */
.agent-selector {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

.agent-selector h3 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #333;
}

.agent-options {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 2rem;
}

.agent-option {
padding: 1rem;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid #e0e0e0;
display: flex;
align-items: center;
gap: 1rem;
}

.agent-option:hover {
border-color: #4a90e2;
background: #f0f7ff;
transform: translateX(5px);
}

.agent-option.selected {
border-color: #4a90e2;
background: #e3f2fd;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.2);
}

.agent-icon {
font-size: 1.5rem;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
color: white;
}

.agent-info h4 {
margin: 0;
color: #333;
}

.agent-info p {
margin: 0.2rem 0 0 0;
color: #666;
font-size: 0.9rem;
}

.current-agent-info {
border-top: 1px solid #eee;
padding-top: 1.5rem;
}

.current-agent-info h4 {
margin: 0 0 1rem 0;
color: #333;
}

.current-agent {
display: flex;
align-items: center;
gap: 1rem;
}

.agent-badge {
background: linear-gradient(135deg, #4CAF50, #8BC34A);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-weight: bold;
}

/* Окно чата */
.chat-window {
background: white;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
height: 600px;
}

.messages-container {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}

/* Сообщения */
.message {
max-width: 80%;
padding: 1rem;
border-radius: 15px;
animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}

.message.user {
align-self: flex-end;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-bottom-right-radius: 5px;
}

.message.bot {
align-self: flex-start;
background: #f0f2f5;
color: #333;
border-bottom-left-radius: 5px;
}

.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}

.message-sender {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: bold;
}

.message-time {
color: #999;
font-size: 0.8rem;
}

.message-content {
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
}

.message-footer {
margin-top: 0.5rem;
display: flex;
justify-content: flex-end;
}

.agent-tag {
background: rgba(74, 144, 226, 0.1);
color: #4a90e2;
padding: 0.2rem 0.8rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}

/* Индикатор набора */
.typing-indicator {
display: flex;
gap: 0.3rem;
align-items: center;
}

.typing-indicator span {
width: 8px;
height: 8px;
background: #999;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }

@keyframes typing {
0%, 80%, 100% { transform: scale(0.5); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}

/* Поле ввода */
.input-area {
border-top: 1px solid #eee;
padding: 1.5rem;
display: flex;
gap: 1rem;
}

.message-input {
flex: 1;
padding: 1rem;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-family: inherit;
font-size: 1rem;
resize: none;
transition: border-color 0.3s ease;
}

.message-input:focus {
outline: none;
border-color: #4a90e2;
}

.send-button {
background: linear-gradient(135deg, #4CAF50, #8BC34A);
color: white;
border: none;
padding: 0 2rem;
border-radius: 10px;
cursor: pointer;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
}

.send-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
}

.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

/* Статистика */
.chat-stats {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
display: flex;
justify-content: space-around;
}

.stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}

.stat-label {
color: #666;
font-size: 0.9rem;
}

.stat-value {
font-size: 1.2rem;
font-weight: bold;
color: #333;
}

Запускаем чат отдельно:

bash

# В папке chat-app
npm run dev

Открываем в браузере: http://localhost:5175

Что должно получиться:

✅ Полноценный чат с выбором ИИ-агентов
✅ Анимации набора текста
✅ История сообщений с временными метками
✅ Возможность очистки чата
✅ Статистика в реальном времени

На что обратить внимание:

  • Ответы ИИ имитируются
  • В реальном проекте будет WebSocket соединение
  • Сообщения сохраняются только в памяти (при перезагрузке пропадут)
  • Можно выбрать разных ИИ-агентов

🧩 Шаг 5: Объединяем микрофронтенды в контейнер

Цель шага: Собрать все независимые приложения в одно целое.

Что такое "контейнерное приложение":

Это главное приложение, которое загружает и отображает микрофронтенды. Как рама для картин — можно менять картины, не меняя раму.

Создаём контейнерное приложение:

bash

# Возвращаемся в папку проекта
cd ..
npm create vite@latest container -- --template react
cd container
npm install

# Устанавливаем модуль-федерацию для Webpack
npm install @module-federation/enhanced

# Или используем готовый шаблон с микрофронтендами:
# npx @module-federation/create-mf-app

Настраиваем Module Federation (объединение модулей):

Объяснение термина: Module Federation — технология, позволяющая загружать код из других приложений во время выполнения. Как если бы разные программы могли делиться своими частями.

javascript

// container/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@module-federation/enhanced/vite'

export default defineConfig({
plugins: [
react(),
federation({
name: 'container',
remotes: {
header: 'http://localhost:5173/assets/remoteEntry.js',
dashboard: 'http://localhost:5174/assets/remoteEntry.js',
chat: 'http://localhost:5175/assets/remoteEntry.js',
},
shared: ['react', 'react-dom']
})
],
build: {
target: 'esnext'
}
})

Настраиваем микрофронтенды для экспорта:

javascript

// header-app/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@module-federation/enhanced/vite'

export default defineConfig({
plugins: [
react(),
federation({
name: 'header',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/App'
},
shared: ['react', 'react-dom']
})
],
build: {
target: 'esnext'
}
})

javascript

// dashboard-app/vite.config.js и chat-app/vite.config.js - аналогично

Создаём главный контейнер:

jsx

// container/src/App.jsx
import { Suspense, lazy } from 'react';
import './App.css';

// Динамически загружаем микрофронтенды
const Header = lazy(() => import('header/Header'));
const Dashboard = lazy(() => import('dashboard/Dashboard'));
const Chat = lazy(() => import('chat/Chat'));

function App() {
return (
<div className="container-app">
{
/* Загрузчик для микрофронтендов */}
<Suspense fallback={<div className="loading">🌀 Загрузка приложения...</div>}>
<Header />
</Suspense>

<main className="main-content">
<div className="left-panel">
<Suspense fallback={<div className="loading">📊 Загрузка дашборда...</div>}>
<Dashboard />
</Suspense>
</div>

<div className="right-panel">
<Suspense fallback={<div className="loading">💬 Загрузка чата...</div>}>
<Chat />
</Suspense>
</div>
</main>

<footer className="app-footer">
<p>ИИ-Платформа 2024 | Все микрофронтенды работают независимо</p>
<div className="footer-info">
<span>🚀 Загружено: 3 микрофронтенда</span>
<span>⚡ Версия контейнера: 1.0.0</span>
</div>
</footer>
</div>
);
}

export default App;

Стили контейнера:

css

/* container/src/App.css */
.container-app {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
}

/* Загрузчик */
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
font-size: 1.2rem;
color: #666;
background: #f5f5f5;
border-radius: 10px;
animation: pulse 1.5s infinite;
}

@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}

/* Основной контент */
.main-content {
display: flex;
flex: 1;
padding: 2rem;
gap: 2rem;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}

.left-panel {
flex: 2;
min-width: 0;
/* Для правильного скролла */
}

.right-panel {
flex: 1;
min-width: 0;
min-height: 600px;
}

/* Футер */
.app-footer {
background: #333;
color: white;
padding: 1.5rem 2rem;
text-align: center;
}

.app-footer p {
margin: 0 0 1rem 0;
font-size: 1.1rem;
}

.footer-info {
display: flex;
justify-content: center;
gap: 3rem;
font-size: 0.9rem;
color: #ccc;
}

Настраиваем package.json для CORS (разрешаем доступ):

json

{
"scripts": {
"dev": "vite --host --port 3000",
"build": "vite build",
"preview": "vite preview"
}
}

Запускаем все приложения:

  1. В терминале 1: cd header-app && npm run dev -- --port 5173 --host
  2. В терминале 2: cd dashboard-app && npm run dev -- --port 5174 --host
  3. В терминале 3: cd chat-app && npm run dev -- --port 5175 --host
  4. В терминале 4: cd container && npm run dev -- --port 3000 --host

Что должно получиться:

✅ Все 4 приложения запущены
✅ Контейнер на localhost:3000 загружает 3 микрофронтенда
✅ Можно обновлять каждый микрофронтенд отдельно
✅ Стили не конфликтуют
✅ Ленивая загрузка (подгружаются по мере необходимости)

На что обратить внимание:

  • Module Federation требует настройки CORS
  • Все приложения должны быть запущены
  • В production нужно собирать статичные файлы
  • Можно использовать разные версии React в микрофронтендах

🔄 Шаг 6: Управление версиями микрофронтендов

Цель шага: Научиться обновлять части приложения независимо друг от друга.

Что такое "семантическое версионирование":

Формат версий: MAJOR.MINOR.PATCH (например: 2.1.3)

  • MAJOR — ломающие изменения
  • MINOR — новые функции, обратная совместимость
  • PATCH — исправления багов

Создаём систему управления версиями:

json

// В каждом микрофронтенде создаём version.json
// header-app/public/version.json
{
"name": "header-app",
"version": "1.2.0",
"buildDate": "2024-01-15T12:00:00Z",
"changelog": [
"Добавлена анимация логотипа",
"Исправлена адаптивность на мобильных",
"Добавлен tooltip к кнопкам"
],
"compatibility": {
"container": ">=1.0.0",
"react": "^18.0.0"
}
}

Добавляем компонент для отображения версий:

jsx

// container/src/components/VersionManager.jsx
import { useState, useEffect } from 'react';
import './VersionManager.css';

function VersionManager() {
const [versions, setVersions] = useState({});
const [updateAvailable, setUpdateAvailable] = useState(false);

// Загружаем версии микрофронтендов
useEffect(() => {
const loadVersions = async () => {
try {
const mfUrls = {
header: 'http://localhost:5173/version.json',
dashboard: 'http://localhost:5174/version.json',
chat: 'http://localhost:5175/version.json'
};

const results = {};

for (const [name, url] of Object.entries(mfUrls)) {
try {
const response = await fetch(url);
const data = await response.json();
results[name] = data;
} catch (error) {
results[name] = {
name,
version: 'Недоступно',
error: true
};
}
}

setVersions(results);

// Проверяем обновления
checkForUpdates(results);
} catch (error) {
console.error('Ошибка загрузки версий:', error);
}
};

loadVersions();
// Проверяем каждые 5 минут
const interval = setInterval(loadVersions, 5 * 60 * 1000);

return () => clearInterval(interval);
}, []);

const checkForUpdates = (currentVersions) => {
// Здесь могла бы быть логика сравнения с последними версиями
// Например, запрос к API с доступными обновлениями
const hasUpdate = Object.values(currentVersions).some(
v => v.latestVersion && v.latestVersion !== v.version
);

setUpdateAvailable(hasUpdate);
};

const handleUpdate = (appName) => {
// В реальном проекте здесь была бы логика обновления
alert(`Обновление ${appName}... В реальном проекте здесь был бы механизм обновления`);

// Имитация обновления
window.location.reload();
};

return (
<div className="version-manager">
<div className="version-header">
<h3>📦 Управление версиями</h3>
{updateAvailable && (
<span className="update-badge">Доступны обновления!</span>
)}
</div>

<div className="version-list">
{Object.entries(versions).map(([name, info]) => (
<div key={name} className="version-item">
<div className="version-info">
<span className="app-name">{info.name || name}</span>
<span className={`app-version ${info.error ? 'error' : ''}`}>
{info.version} {info.error && '❌'}
</span>
{info.buildDate && (
<span className="build-date">
{new Date(info.buildDate).toLocaleDateString()}
</span>
)}
</div>

<div className="version-actions">
{info.error ? (
<button className="btn-error">Ошибка загрузки</button>
) : (
<>
<button
className="btn-details"
onClick={() => alert(JSON.stringify(info, null, 2))}
>
Детали
</button>
<button
className="btn-update"
onClick={() => handleUpdate(name)}
>
Обновить
</button>
</>
)}
</div>
</div>
))}
</div>

<div className="version-notes">
<h4>🎯 Преимущества версионности:</h4>
<ul>
<li>Каждый микрофронтенд обновляется независимо</li>
<li>Можно откатить одну часть без влияния на другие</li>
<li>A/B тестирование разных версий</li>
<li>Постепенное развертывание новых функций</li>
</ul>
</div>
</div>
);
}

export default VersionManager;

Стили менеджера версий:

css

/* VersionManager.css */
.version-manager {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
margin: 2rem;
}

.version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 1rem;
}

.version-header h3 {
margin: 0;
color: #333;
}

.update-badge {
background: linear-gradient(135deg, #4CAF50, #8BC34A);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: bold;
animation: pulse 2s infinite;
}

.version-list {
display: flex;
flex-direction: column;
gap: 1rem;
}

.version-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 10px;
border-left: 4px solid #4a90e2;
transition: all 0.3s ease;
}

.version-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.version-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.app-name {
font-weight: bold;
color: #333;
font-size: 1.1rem;
}

.app-version {
color: #666;
font-family: monospace;
}

.app-version.error {
color: #f44336;
}

.build-date {
font-size: 0.9rem;
color: #999;
}

.version-actions {
display: flex;
gap: 1rem;
}

.btn-details, .btn-update, .btn-error {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}

.btn-details {
background: #e3f2fd;
color: #1976d2;
}

.btn-details:hover {
background: #bbdefb;
}

.btn-update {
background: linear-gradient(135deg, #4CAF50, #8BC34A);
color: white;
}

.btn-update:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
}

.btn-error {
background: #ffebee;
color: #c62828;
cursor: not-allowed;
}

.version-notes {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 2px solid #f0f0f0;
}

.version-notes h4 {
margin: 0 0 1rem 0;
color: #333;
}

.version-notes ul {
margin: 0;
padding-left: 1.5rem;
color: #666;
line-height: 1.6;
}

.version-notes li {
margin-bottom: 0.5rem;
}

Что должно получиться:

✅ Панель управления версиями всех микрофронтендов
✅ Видно, какая версия каждого приложения загружена
✅ Можно проверять обновления
✅ Отдельные кнопки обновления для каждого приложения

На что обратить внимание:

  • В production версии хранятся в CDN
  • Нужен механизм безопасного обновления (feature flags)
  • Можно делать канареечные развертывания
  • Важно тестировать совместимость версий

🏗️ Шаг 7: Архитектура и deployment (развертывание)

Цель шага: Понять, как развернуть микрофронтенды в production.

Структура проекта для production:

text

production/
├── nginx/ # Веб-сервер для проксирования
│ └── nginx.conf
├── docker-compose.yml # Оркестрация контейнеров
├── container/ # Собранный контейнер
├── header-app/ # Собранный header
├── dashboard-app/ # Собранный dashboard
└── chat-app/ # Собранный chat

Конфигурация Nginx:

nginx

# nginx/nginx.conf
events {
worker_connections 1024;
}

http {
# Контейнерное приложение
server {
listen 80;
server_name app.example.com;

location / {
proxy_pass http://container:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

# Микрофронтенды (каждый на своём поддомене)
server {
listen 80;
server_name header.example.com;

location / {
proxy_pass http://header-app:5173;
# ... те же настройки proxy
}
}

server {
listen 80;
server_name dashboard.example.com;

location / {
proxy_pass http://dashboard-app:5174;
}
}

server {
listen 80;
server_name chat.example.com;

location / {
proxy_pass http://chat-app:5175;
}
}
}

Dockerfile для микрофронтенда:

dockerfile

# header-app/Dockerfile
# Шаг 1: Сборка
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Шаг 2: Продакшен
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Docker Compose для запуска всего:

yaml

# docker-compose.yml
version: '3.8'

services:
# Контейнерное приложение
container:
build: ./container
ports:
- "3000:80"
environment:
- NODE_ENV=production
depends_on:
- header-app
- dashboard-app
- chat-app

# Микрофронтенды
header-app:
build: ./header-app
ports:
- "5173:80"
environment:
- NODE_ENV=production

dashboard-app:
build: ./dashboard-app
ports:
- "5174:80"
environment:
- NODE_ENV=production

chat-app:
build: ./chat-app
ports:
- "5175:80"
environment:
- NODE_ENV=production

# Nginx для проксирования
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- container
- header-app
- dashboard-app
- chat-app

Скрипты для сборки и деплоя:

json

// package.json в корне проекта
{
"scripts": {
"build:all": "npm run build --workspaces",
"dev:all": "concurrently \"npm run dev --workspace=header-app\" \"npm run dev --workspace=dashboard-app\" \"npm run dev --workspace=chat-app\" \"npm run dev --workspace=container\"",
"deploy:staging": "docker-compose -f docker-compose.staging.yml up -d",
"deploy:production": "docker-compose -f docker-compose.prod.yml up -d",
"update:header": "cd header-app && npm run build && docker build -t header-app:latest .",
"rollback:header": "docker service update --image header-app:previous myapp_header-app"
},
"workspaces": [
"header-app",
"dashboard-app",
"chat-app",
"container"
]
}

Что должно получиться:

✅ Все приложения упакованы в Docker контейнеры
✅ Nginx проксирует запросы
✅ Можно деплоить каждый микрофронтенд отдельно
✅ Автоматическое масштабирование возможно
✅ Отказоустойчивость (если один упадёт, остальные работают)

На что обратить внимание:

  • В production нужен SSL/TLS
  • Важно настроить кэширование
  • Мониторинг каждого микрофронтенда отдельно
  • CI/CD пайплайн для каждого приложения

🎓 Итог: Что вы научились делать

✅ Вы освоили:

  1. Создавать микрофронтенды на React с помощью Vite
  2. Изолировать стили и логику каждого приложения
  3. Объединять через Module Federation
  4. Управлять версиями независимо
  5. Деплоить в production с Docker

🚀 Где хранить микрофронтенды:

  1. Git репозитории — отдельный репозиторий для каждого
  2. Docker Registry — образы контейнеров
  3. CDN — статические файлы для быстрой загрузки
  4. NPM Registry — если публикуете как библиотеки

🔧 Как управлять версионностью:

  1. Semantic Versioning — major.minor.patch
  2. Feature Flags — постепенное включение функций
  3. A/B тестирование — разные версии для разных пользователей
  4. Канареечные развертывания — сначала 1% трафика, потом больше

💻 На каком языке писать:

-2

🎯 Для ИИ-агентов рекомендую:

  • React — для сложных дашбордов и визуализаций
  • Vue.js — для быстрого прототипирования интерфейсов
  • Svelte — для высокопроизводительных компонентов

📚 Технологии, которые позволяют создавать микрофронтенды:

1. Module Federation (Webpack 5)

javascript

// Самый популярный подход
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
}
})
]
};

2. Single-spa

javascript

// Framework-agnostic роутер
import { registerApplication, start } from 'single-spa';

registerApplication({
name: 'app1',
app: () => System.import('app1'),
activeWhen: '/app1'
});

start();

3. Qiankun (от Alibaba)

javascript

// Основан на single-spa, но проще
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
{
name: 'react-app',
entry: '//localhost:7100',
container: '#container',
activeRule: '/react'
}
]);

start();

4. iframe (самый простой)

html

<!-- Просто, но ограниченно -->
<iframe src="http://localhost:3001" width="100%" height="500"></iframe>

5. Web Components

javascript

// Нативные веб-компоненты
class MyElement extends HTMLElement {
connectedCallback() {
this.innerHTML = `<h1>Микрофронтенд</h1>`;
}
}

customElements.define('my-element', MyElement);

🎯 Следующие шаги для развития:

  1. Изучите State Management — как делиться состоянием между микрофронтендами
  2. Добавьте TypeScript — для типизации и лучшего DX
  3. Настройте мониторинг — Sentry, LogRocket для каждого приложения
  4. Оптимизируйте загрузку — lazy loading, code splitting
  5. Создайте дизайн-систему — общие компоненты для всех приложений

💡 Практические советы:

  • Начинайте с малого — один микрофронтенд, потом добавляйте
  • Тестируйте интеграцию — между микрофронтендами
  • Стандартизируйте — код-стайл, линтеры, тесты
  • Документируйте — API, версии, зависимости

⚠️ Чего избегать:

  • ❌ Глобальных стилей без изоляции
  • ❌ Прямых зависимостей между микрофронтендами
  • ❌ Разных версий одних и тех же библиотек
  • ❌ Сложной синхронизации состояния

Удачи в создании ваших ИИ-агентов с микрофронтендами! 🚀

Часто задаваемые вопросы:

Q: Как общаться между микрофронтендами?
A: Через Custom Events, Window свойства или выделенную библиотеку (Redux, Zustand).

Q: Что делать с авторизацией?
A: Единый сервис аутентификации, токены в куках или localStorage.

Q: Как тестировать микрофронтенды?
A: Каждый отдельно + интеграционные тесты контейнера.

Q: SEO для микрофронтендов?
A: SSR для каждого приложения или pre-rendering.

Q: Как дебажить?
A: Source maps + отдельные dev серверы для каждого приложения.