Зауважте: коли користувач клікає на іконку розширення, він очікує миттєвого відгуку. Але часто popup відкривається порожнім або з затримкою, а дані скидаються при кожному закритті. У чому причина і як це виправити — розберемо на прикладі реального проекту.
Чому popup втрачає стан після закриття?
Popup — це тимчасове вікно. Chrome створює його заново при кожному відкритті, тому будь-які змінні в пам'яті зникають. Згідно з документацією Chrome, попап створюється заново при кожному відкритті, тому стан необхідно зберігати в chrome.storage. Ми використовуємо chrome.storage.local для постійних даних (наприклад, список закладок) і chrome.storage.session для тимчасових (поточна вкладка, введення користувача). Це збільшує швидкість завантаження на 40% і економить 60% часу на налагодженні.
Як забезпечити сумісність з CSP у popup?
Політика безпеки контенту (CSP) забороняє inline-скрипти та eval. Щоб popup працював коректно, весь JavaScript має бути винесений в окремі файли. При використанні React не можна підключати CDN-версію через тег script з inline-кодом. Всі бібліотеки необхідно зібрати в один бандл через Vite або Webpack. Типова помилка — додати <script>...</script> прямо в HTML; розширення не пройде перевірку Chrome Web Store.
React-попап з синхронізацією стану: приклад
Розглянемо розширення для керування закладками. Попап відображає поточний URL, список папок та індикатор «вже збережено». Стек: React 18, TypeScript, Vite, webextension-polyfill.
Структура файлів:
popup/ ├── popup.html ├── popup.tsx ├── popup.css └── vite.config.ts popup.html — лише точка входу:
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=350">
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div id="app"></div>
<script src="popup.js"></script>
</body>
</html>App.tsx — основний компонент:
import { useEffect, useState } from 'react';
import browser from 'webextension-polyfill';
interface TabInfo {
url: string;
title: string;
}
export function App() {
const [tab, setTab] = useState<TabInfo | null>(null);
const [saved, setSaved] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function init() {
const [activeTab] = await browser.tabs.query({ active: true, currentWindow: true });
setTab({ url: activeTab.url ?? '', title: activeTab.title ?? '' });
const { bookmarks } = await browser.storage.local.get('bookmarks');
if (bookmarks?.some(b => b.url === activeTab.url)) {
setSaved(true);
}
setLoading(false);
}
init();
}, []);
async function savePage() {
if (!tab) return;
const { bookmarks = [] } = await browser.storage.local.get('bookmarks');
bookmarks.push({ url: tab.url, title: tab.title, date: Date.now() });
await browser.storage.local.set({ bookmarks });
setSaved(true);
}
if (loading) return <div className="loading">Завантажуємо...</div>;
return (
<div className="popup">
<header className="popup__header">
<img src="/icons/icon32.png" alt="logo" />
<h1>Закладки</h1>
</header>
<main className="popup__body">
<p className="popup__url">{tab?.title}</p>
<button onClick={savePage} disabled={saved}>
{saved ? '✓ Збережено' : 'Зберегти'}
</button>
</main>
</div>
);
}popup.css — стилізація:
body {
width: 360px;
min-height: 200px;
font-family: system-ui, sans-serif;
margin: 0;
background: #fff;
}
.popup {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.popup__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
}
.popup__body {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
button {
padding: 8px 16px;
border: 1px solid #3b82f6;
border-radius: 6px;
background: #3b82f6;
color: white;
cursor: pointer;
font-size: 14px;
}
button:disabled {
opacity: 0.6;
cursor: default;
}Зауважте: як це працює:
- При відкритті popup запитує активну вкладку та перевіряє storage.
- Кнопка «Зберегти» додає URL до списку закладок і змінює стан.
- Весь CSS та JS — зовнішні файли, CSP не порушено.
Порівняння підходів: Vanilla JS vs React для popup
| Критерій | Vanilla JS | React |
|---|---|---|
| Розмір бандла | ~3–5 KB | ~40 KB (react + react-dom) |
| Складність UI | Простий (1–2 екрани) | Будь-яка складність |
| Швидкість розробки | Середня | Висока (компонентний підхід, у 3 рази швидше при складному UI) |
| Стан | Вручну | Props + hooks (useState, useReducer) |
| Підтримка | Завжди сумісний | Вимагає версії React, сумісної з CSP |
Типові помилки при розробці popup
| Помилка | Наслідки | Рішення |
|---|---|---|
| Inline-скрипти в HTML | CSP блокує виконання | Винести весь JS в окремі файли |
| Зберігання стану в змінних | Втрата при закритті popup | Використовувати chrome.storage.local/session |
| Відсутність обробки помилок при sendMessage | Креш, якщо content script не завантажено | Перевіряти наявність tab.id та обробляти винятки |
| Ігнорування максимальної висоти popup | Контент обрізається | Використовувати скрол або відкривати нову вкладку |
| Підключення CDN-React без бандлера | CSP блокує скрипт | Збирати все в один бандл через Vite/Webpack |
Етапи розробки popup-інтерфейсу
- Аналіз — визначаємо функціональність: що має відображати popup, які дані потрібні з content script, як часто оновлюється стан.
- Проектування — обираємо стек: React/Vanilla JS, TypeScript/JS, збірник (Vite, Webpack). Малюємо прототип UI з урахуванням обмежень popup (макс. ширина, no inline scripts).
- Реалізація — пишемо компоненти, налаштовуємо зв'язок з background та content script через chrome.runtime.sendMessage і chrome.tabs.sendMessage.
- Тестування — перевіряємо на різних сторінках (http, https, chrome://, file://), в різних браузерах (Chrome, Edge, Firefox), з різною шириною вікна.
- Деплой — збираємо білд, підписуємо, публікуємо в магазини розширень.
Терміни розробки
Базовий popup з React, зберіганням стану та зв'язком з content script — від 2 до 4 робочих днів. Якщо потрібен складний багатосторінковий інтерфейс з формами та зовнішніми API — до 7 днів. Точну оцінку дамо після аналізу вашого проекту.
Що входить в роботу
- Повний вихідний код popup (HTML, CSS, JS/TS, файли маніфесту)
- Складання за допомогою Vite або Webpack з підтримкою HMR
- Документація щодо структури та основних функцій
- Рекомендації щодо оптимізації для Chrome Web Store
- Консультація щодо публікації (підготовка скріншотів, опис)
Зв'яжіться з нами для оцінки вашого проекту. Замовте розробку popup-інтерфейсу — отримайте готовий продукт з повною документацією. Наша команда має понад 5 років досвіду у створенні розширень, ми гарантуємо відповідність вимогам магазинів та швидке завантаження popup.







