Горячая перезагрузка Chrome-расширения

от автора

На днях возникло желание написать простенькое расширение для Google Chrome. Столкнулся с такой проблемой, что после изменениях в коде расширения, браузер не перезагружает его автоматически. Это очень сильно затрудняет разработку, т.к. после каждого Cmd-S в редакторе, приходится нажимать "Reload" в списке расширений, а затем еще и рефрешить страницу, чтобы перезапустить контент-скрипты.

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

Готовое встраиваемое решение лежит на github.com/xpl/crx-hotreload, а в этой статье я расскажу, как оно реализовано.

Используем File and Directory Entries API для рекурсивного получения списка файлов в папке:

const filesInDirectory = dir => new Promise (resolve =>      dir.createReader ().readEntries (entries =>          Promise.all (entries.filter (e => e.name[0] !== '.').map (e =>              e.isDirectory                 ? filesInDirectory (e)                 : new Promise (resolve => e.file (resolve))         ))         .then (files => [].concat (...files))         .then (resolve)     ) )

Генерируем «сборный» timestamp из всех timestamp’ов полученных файлов:

const timestampForFilesInDirectory = dir =>         filesInDirectory (dir).then (files =>             files.map (f => f.name + f.lastModifiedDate).join ())

Таким образом, мы можем детектировать не только изменения в файлах, но и их удаление/добавление/переименование.

Вотчдог, проверяющий изменения каждые 1000мс:

const watchChanges = (dir, lastTimestamp) => {      timestampForFilesInDirectory (dir).then (timestamp => {          if (!lastTimestamp || (lastTimestamp === timestamp)) {              setTimeout (() => watchChanges (dir, timestamp), 1000) // retry after 1s          } else {             reload ()         }     })  }

Перезагрузка расширения и активной вкладки:

const reload = () => {      chrome.tabs.query ({ active: true, currentWindow: true }, tabs => {          if (tabs[0]) { chrome.tabs.reload (tabs[0].id) }          chrome.runtime.reload ()     }) }

Перезагрузка вкладки вызывается до runtime.reload, иначе она не сработает — вызов runtime.reload прекращает выполнение скрипта. Но поскольку перезагрузка вкладки отрабатывает асинхронно, то в итоге всё перезагружается в корректном порядке — хоть и выглядит в коде нелогично.

Ну и финальный штрих — запускаем вотчдог, натравленный на папку с кодом расширения. Но делаем это только если расширение загружено в режиме разработчика, через "Load unpacked extension":

chrome.management.getSelf (self => {      if (self.installType === 'development') {          chrome.runtime.getPackageDirectoryEntry (watchChanges)     } })

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

Finally

Вот, в общем-то, и всё. Совершенно непонятно, впрочем, как тестировать такие штуки. Вряд ли какой-нибудь Selenium здесь поможет, или всё-таки? Фидбек приветствуется.

ссылка на оригинал статьи https://habrahabr.ru/post/314766/


Комментарии

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

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