Разберём микрофронтенд через историю вымышленного хакера — и заодно поймём, почему это спрашивают на собеседованиях.
Статья собрана по заметкам в телеграм канале.
Недавно на собесе меня спросили: "А как именно работают микрофронты? Там что, прямо 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 и фронтенд для подготовки к собеседованиям.