Разберём микрофронтенд через историю вымышленного хакера — и заодно поймём, почему это спрашивают на собеседованиях.
Статья собрана по заметкам в телеграм канале.
Недавно на собесе меня спросили: "А как именно работают микрофронты? Там что, прямо eval используют?"
Я что-то промямлил про expose, host, сборку... и понял, что вообще не понимаю сути. Знакомо?
После этого я потратил неделю на изучение webpack изнутри. Чтобы запомнить материал, придумал историю про горе-хакера.
Это образовательная статья. Фишинг — это уголовка.
Пролог
Иван Донов Джон Доу на информатике услышал про "фишинг", "XSS", а главное про "крипту"! После пятёрки за год он считает себя крутым хацкером. Его амбициозный план — взломать несколько учёток GitHub, закинуть в репозитории майнер крипты и стричь капусту.
Джон не дурак: зачем верстать с нуля, если можно взять настоящий GitHub?
iframe
Джон узнал про старую технологию <iframe>. Идея простая: встроить настоящий GitHub и перехватить данные.
<!-- гитхаб.рф/index.html --> <html> <body> <iframe src="github.com/login"></iframe> </body> </html>
Почему не работает?

Форма внутри iframe отправляет данные напрямую на GitHub. Броузер не даёт JavaScript доступ к содержимому iframe с другого домена (правила Same-Origin Policy).
const loginFrame = document.querySelector("iframe"); const form = loginFrame.contentDocument.querySelector("form"); // ❌ Uncaught DOMException: Blocked a frame with origin "гитхаб.рф" // from accessing a cross-origin frame.
Историческая справка
Представь: у тебя super app — одно приложение с кучей фич. Звонки, календарь, чаты, платежи. Некий аналог приложения российского Т-банк, китайского WeChat или арабского Telegram. Как разбить это на команды?
Эволюция подходов
iframe:
<iframe src="https://gifts-team.telegram.com"></iframe> <iframe src="https://ton-team.telegram.com"></iframe> <iframe src="https://stickers.telegram.com"></iframe>
Разбили, но как обеспечить обмен данными между ними? (сложно).
Монорепозиторий:
import Gifts from "../teams/gifts/Gifts"; import Ton from "../teams/ton/Ton"; import Stickers from "../teams/stickers/Stickers"; function App() { return ( <> <Gifts /> <Ton /> <Stickers /> </> ); }
Git конфликты, долгая сборка проекта – катим всё или ничего.
Module Federation:
import Stickers from 'stickers-team@https://stickers-team.telegram.com/remoteEntry.js'
Независимые команды, независимая раскатка, загрузка по сети.
Module Federation
Джон узнал, что webpack умеет загружать код с других серверов. Новый план: найти LoginForm на GitHub и подгрузить к себе.
Как работает Module Federation под капотом
Когда приложение делает import('loginApp/LoginForm'), webpack проходит 4 шага:
Шаг 1: Создаётся
const script = document.createElement('script'); script.src = 'https://github.com/remoteEntry.js'; document.head.appendChild(script);
Шаг 2: remoteEntry.js регистрируется в window
// Содержимое remoteEntry.js (упрощённо): window.loginApp = { init: function(hostShared) { // Синхронизация зависимостей (React, etc.) this._shared = hostShared; }, get: function(moduleName) { // Загрузка конкретного модуля return import('./chunks/' + moduleName + '.js'); } };
Шаг 3: Host вызывает init()
await window.loginApp.init({ react: { '18.2.0': { get: () => import('react') } } }); // "Эй, у меня React 18.2.0, используй его"
Шаг 4: Host вызывает get()
const LoginForm = await window.loginApp.get('./LoginForm'); // Броузер загружает и выполняет модуль
Конфигурация webpack
// webpack.config.js хакера module.exports = { plugins: [ new ModuleFederationPlugin({ name: "phishingShell", remotes: { githubLogin: "githubLogin@https://github.com/remoteEntry.js" } }) ] };
// App.jsx хакера const LoginForm = React.lazy(() => import("githubLogin/LoginForm")); function App() { const handleCredentials = (username, password) => { // Джон мечтает прочитать данные здесь... }; return <LoginForm onSubmit={handleCredentials} />; }
Чего-то не хватает
Джон запустил сервер, открыл страницу и... стили формы поехали. Тема не подхватилась. Не похоже на гитхаб ☹️
Проблемы
LoginForm ожидает CSS стили от родительского приложения.
LoginForm использует
ThemeContextдля определения темы.
Module Federation хорошо работает когда каждому компоненту известен контракт – набор правил, которых от него ожидают.
Исправления
Чтобы микрофронтенд заработал, Джону нужно эмулировать родительское приложение – GitHub.
ThemeContext ожидается как shared-зависимость.
Создаём контекст:
import { createContext } from "react"; export const ThemeContext = createContext("light"); export default ThemeContext;
Добавляем ThemeProvider:
import React from "react"; import { ThemeContext } from "githubLogin/ThemeContext"; function ThemeProvider({ children }) { return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>; } export default ThemeProvider;
Джону нужно подключить CSS-фреймворк, который использует GitHub.
<html> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" /> </head> <body> <div id="root"></div> </body> </html>
А что, так можно было?
Джон реализовал свой план. Но почему в реальности этого бы не случилось?
1. CORS

GitHub никогда не вернёт:
Access-Control-Allow-Origin: https://гитхаб.рф
2. Content Security Policy
Даже если бы CORS не было, GitHub использует CSP:
Content-Security-Policy: script-src 'self' github.githubassets.com
Это значит: "Выполняй только скрипты с github.com и github.githubassets.com".
3. Subresource Integrity
Хитрый метод проверки хэшей микрофронтов.
Практические выводы для собесов:
Module Federation — способ делиться модулями между независимыми webpack-сборками
Remote — тот, кого подгружают
Host — тот, кто подгружает
remoteEntry.js — файл с функциями
init()иget()Shared dependencies — общие библиотеки (React), чтобы не грузить дважды
Код встраивается в броузер через
<script>– всё безопасно
Код с примерами
Литература
Подписывайтесь на канал в Telegram — там разбираю JavaScript и фронтенд для подготовки к собеседованиям.
