Сегодня я хочу показать вам, как использовать сервисный воркер Angular 6+ по умолчанию для кэширования динамически загружаемых ресурсов. Это особенно полезно, если вы используете стек JAM и не знаете, какие ресурсы редактор может использовать для контента.

А поскольку мы не можем установить более одного сервис-воркера на страницу, было бы здорово, если бы мы могли использовать Angular Service Worker по умолчанию вместо того, чтобы переписывать весь сервис-воркер самостоятельно.

Какую проблему мы хотим решить?

По умолчанию встроенный Angular Service Worker (с этого момента NGSW) просто кэширует все ресурсы в папке с ресурсами. Фактически, NGSW можно настроить для кэширования таких групп данных (дополнительную информацию см. В https://angular.io/guide/service-worker-config#datagroups):

export interface DataGroup {
    name: string;
    urls: string[];
    version?: number;
    cacheConfig: {
        maxSize: number;
        maxAge: string;
        timeout?: string;
        strategy?: 'freshness' | 'performance';
    }; 
}

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

Так как же решить эту проблему?

Недавно я создал прогрессивное веб-приложение для клиента, контент которого поступает из серверной части Typo3 через GraphQL API. Таким образом, весь контент находится в формате JSON, и в любой позиции в JSON может быть скрыт URL-адрес изображения (или других ресурсов).

Теперь мы могли использовать две разные стратегии:

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

Настроить DataGroup

Для обеих стратегий мы должны настроить DataGroup одинаково. Предположим, ваши ресурсы хранятся в https://api.example.org/uploads, тогда мы должны добавить следующие строки в наш ngsw-config.json:

"dataGroups": [
    {
        "name": "dynamicResources",
        "urls": [
            "**/uploads/**/*.jpg",
        ],
        "cacheConfig": {
            "maxSize": 250,
            "maxAge": "7d"
        }
    }
]

Атрибут name - это просто ключ к кеш-хранилищу, в котором будут храниться ресурсы. Под urls вы найдете массив со всеми URL-адресами, для которых эта группа данных должна быть активна. Вам не нужно писать здесь полный URL. Обратите внимание, что это не синтаксис регулярного выражения, а специальный шаблон глобуса (см. Https://angular.io/guide/service-worker-config#urls). Вы можете добавить больше шаблонов URL для дополнительных типов файлов.

Раздел cacheConfig определяет политику кэширования. Настоятельно рекомендуется указать хотя бы атрибут maxSize. Он определяет количество запросов, которые следует кэшировать. В целях экономии дискового пространства пользователей вы должны установить максимальное количество кэшируемых файлов. В противном случае ваш кеш может бесконтрольно расти.

CORS просто необходим

Теперь вы добавили свою группу данных в ngsw-config.json и хотите протестировать новую настройку со стандартным тегом изображения.

 <img src="https://api.example.org/uploads/isthiscached.jpg" />

Вы с восторгом смотрите на инструменты разработчика в разделе Application и не видите… ничего. Что случилось?

Вы сделали две ошибки:

  1. Веб-сервер должен поддерживать CORS для каталога статических файлов.
  2. Вы должны указать своему браузеру использовать CORS для этого тега изображения. Для этого вам нужно добавить crossorigin="anonymous" в свой тег изображения.
<img src="https://api.example.org/uploads/isthiscached.jpg" crossorigin="anonymous" />

Et voilà… Вы успешно реализовали стратегию №1. Теперь ваши изображения автоматически кэшируются NGSW.

Но зачем нужен CORS?

Когда вы отправляете запрос, отличный от CORS, ответ становится непрозрачным. Сервисный работник не знает, был ли запрос успешным. Если вы напишете собственного сервис-воркера, можно будет вручную поместить непрозрачный ответ в кеш. Но по соображениям безопасности браузер добавит заполнение к телу ответа, и ваш ответ будет иметь размер не менее ~ 7 МБ. Размер кеша резко увеличится.

Таким образом, NGSW даже не заботится о кэшировании непрозрачных ответов.

Кэшировать все изображения заранее

Но этого недостаточно. Ты хочешь больше! И я с тобой ;-)

Чтобы заранее кэшировать все изображения в тот момент, когда вы получите JSON с контентом, вам просто нужно сделать немного больше:

function onContentLoaded(contents: any) {
    if ('serviceWorker' in navigator) {    
        const urls = new Set(...this.extractAllUrls(contents));
        Array.from(urls).forEach(url => fetch(url));
    }
}

Вам просто нужно извлечь все URL-адреса из ответа вашего сервера. Почему я использую new Set()? Потому что это простой способ отфильтровать дубликаты.

Затем мы перебираем URL-адреса и используем fetch(). Это инициирует запрос, и NGSW автоматически перехватит этот запрос и кэширует ответ. И вам не нужно беспокоиться о перезагрузке! Во второй раз, когда вы извлечете ресурсы, они будут обслуживаться из кеша. Так что вам не нужно бояться, что пользователю придется загружать все изображения при каждой перезагрузке страницы.

Заключение

Не так сложно динамически кэшировать изображения с помощью Angular Service Worker. Но в документации по Angular упущены некоторые важные детали о том, когда запросы кэшируются. Особенно вы должны помнить, что вам нужно, чтобы на вашем веб-сервере был включен CORS.