image

Привет! Меня зовут Георгий Костуров, я лид фронта в одной из команд СберМаркета. В один прекрасный день ко мне пришли менеджеры и сказали, что нужно написать браузерное расширение для внутреннего джобборда. Оно должно взаимодействовать с сайтами avito и hh, чтобы добавлять кандидатов в HR-систему, не скачивая и не вбивая вручную контакты из резюме на сайте.

Как человек, который ни разу не писал подобных вещей, расскажу о своем пути, о том с какими проблемами столкнулся и к каким пришел решениям.

На каком языке писать расширение?


Мой путь начался с поиска ответа на вопрос: «А хотя бы на каком языке придется это писать?» Полазив по интернету, с радостью обнаружил, что всё-таки это можно делать на JS. Хотя так было далеко не всегда. Первый браузер с API расширений, основанным исключительно на HTML, CSS и JavaScript, появился в 2010 году, им был Chrome. А до этого расширения необходимо было писать с использованием языка XUL (язык разметки на основе xml, обладающий большим функционалом по сравнению с HTML4).

Проблемы кроссбраузерности


То, что можно писать расширение на основе тех же технологий, что и обычный сайты — это, конечно, хорошо, но в разных браузерах код может работать немного по-разному. Как сейчас обстоят дела с кроссбраузерностью? Может есть какие-нибудь стандарты… И на самом дела, да, стандарт вроде как есть, но имеются три проблемы:

  • API расширений. Как я уже говорил, Chrome был первым браузером с простым API расширений. Firefox, видя набирающую популярность Chrome, начал поддерживать тот же API. Но среди остальных браузеров никакой стандартизации не было, и каждый представлял свои требования к расширениям. Ситуация изменилась в 2015 году. По инициативе Mozilla в рамках World Wide Web Consortium (W3C) была создана специальная группа для работы над спецификациями кроссбраузерных расширений и за основу был взят Chrome API. Черновик спецификации вышел в январе 2020 года. На данный момент его поддерживают Firefox, Edge и Opera. Самое забавное, что сам Google не участвовал в разработке этой спецификации и не поддерживает её. То есть стандарт во многом совпадает с Chrome API, но не полностью.
  • Версия манифеста. Манифест — файл в формате json, содержащий всю важную информацию о расширении (название, описание и т. д.), а также определяющий необходимые разрешения и выполняемые скрипты. На данный момент активно используются вторая и третья версии манифеста. Проблема в том, что в Chrome третья версия поддерживается с осени 2020 года, а с 2023 года прекращается поддержка второй версии. В то время как в Firefox поддержку третьей версии завезли только в мае 2022 года, и то со включенным экспериментальным флагом. По сути сейчас использовать третью версию ещё рано, а вторую уже поздно.
  • Apple опять выделяется. С Safari ситуация несколько сложнее. Расширение может быть только проектом XCode. Но начиная с 14-й версии появилась возможность сконвертировать расширение, написанное для другого браузера, в XCode-проект посредством одной команды в консоле.

Из каких элементов состоит расширение


Мы разобрались с кроссбраузерностью. Теперь давайте разбираться с самим расширением: как его писать и из каких элементов оно состоит.

Всё начинается с манифеста. Манифест — это точка входа для расширения, связывающая между собой остальные его части. Единственная вещь, без которой расширение не может существовать.

Помимо манифеста можно выделить следующие части расширения:

  • Background page. Страница, запускающаяся вместе с браузером и позволяющая выполнять какие-нибудь скрипты в фоновом режиме. Это актуально только для второй версии манифеста. В третьей ей на замену пришли веб-воркеры.
  • Content scripts. Скрипты, выполняющиеся непосредственно на странице и позволяющий взаимодействовать с DOM-элементами.
  • Browser action. Это иконка на панели инструментов браузера, при нажатие на которую может появляется попап, либо происходить другие действия. На эту иконку можно динамически воздействовать, изменяя её вид, текст и переопределяя поведение при нажатии.
  • Page action. Это иконка, возникающая в правом углу адресной строки (там где иконка добавления в избранное, перевод страниц и подобное). На неё также можно воздействовать динамически.
  • Popup. Всплывающее окно, представляющее собой самостоятельную страницу со своим body, скриптами и прочим. Непосредственно на страницу сайта воздействовать не может, но может включать в себя настройки и посылать какие-то сообщения другим частям расширения.
  • Option page. Полноценная веб страница для настроек расширения. Открывается в отдельной вкладке.
  • Resources. Дополнительные ресурсы типа картинок, которые могут потребоваться расширению для работы.

image
Схема архитектуры расширений. Источник: developer.mozilla.org

Ранее, когда рассказывал про попап, упоминал то, что он может посылать сообщения другим частям расширения. Собственно, разные части приложения могут обмениваться сообщениями между собой. Для этого существуют API runtime.sendMessage для отправки сообщения background и tabs.sendMessage для отправки сообщения странице (контент-скрипту, попапу или веб странице при наличии externally_connectable свойства в файле манифеста). Кроме того, можно открыть полноценное соединение и общаться приблизительно так же, как по вебсокетам.

Как пишут расширения в крупных компаниях


Перед тем, как приступать к разработке, надо посмотреть, как это делать правильно. Наверняка же у крупных компаний есть проверенные временем подходы, которыми они готовы поделиться. Может есть и готовые фреймворки.

Реальность оказалась менее радужной. Информации по браузерным расширениям очень мало, а компетенций ещё меньше. Большинство статей в интернете устарели. И даже многие свежие статьи, датированные последними двумя годами, рассказывают про Vanilla JS, не предоставляя комплексного подхода. Компании же зачастую пишут свои фреймворки, которые не покидают их стен.

Но несколько полезных материалов на тему разработки расширений я всё-таки нашёл. Некоторые из них про корпоративные разработки, другие — советы для новичков. Делюсь подборкой:

  • Выступление разработчика из «Тинькофф» про то, как они делали менеджер паролей. Они не нашли экспертизы внутри компании и разработали архитектуру с нуля. Поднимаются вопросы взаимодействия между различными частями расширения, синхронизации данных и внедрения своих элементов на страницу, а также вопрос тестирования.
  • Разработчик из Oxonit и ментор «Яндекс.Практикума» рассказывает про создание кроссбраузерного расширения для перевода и сохранения слов на React и Typescript.
  • Статья от Waves про разработку расширения для осуществления транзакций в блокчейн-сети. Лонгрид с пошаговым рассказом о проделанной работе. Поднимаются проблемы безопасности и особенности работы с блокчейном.
  • Небольшое руководство по написанию баузерного расширения с нуля от разработчика Avito.
  • Рассказ про создание браузерного расширения для работы с комментариями на Reddit. В частности рассказывают про инструменты для работы с мультиязычностью.

Чек-лист требований к расширениям в 2022


Раз все пишут по-своему, и нет никакой унификации, то я решил составить собственный чек-лист требований, учитывающий современные подходы к разработке сайтов, а дальше думать, как их реализовывать.

У меня получился следующий список:

  • TypeScript. Хочется иметь типизацию для упрощения отладки расширения, чтобы проблемы выплывали ещё на этапе компиляции. Ну серьезно, кто в 2к22 пишет без TypeScript?
  • Сборка. Раз у нас появился TypeScript, то должна быть и сборка. Кроме того компонентный подход никто не отменял.
  • Подключение готовой библиотеки компонентов. Раз заговорили про компонентный подход, существует множество готовых библиотек компонентов, которые повсеместно используются на большом количестве сайтов, которые уже протестированы и позволяют не писать свой велосипед. Да, у в каждой, хотя бы средней по размеру, компании-разработчика есть собственные библиотеки, которые можно переиспользовать между проектами.
  • Фреймворк. Хотелось бы использовать уже какой-нибудь фреймворк. Если я напишу что-то своё, то другие разработчики, безусловно, в этом разберутся и смогут поддерживать, но, если использовать фреймворк, то и разбираться не придется. К тому же браузерное расширение загружается на компьютер только один раз и нагрузки на сеть нет, поэтому такие понятия как размер бандла, time-to-first-byte и прочие к расширениям не применимы.
  • Кросбраузерность. Как говорил выше, с кросбраузерностью не все хорошо. Поэтому нужно уметь генерировать стандартный вариант для Chrome, версию со старым манифестом для FireFox и XCode style для Safari.
  • Hotreload. Необходимо подумать и об удобстве разработки. Будет здорово, если после внесения изменений в расширение, не придётся каждый раз собирать его и по новой загружать в браузер.
  • Изолирование стилей расширения от стилей страницы. Это не та проблема, что приходит первой на ум, но она есть. Поскольку мы пишем расширение и собираемся внедряться с его помощью на постороннюю страницу, стоит учитывать тот факт, что стили сайта будут влиять на наши компоненты, а стили наших компонентов — на компоненты сайта. В общем стандартная проблема микрофронтендов, имеющая стандартное решение в виде iframe и shadow-DOM.
  • Возможность работать с переменными окружения. Нужен какой-нибудь инструмент для хранения урлов бэкенда, ключей и прочих вещей, которые не стоит хардкодить и которые будут отличаться между dev-версией и продом.
  • Возможность написания тестов. Хочется, чтобы в проекте были интеграционные тесты на основную функциональность.
  • Синхронизация со стором. Каждый раз, когда открывается попап браузерного расширения, это считается как новое открытие сайта (отправляются запросы и прочее). Хочется, чтобы расширение умело сохранять состояние между открытиями попапа.
  • Генерация API по swagger схеме. Удобно генерировать API автоматически. Это позволяет синхронизировать типы между фронтом и бэком и не писать лишний бойлерплейт.

Фреймворк Plasmo — луч света в этой истории


Я уже опустил руки и приготовился писать свой велосипед. Но напоследок решил ещё порыться на форумах, вдруг повезёт. И о чудо, действительно повезло. Случайно наткнулся на новый проект — Plasmo Framework. Это полноценный фреймворк для создания расширений. Первый коммит датирован 02.05.22. То есть на момент поиска этой информации проекту не было и двух месяцев. О нём нет ни одной статьи ни на Habr, ни на Medium, но на тот момент было уже 3,5к звёзд (на момент написания статьи, то есть спустя два месяца, количество звёзд достигло 4,1к).

Проект быстро развивается и новые версии выходят с периодичностью в несколько дней. Когда я на него наткнулся в первый раз, была поддержка только Google Chrome, и в ишью появилась просьба о добавлении генерации манифеста второй версии для Firefox. Спустя всего четыре дня был готов новый релиз с поддержкой данного функционала. Ещё из примеров — изначально была поддержка только React, но сейчас добавились ещё Svelte и Vue.

На сайте проекта представлено следующее описание:
The Plasmo Framework is a battery-packed browser extension SDK made by hackers for hackers. Build your product and stop worrying about config files and the odd peculiarities of building browser extensions.

It's like Next.js for browser extensions!

Достаточно громкие заявления, но, поработав с ним, могу сказать, что уже сейчас он выглядит весьма удобным и во многом вдохновлялся Next.js. А учитывая то, что это по сути первый фреймворк для написания расширений, то у него есть неплохие перспективы стать эталонным вариантом.

Что умеет Plasmo


Разберемся с предоставляемым функционалом:

  • Для сборки использует Parcel. То есть сразу есть поддержка TypeScript, SCSS, dotenv и прочих прелестей разработки.
  • Поддерживает популярные фреймворки (React, Vue, Svelte).
  • Наличие hotreload
  • Отсутствие жесткой привязки к версии манифеста (описание основных вещей идёт в package.json, а всё остальное генерируется на лету под требуемую версию манифеста).
  • Есть удобные инструменты для добавления компонентов на страницу. Кроме того, эти компоненты автоматически изолируют свои стили через shadow DOM.
  • Инструменты для работы с Google Analytics.
  • Инструменты для загрузки расширения в магазины расширений.
  • В репозитории есть множество примеров использования совместно с другими библиотеками. В частности ant-design, jest, dotenv, i18n, next.js, redux-toolkit и многими другими.

По сути Plasmo выполняет все те требования, которые я ранее сформулировал.

Что в итоге получилось


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

Отличия возникают при взаимодействии с url, общением между скриптами попапа и скриптами, выполняющимися на основной странице, и прочими специфическими вещами. В остальном же это самый обычный сайт.

В моем случае это сайт на React с авторизацией. Для хранения данных и отправки запросов используется Redux Toolkit, а сами данные синхронизированы с браузерным стором.

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

image

А само расширение выглядит так:

image
По результатам использования Plasmo могу сказать, что это удобный фреймворк, который значительно упрощает разработку расширений. Рекомендую к использованию.

Для тех, кому показалось, что в этой статье мало конкретики и практических примеров,
6 сентября в рамках Frontend Meetup, освещу эту тему подробнее.

На live-coding сессии, мы ещё раз разберем структуру расширения, создадим каркас c использованием популярных библиотек и фреймворков и на его основе напишем простенькое, но соответствующее современным тенденциям расширение.

Регистрируйтесь по ссылке, буду рад ответить на все ваши вопросы.

Надеюсь мой опыт был полезен, делитесь в комментариях, как вы решали подобные вопросы)

Комментарии (4)


  1. Lazytech
    02.09.2022 07:17
    +1

    Отсутствие жесткой привязки к версии манифеста (описание основных вещей идёт в package.json, а всё остальное генерируется на лету под требуемую версию манифеста).

    Вот что я нашел касательно этого: https://github.com/PlasmoHQ/plasmo/issues/147

    == НАЧАЛО ЦИТАТЫ ==

    plasmo takes the manifest and determine if it's MV2 or MV3.

    • If MV3, it keeps the basic structure and convert most of the config into the package.json

    • If MV2, it convert the manifest into mv3 and throw them into package.json as much as possible, and provides guideline on how to migrate their project from there.

    == КОНЕЦ ЦИТАТЫ ==

    То есть, если я правильно понял, на входе может быть либо Manifest V2, либо Manifest V3, а на выходе будет только Manifest V3. Причем, если на входе будет Manifest V2, в отдельных случаях может потребоваться ручная миграция с V2 на V3. Читайте, об "автоматической" кросс-браузерности пока можно забыть, учитывая достаточно большую разницу между V2 и V3.

    Оговорюсь, что у меня весьма скромный опыт разработки браузерных расширений. Если где-то выше ошибся, прошу поправить.


    1. Geosins Автор
      02.09.2022 10:10
      +1

      Не совсем. Это предложение по улучшению, чтобы можно было инициализировать проект на основе уже существующего манифеста, то есть для миграции уже существующего расширения в инфраструктуру Plasmo.

      • How do you envision this feature/change to look/work like?
        - pnpm create plasmo
         OR pnpm dlx plasmo init OR plasmo init inside a directory with a manifest.json


      Если посмотреть на скрин со структурой папок, то там видно, что файла manifest.json в проекте нет. Все вещи описываются в package.json, а потом по команде plasmo build проходит сборка и генерируется манифест с ссылками на все используемые скрипты и ресурсы.

      Два месяца назад было ишью, о котором я упоминал в статье с просьбой добавить возможность генерации второй версии манифеста. В результате добавили флаг target. И теперь появилась возможность выполнить следующую команду: plasmo build --target=firefox-mv2

      Ну и к тому же проект развивается и, если сейчас какая-то функциональность не поддерживается, то думаю это доработают


  1. fiter1
    02.09.2022 10:13

    Спасибо за статью. Как вы думаете , насколько большое будущее у Plasmo Framework ?


    1. Geosins Автор
      02.09.2022 10:25

      На самом деле многое зависит от продвижения и рекламы. Поскольку даже крутые проекты могут не сыскать известности, а менее качественными продолжают пользоваться только потому, что они на слуху.

      Мне кажется, что шансы на большое будущее есть, поскольку фреймворк уже в текущем состоянии весьма удобен, и что самое главное - он первый в своём роде.

      Но его создателям я бы рекомендовал писать статьи на профильных ресурсах и выступать на митапах. Собственно этой своей статьей вношу посильную лепту)