← Назад к вопросам
Была ли необходимость изолировать части сайта
1.8 Middle🔥 191 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Изоляция частей сайта: Web Components и Микрофронтенды
Да, я сталкивался с необходимостью изолировать части приложения и применял несколько подходов к этому.
Реальные сценарии, где нужна изоляция
1. Мультитенантное приложение
В одном проекте разрабатывал платформу с несколькими компаниями, где каждая компания может кастомизировать интерфейс:
Проблема:
✗ Стили одной компании влияют на другую
✗ JavaScript одного клиента может сломать другого
✗ Данные одной компании видны другой
✗ Сложно развивать части независимо
Решение: Веб-компоненты (Web Components) с Shadow DOM
Реализация:
// web-components/company-widget.ts
export class CompanyWidget extends HTMLElement {
constructor() {
super();
// Shadow DOM изолирует стили и DOM
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const companyId = this.getAttribute('company-id');
this.render(companyId);
}
private render(companyId: string) {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: Arial;
}
.company-card {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 8px;
background: white;
}
</style>
<div class="company-card">
<h2>Company: ${companyId}</h2>
<p>Изолированный контент</p>
</div>
`;
}
}
customElements.define('company-widget', CompanyWidget);
// Использование в HTML
// <company-widget company-id="client-1"></company-widget>
// <company-widget company-id="client-2"></company-widget>
// Каждый виджет изолирован и не влияет на другого
2. Микрофронтенды: независимые команды разрабатывают разные части
В крупном приложении с 5+ командами нужно было изолировать разработку:
Команда A: разрабатывает Header
Команда B: разрабатывает Dashboard
Команда C: разрабатывает Settings
Команда D: разрабатывает Payment
Проблема:
✗ Конфликты в одном bundle
✗ Одна команда может сломать всё
✗ Нельзя деплоить независимо
✗ Сложно масштабировать
Решение: Micro Frontends с Module Federation
Архитектура:
docker-compose.yml
├── container:host-app (5000) <- главное приложение
├── container:dashboard (5001) <- отдельное приложение
├── container:settings (5002) <- отдельное приложение
└── container:payment (5003) <- отдельное приложение
Host App динамически загружает модули от других контейнеров
Реализация с Webpack Module Federation:
// webpack.config.js (host app)
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
module.exports = {
webpack: (config, options) => {
config.plugins.push(
new NextFederationPlugin({
name: 'host',
filename: 'static/chunks/remoteEntry.js',
// Какие модули мы предоставляем
exposes: {
'./shared-auth': './lib/auth.ts',
'./shared-api': './lib/api.ts'
},
// Какие модули мы потребляем
remotes: {
dashboard: 'dashboard@http://localhost:5001/_next/static/chunks/remoteEntry.js',
settings: 'settings@http://localhost:5002/_next/static/chunks/remoteEntry.js',
payment: 'payment@http://localhost:5003/_next/static/chunks/remoteEntry.js'
},
// Общие зависимости (чтобы не было дублей)
shared: {
'react': { singleton: true, requiredVersion: '18.0.0' },
'react-dom': { singleton: true, requiredVersion: '18.0.0' }
}
})
);
return config;
}
};
// pages/index.tsx (host app)
import dynamic from 'next/dynamic';
const DashboardRemote = dynamic(
() => import('dashboard/dashboard'),
{ loading: () => <div>Loading Dashboard...</div> }
);
const SettingsRemote = dynamic(
() => import('settings/settings'),
{ loading: () => <div>Loading Settings...</div> }
);
export default function Home() {
return (
<div>
<h1>Main Application</h1>
<DashboardRemote />
<SettingsRemote />
</div>
);
}
3. Управление CSS в большом приложении
Проблема: Стили компонентов одной части влияют на другую часть.
❌ Проблема:
.button { background: blue; } // Это влияет на все .button в приложении!
✓ Решение 1: CSS-in-JS (эмуляция Shadow DOM)
import styled from 'styled-components';
const StyledButton = styled.button`
background: blue;
// Стили изолированы этим компонентом
`;
✓ Решение 2: BEM методология
.dashboard-button { background: blue; }
.settings-button { background: green; }
✓ Решение 3: CSS Modules
// dashboard.module.css
.button { background: blue; }
// dashboard.tsx
import styles from './dashboard.module.css';
<button className={styles.button}>Click</button>
// Автоматически: button_xyz1234 (уникальный класс)
Мой выбор: CSS Modules для части сайта, Shadow DOM для компонентов:
// dashboard/dashboard.module.css
.container {
padding: 2rem;
background: white;
border-radius: 8px;
}
.title {
font-size: 2rem;
color: #333;
}
// dashboard/page.tsx
import styles from './dashboard.module.css';
export function Dashboard() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Dashboard</h1>
</div>
);
}
// Скомпилируется как:
// <div class="dashboard_container__xyz1234">
// <h1 class="dashboard_title__xyz1234">
// Классы уникальны, не будут конфликтовать с другими частями
4. Iframe для полной изоляции (в экстремальных случаях)
Когда нужна абсолютная изоляция (например, для plugin системы):
// plugin-host.tsx
import { useRef, useEffect } from 'react';
export function PluginHost({ pluginUrl }) {
const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
if (!iframeRef.current) return;
const iframe = iframeRef.current;
// Коммуникация через postMessage
window.addEventListener('message', (event) => {
if (event.source !== iframe.contentWindow) return;
// Плагин просит данные
if (event.data.type === 'GET_USER_DATA') {
const userData = { id: 1, name: 'John' };
iframe.contentWindow.postMessage(
{ type: 'USER_DATA', data: userData },
'*'
);
}
});
iframe.src = pluginUrl;
}, [pluginUrl]);
return (
<iframe
ref={iframeRef}
style={{ width: '100%', height: '500px', border: 'none' }}
sandbox="allow-same-origin allow-scripts"
// sandbox атрибут изолирует: no local storage, no cookies, etc
/>
);
}
// plugin (внутри iframe, отдельное приложение)
// plugin.html
<script>
// Плагин запрашивает данные у хоста
window.parent.postMessage(
{ type: 'GET_USER_DATA' },
'*'
);
window.addEventListener('message', (event) => {
if (event.data.type === 'USER_DATA') {
console.log('Получены данные:', event.data.data);
}
});
</script>
Сценарии использования разных подходов
┌──────────────────┬────────────────┬──────────────────┐
│ Сценарий │ Подход │ Уровень изоляции │
├──────────────────┼────────────────┼──────────────────┤
│ Компонент │ CSS Modules │ Средний │
│ (button, card) │ BEM │ │
├──────────────────┼────────────────┼──────────────────┤
│ Виджет │ Web Components │ Высокий │
│ (таблица, форма) │ (Shadow DOM) │ │
├──────────────────┼────────────────┼──────────────────┤
│ Большая фича │ CSS Modules │ Средний │
│ (Dashboard) │ + разные папки │ │
├──────────────────┼────────────────┼──────────────────┤
│ Отдельная │ Module │ Очень высокий │
│ команда разработки│Federation │ (отдельная сборка)│
├──────────────────┼────────────────┼──────────────────┤
│ Плагин система │ Iframe + │ Максимальный │
│ (от 3-х лиц) │ postMessage │ │
└──────────────────┴────────────────┴──────────────────┘
Практический пример из моего опыта: Платформа с несколькими интеграциями
// Структура проекта
src/
├── shared/ // Общий код
│ ├── types/
│ ├── utils/
│ └── api/
│
├── modules/
│ ├── core/ // Core модуль (всегда загружен)
│ │ ├── Header.tsx
│ │ ├── Layout.tsx
│ │ └── core.module.css
│ │
│ ├── dashboard/ // Отдельный модуль (изолирован)
│ │ ├── Dashboard.tsx
│ │ ├── dashboard.module.css # CSS Modules
│ │ └── components/
│ │
│ ├── analytics/ # Отдельный модуль
│ │ ├── Analytics.tsx
│ │ ├── analytics.module.css # Изолированные стили
│ │ └── components/
│ │
│ └── integrations/ # Плагины/интеграции
│ ├── slack.tsx
│ ├── github.tsx
│ └── slack.module.css
// utils/isolation.ts
export const createIsolatedScope = (namespace: string) => {
const styles = document.createElement('style');
styles.textContent = `
.${namespace} {
all: initial; # Сбрасываем наследуемые стили
}
`;
document.head.appendChild(styles);
};
// modules/dashboard/Dashboard.tsx
import styles from './dashboard.module.css';
export function Dashboard() {
return (
<div className={styles.container}>
{/* Стили только в этом файле */}
<h1 className={styles.title}>Dashboard</h1>
<DashboardChart />
</div>
);
}
Когда я не использовал бы изоляцию
❌ Не нужна если:
• Маленькое приложение (1-3 разработчика)
• Не конфликты в styles
• Нет независимых команд
• Нет плагин-системы
✓ Нужна если:
• Большая команда (5+ разработчиков)
• Много модулей/фич
• Нужны плагины от 3-х лиц
• Разные teams=разные deploy
Итоговый вывод
Я использовал изоляцию когда:
- Web Components для переиспользуемых виджетов
- CSS Modules для изоляции стилей в больших приложениях
- Module Federation для микрофронтендов с независимыми командами
- Iframe для плагин-системы с максимальной безопасностью
Главный принцип: Используй только нужный уровень изоляции. Не усложняй если приложение маленькое.