Разрабатываем браузерное расширение легким движением руки

от автора

Hello, world!

В этом небольшом туториале мы с вами разработаем простое, но полезное расширение для браузера с помощью Plasmo.

Наше расширение будет представлять собой вызываемый сочетанием клавиш попап с инпутом для поиска информации на MDN с выводом 5 лучших результатов в виде списка. Кроме основного функционала, мы добавим страницу настроек для кастомизации цветов и отображения хлебных крошек. Мы будем разрабатывать расширения для Chrome, которое также будет работать в Firefox.

Вот как это будет выглядеть:

Для тех, кого интересует только код, вот ссылка на соответствующий репозиторий.

Интересно? Тогда прошу под кат.

Основной функционал — попап с поиском

Для работы с зависимостями будет использоваться Yarn.

Создаем шаблон приложения:

# mdn-finder - название приложения/расширения yarn create plasmo mdn-finder

Переходим в созданную директорию и устанавливаем зависимости:

cd mdn-finder  yarn

Устанавливаем дополнительные зависимости, необходимые для работы поиска:

yarn add @plasmohq/storage downshift flexsearch fzf swr

  • @plasmohq/storage — абстракция над Storage API, который может использоваться расширениями браузера для локального хранения данных;
  • downshift — библиотека, предоставляющая примитивы для разработки простых, гибких, отвечающих всем критериям WAI-ARIA React-компонентов autocomplete/combobox или select/dropdown;
  • flexsearch — библиотека для реализации полнотекстового поиска;
  • fzf — библиотека для реализации неточного (fuzzy) поиска;
  • swr — хуки React для получения, кэширования и мутации данных.

Структура проекта будет следующей:

- assets   - icon.png   - search-index.json   - search.png - src   - components     - Search.tsx   - search     - fuzzy-search.ts     - search-utils.ts     - search.tsx   - background.ts   - options.tsx   - popup.tsx   - storage.ts   - style.css   - ...

После переноса файлов в директорию src, необходимо немного отредактировать файл tsconfig.json:

{   // ...   "compilerOptions": {     "baseUrl": ".",     "paths": {       "~*": [         "./src/*"       ]     }   } }

О самом поиске я рассказывал в этой статье, поэтому в данном туториале мы сосредоточимся на Plasmo. Скопируйте файлы из директорий components, search и assets, а также файл style.css из репозитория проекта. Поисковый индекс (search-index.json), также можно копировать с MDN. Запросы к MDN из другого источника блокируются CORS, поэтому поисковый индекс хранится локально.

Для того, чтобы иметь возможность работать с поисковым индексом, необходимо немного отредактировать файл package.json:

{   // ...   "manifest": {     "web_accessible_resources": [       {         "resources": [           "assets/search-index.json"         ],         "matches": [           "https://*/*"         ]       }     ],     "host_permissions": [       "https://*/*"     ]   } }

Точкой входа приложения Plasmo является файл popup.tsx. Как следует из названия, этот компонент отвечает за рендеринг попапа, в котором будет находиться инпут для поиска. Редактируем этот файл следующим образом:

import Search from './components/Search' import './style.css'  function IndexPopup() {   return <Search preload={true} /> }  export default IndexPopup

Запускаем сервер для разработки с помощью команды yarn dev. Выполнение этой команды приводит к генерации директории build/chrome-mv3-dev с файлами расширения.

Переходим по адресу chrome://extensions/ и загружаем расширение в браузер (кнопка «Загрузить распакованное расширение»/»Load unpacked extension»):

В режиме разработки расширение, загруженное в браузер, автоматически обновляется при изменении соответствующих файлов.

Сочетание клавиш для запуска расширения можно установить на странице chrome://extensions/shortcuts:

Для создания производственной сборки необходимо выполнить команду yarn build. По умолчанию создается сборка для Chrome. В настоящее время Plasmo также поддерживает создание сборок для Firefox. Команда для создания такой сборки: yarn build --target=firefox-mv2. Подробнее почитать об этом можно здесь.

Для тестирования расширения в Firefox необходимо сделать 2 вещи:

  • создать в директории src файл background.ts следующего содержания:

export {}

Этот файл предназначен для запуска скриптов, отвечающих за выполнение фоновых задач. К таким скриптам относится, например, логика сервис-воркера. Подробнее почитать об этом можно здесь. Почему-то без этого файла расширение в Firefox не запускается.

  • создать производственную сборку в виде архива с помощью команды yarn build --target=firefox-mv2 --zip.

Дополнительный функционал — страница настроек

Для инициализации страницы настроек достаточно создать файл options.tsx в директории src.

Простейшим способом обмена данными между попапом и страницей настроек (а также другими скриптами) является использование предоставляемого Plasmo хранилища.

Создаем в директории src файл storage.ts следующего содержания:

import { Storage } from '@plasmohq/storage'  // ключ объекта настроек export const OPTIONS_KEY = 'mdn_finder_options'  // дефолтные настройки export const defaultOptions = {   // цвет фона   backgroundColor: '#282c34',   // цвет текста   textColor: '#f7f7f7',   // фон выделения   selectionBackground: '#5cb85c',   // цвет выделения   selectionColor: '#282c34',   // индикатор отображения хлебных крошек в списке результатов поиска   showUrl: true }  // создаем экземпляр хранилища const storage = new Storage()  // и экспортируем его export default storage

Редактируем файл options.tsx следующим образом:

import { useRef } from 'react' import storage, { defaultOptions, OPTIONS_KEY } from '~storage' import './style.css'  export default function IndexOptions() {   // ссылка на кнопку отправки формы   const btnRef = useRef<HTMLButtonElement | null>(null)    // обработчик отправки формы   const onSubmit: React.FormEventHandler = async (e) => {     e.preventDefault()      // получаем данные формы в виде объекта     const formData = Object.fromEntries(       new FormData(e.target as HTMLFormElement).entries(),     )      try {       // записываем настройки в хранилище       await storage.set(OPTIONS_KEY, formData)        // меняем текст кнопки       if (btnRef.current) {         btnRef.current.textContent = 'Saved'          const id = setTimeout(() => {           btnRef.current.textContent = 'Save'           clearTimeout(id)         }, 1000)       }     } catch (e) {       console.log(e)     }   }    return (     <form className='options' onSubmit={onSubmit}>       <label>         Background color:{' '}         <input           type='color'           name='backgroundColor'           defaultValue={defaultOptions.backgroundColor}         />       </label>       <label>         Result item color:{' '}         <input           type='color'           name='textColor'           defaultValue={defaultOptions.textColor}         />       </label>       <label>         Selection background:{' '}         <input           type='color'           name='selectionBackground'           defaultValue={defaultOptions.selectionBackground}         />       </label>       <label>         Selection color:{' '}         <input           type='color'           name='selectionColor'           defaultValue={defaultOptions.selectionColor}         />       </label>       <label>         Show URL:{' '}         <input           type='checkbox'           name='showUrl'           defaultChecked={defaultOptions.showUrl}         />       </label>       <button ref={btnRef}>Save</button>     </form>   ) }

Для того, чтобы попасть на страницу настроек, необходимо кликнуть по иконке расширения и выбрать пункт «Параметры»/»Options»:

Возвращаемся к попапу. Редактируем файл search/search.tsx. Импортируем хранилище и извлекаем из него настройки:

import storage, { defaultOptions, OPTIONS_KEY } from '~storage'  // ...  function InnerSearchNavigateWidget(props: InnerSearchNavigateWidgetProps) {   // ...   const [options, setOptions] = useState(defaultOptions)    // ...   useEffect(() => {     storage.get<typeof options>(OPTIONS_KEY).then((opts) => {       if (opts) {         setOptions(opts)       }     })   }, [])    // далее работаем с этим компонентом }

Индикатор отображения хлебных крошек (options.showUrl) используется при формировании списка результатов поиска:

resultItems.map((item, i) => (   <div     {...getItemProps({       key: item.url,       className:         'result-item ' + (i === highlightedIndex ? 'highlight' : ''),       item,       index: i,     })}   >     <HighlightMatch title={item.title} q={inputValue} />     {/* ! */}     {Boolean(options.showUrl) ? (       <>         <br />         <BreadcrumbURI uri={item.url} positions={item.positions} />       </>     ) : null}   </div> ))

Цвет фона (options.backgroundColor) передается элементу формы:

<form   // ...   style={     {       '--background-color': options.backgroundColor,     } as React.CSSProperties   } >   {/* ... */} </form>

В файле style.css у нас имеются такие строки:

.search-form {   --background-color: var(--dark);    /* ... */   background-color: var(--background-color); }

Остальные цвета передаются контейнеру с результатами поиска:

<div   className='search-results'   style={     {       '--text-color': options.textColor,       '--selection-background': options.selectionBackground,       '--selection-color': options.selectionColor,     } as React.CSSProperties   } >   {searchResults} </div>

В style.css у нас имеются такие строки:

.search-results {   --text-color: var(--light);   --selection-background: var(--success);   --selection-color: var(--dark); }  .result-item span, .result-item small {   color: var(--text-color); }  .result-item mark {   background-color: var(--selection-background);   color: var(--selection-color); }

Спасибо переменным CSS за их динамичность 🙂

Меняем настройки:

Запускаем расширение:

Видим, что настройки благополучно применяются к попапу.

Следует отметить, что проект, созданный с помощью Plasmo CLI, включает в себя GitHub Action Browser Platform Publisher для автоматической публикации расширения во всех поддерживаемых сторах. Подробнее почитать об этом можно здесь. Соответствующий файл можно найти в директории .github/workflows.

К слову, поисковый индекс со статьями на русском языке можно найти здесь.

Надеюсь, вы узнали что-то новое и не зря потратили время.

Happy coding!



ссылка на оригинал статьи https://habr.com/ru/company/timeweb/blog/720646/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *