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

mk0emfkez9es5uyh-ahvqmucgdw.png


Hello, world!

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

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

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


2b22wndyxvqm1_axjfttw0c-hlg.png

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

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


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

Для работы с зависимостями будет использоваться 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 
}

export default IndexPopup

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

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


rkdj61shaakmqltsfl_xnd09_2i.png

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

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


num9r47zvohex0x5po872p3kf-q.png

Для создания производственной сборки необходимо выполнить команду 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(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 (
    
) }

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


y0jdx3c7reh2pb5epfzefwbuhrm.png
t9lcolgtrtfxdfgglyy0i6eq6py.png

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

import storage, { defaultOptions, OPTIONS_KEY } from '~storage'

// ...

function InnerSearchNavigateWidget(props: InnerSearchNavigateWidgetProps) {
  // ...
  const [options, setOptions] = useState(defaultOptions)

  // ...
  useEffect(() => {
    storage.get(OPTIONS_KEY).then((opts) => {
      if (opts) {
        setOptions(opts)
      }
    })
  }, [])

  // далее работаем с этим компонентом
}

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

resultItems.map((item, i) => (
  
{/* ! */} {Boolean(options.showUrl) ? ( <>
) : null}
))

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

{/* ... */}

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

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

  /* ... */
  background-color: var(--background-color);
}

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

{searchResults}

В 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 за их динамичность :)

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


kpqycx4aarajdpf7yy5dlxvuphk.png

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


aaqclzqzkyswknzlde70pav7h5u.png

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

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

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

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

Happy coding!


© Habrahabr.ru