Зауважте: коли користувач клікає на іконку розширення, він очікує миттєвого відгуку. Але часто 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.







