На днях возникло желание написать простенькое расширение для 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 (dir => watchChanges (dir))
    }
})

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


Finally


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

Поделиться с друзьями
-->

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


  1. Garrett
    09.11.2016 04:07

    Полезно!
    Я даже сильно и не задумывался сколько времени теряю и как это не удобно вручную постоянно перезагружать


  1. mpakep
    09.11.2016 05:42

    Могли бы выложить свое расширение архивом готовым для подключения? Хотел бы взять его для образца. Спасибо.


    1. bustEXZ
      09.11.2016 14:24
      +1

      Github > Clone or download > Download ZIP?


    1. alex_blank
      09.11.2016 16:37

      Оно лежит готовым (в репозитории указан manifest.json), можете скачать папку из github и подключить через Load unpacked extension.


      1. mpakep
        11.11.2016 18:34

        Спасибо огромное. С этой проблемой перезагрузки уже не один год мучаюсь. Написал кослыль небольшой, но все равно работало не так как планировал. Огромное человеческое спасибо.


  1. Alex_flex
    09.11.2016 16:36

    Удалённо подгружать скрипты, не (execute)? В зависимости от требований расширения ставим на onClicked или onUpdated. Таким образом получаем extension с background, manifest и картинками. Всё остальное грузим с удалёнки.
    Причем для загрузки удалённого скрипта понадобится https. Ну в наше время бесплатно получить его не проблема.


  1. am-amotion-city
    09.11.2016 16:36
    +1

    > Совершенно непонятно, впрочем, как тестировать такие штуки.

    Навскидку придумывается вот что: создается «тестовое расширение», которое добавляет на страницу, скажем, строку. Загрузили, проверили, что исходная строка на странице есть. По живому перезаписали `background.js` у этого тестового расширения, изменив строку. Проверили, что старой строки на странице больше нет, а новая есть.