Как написать кроссбраузерное расширение в 2022 году
Привет! Меня зовут Георгий Костуров, я лид фронта в одной из команд СберМаркета. В один прекрасный день ко мне пришли менеджеры и сказали, что нужно написать браузерное расширение для внутреннего джобборда. Оно должно взаимодействовать с сайтами 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. Полноценная веб страница для настроек расширения. Открывается в отдельной вкладке.
- Rosources. Дополнительные ресурсы типа картинок, которые могут потребоваться расширению для работы.
Схема архитектуры расширений. Источник: 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, а сами данные синхронизированы с браузерным стором.
Структура папок получилась следующей:
А само расширение выглядит так:
По результатам использования Plasmo могу сказать, что это удобный фреймворк, который значительно упрощает разработку расширений. Рекомендую к использованию.
Для тех, кому показалось, что в этой статье мало конкретики и практических примеров,
6 сентября в рамках Frontend Meetup, освещу эту тему подробнее.
На live-coding сессии, мы ещё раз разберем структуру расширения, создадим каркас c использованием популярных библиотек и фреймворков и на его основе напишем простенькое, но соответствующее современным тенденциям расширение.
Регистрируйтесь по ссылке, буду рад ответить на все ваши вопросы.
Надеюсь мой опыт был полезен, делитесь в комментариях, как вы решали подобные вопросы)