Pull to refresh

Кэширование кода в веб-приложениях

Level of difficultyMedium
Reading time4 min
Views4.7K

Эта статья — изложение персонального опыта работы с кэшем на стороне браузера при создании веб‑приложений. В повседневной разработке я использую десктопный Chrome. У него есть панель инструментов и он в принципе удобен для разработчика. Но когда нужно проверять приложение на смартфонах, начинается геморрой — каким образом доставить на смартфон новый код, если там уже есть старый? Больше всего меня бесит Safari on iPhone. Если в Chrome есть возможность удалить все данные для отдельного сайта, то в iPhone все данные удаляются для всего Safari. Если и есть в iPhone какой‑нибудь способ удалить через конфигурацию смартфона/приложения данные для отдельного сайта, то мне так и не удалось его найти. Буду благодарен, если кто‑либо мне о нём сообщит в комментах.

Что кэшировать в веб-приложениях?

Что такое веб‑страничка? Это прежде всего данные. HTML — это язык разметки данных. А что такое веб‑приложение? Это код, который обрабатывает данные и формирует веб‑странички уже в самом браузере. В веб‑приложении кэширование данных подчиняется тем же правилам, что и кэширование веб‑страниц — зависит от бизнес‑логики. Что‑то мы можем кэшировать в браузере на подольше, а что‑то нужно запрашивать с сервера каждый раз. С кэшированием кода же всё просто — код веб‑приложения должен сохраняться в браузере до тех пор, пока на сервере не изменится версия этого веб‑приложения (вернее, не изменится версия фронтальной части веб‑приложения).

ETag/If-None-Match

Для отслеживания соответствия версии файла в браузере версии файла на сервере используются HTTP заголовки Etag и If-None-Match. Вместе с содержимым ресурса (страницы, изображения, видео и т.д.) сервер передаёт в браузер в заголовке ETag некую метку, соответствующую содержимому ресурса (версию или хэш, вычисленный по содержимому ресурса):

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: "33a64df5"
Cache-Control: max-age=3600

<!doctype html>
…

Эта метка передаётся браузером на сервер в запросе к ресурсу в заголовке If-None-Match:

GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-None-Match: "33a64df5"

Если ресурс на сервере не изменился, то сервер просто возвращает 304 Not Modified и браузер использует имеющуюся у него копию ресурса из кэша. Если у нас весь код приложения находится в одном или нескольких бандлах, то это вполне себе рабочий способ. Если, конечно, смириться с тем, что на время max-age=3600 браузер не станет запрашивать изменения с сервера, даже если они там есть. Для обычных пользователей веб-приложения такая схема может считаться рабочей, но для разработчика это явно не подходит. Для разработчика Cache-Control должен стоять max-age=0 (или no-cache).

Service Worker и Cache Storage

Что делать, если в веб‑приложении множество файлов? Не один‑два больших бандла и десяток картинок, а несколько сотен файлов с кодом приложения и сопутствующим медиа? В этом случае на помощь приходят Service Worker и Cache Storage — первый позволяет перехватывать обращения браузера за внешним ресурсом, а второй позволяет сохранять ресурс в браузере.

Если загрузить весь исходный код в Cache Storage, то вся логика за обработку обращений к ресурсам ляжет на плечи Service Worker«а. И там уже не важно, что сервер запрещает кэширование ресурсов через заголовки ответа — Service Worker всё равно может брать ответы из Cache Storage и возвращать обратно без выхода в Сеть. Именно так и организована поддержка работы веб‑приложений в offline‑режиме.

Демо

Для своих экспериментов с кэшем я сделал демо-приложение (вот исходный код):

Вид запросов во вкладке "Network"
Вид запросов во вкладке "Network"
  • no_cache/: сервер возвращает текущее время и HTTP-заголовок Cache-Control: no-store, no-cache, must-revalidate, private.

  • cached/: сервер возвращает текущее время и HTTP-заголовок Cache-Control: public, max-age=60.

  • sw/: аналогично режиму Cached, но при этом данные сохраняются Service Worker’ом в Cache Storage, который очищается по кнопке “Clear Cache” на результирующей странице.

Можно использовать эту демку для того, чтобы посмотреть, как различные браузеры на различных устройствах работают с кэшем - видно по приходящему с сервера timestamp’у.

Стратегия кэширования кода

Таким образом на данный момент я пришёл к следующей схеме работы с кодом в веб‑приложении:

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

  • Service Worker самостоятельно кэширует все ответы в Cache Storage.

  • При разработке используется десктопная версия браузера, в которой есть возможность затребовать от Service Worker«а обращаться в Сеть каждый раз за новой версией ресурса (DevTools / Application / Service workers / Bypass for networks).

  • Для проверки работы приложения на смартфонах в приложении самостоятельно реализуется опция принудительной очистки разработчиком Cache Storage через UI.

  • Для обычных пользователей реализуется «инсталляция приложения» — при запуске приложения на фронте проверяется текущая версия установленного приложения и текущая версия на сервере. Если версии не совпадают, то с сервера в виде единого файла закачиваются все исходные файлы новой версии, распаковываются и сохраняются в Cache Storage.

Спасибо за прочтение и хорошего дня!

P.S. Телеграм-канала у меня нет, а то бы я по свежескладывающейся традиции обязательно добавил бы ссылку на него :)

Tags:
Hubs:
Total votes 5: ↑4 and ↓1+3
Comments11

Articles