«Неожиданный токен <» в каждой новой сборке угловой рабочей PWA до обновления сайта - PullRequest
10 голосов
/ 14 марта 2019

Я знаю, что есть некоторые похожие вопросы, например неожиданный токен после Angular 2, производственная сборка , но на самом деле он не отвечает на мой вопрос.

В основном у меня есть угловое приложение и его PWA,теперь каждый раз, когда я делаю новую производственную сборку, при первом посещении сайта я получаю сообщение об ошибке:

enter image description here

, а затем, как только я обновляюстраница, сайт работает как обычно.

Это происходит на любом браузере / устройстве

Теперь я подозреваю, что при каждом обновлении приложения основной js-файл в комплекте меняется, а PWA кэшируетстарый, и приложение все еще пытается использовать новый.

Моя структура проекта выглядит следующим образом

У меня есть корневой модуль, который лениво загружает другие модули, теперь первый модуль, который загружаетсямой модуль учетной записи, поэтому пользователь может войти в систему

это мой root-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

const routes: Routes = [
    { path: '', redirectTo: '/account/login', pathMatch: 'full' },
    {
        path: 'account',
        loadChildren: 'account/account.module#AccountModule',
        data: { preload: true }
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
    exports: [RouterModule],
    providers: []
})
export class RootRoutingModule {

}

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

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

root.component.ts

@import { SwUpdate } from '@angular/service-worker'
...

export class...

constructor(
    private _sw: SwUpdate
) {
    if (this._sw.isEnabled) {
        this._sw.available
            .subscribe(() => {
                this._sw.activateUpdate()
                    .then(() => {
                        window.location.reload(true);
                    });
            });
    }
}     

Теперь нужно проверить, есть ли обновление, а затем просто обновить страницу ..но это не работает.

это мой ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "App",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js",
          "!/main*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

Как вы можете видеть, я исключаю файл чанка main.js, который должен устранить эту проблему ... , но это не

Теперь, когда я проверяю вкладку сети после новой сборки, это то, что я получаю

enter image description here Это изображение было до того, как я исключил файл main.js

так выглядят мои main.js звонки после того, как я исключил его из ngsw.config

enter image description here

I тогдадумал просто попытаться поймать эту ошибку, используя мой обработчик ошибок sentry, вот так ..

export class RavenErrorHandler implements ErrorHandler {
  handleError(err: any): void {
    console.log('error', err);
    if (err.toLowerCase().includes('token <')) {
        window.location.reload(true);
    } else {
        Raven.captureException(err);
    }
  }
}

но это не работает, я получаю ошибку в sentry, но приложение не перезагружается ??

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

Мое приложение размещено на Azure , и мое приложение использует Angular 7.27

Буду признателен за любую помощь!

Ответы [ 6 ]

8 голосов
/ 16 апреля 2019

Проблема вызвана установленным ServiceWorker, который мешает и кэширует все ответы, включая только что обновленные.

Дополнительная информация о работниках сферы обслуживания: MDN - Использование работников сферы услуг

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

Вот почему window.location.reload() не работает, потому что он делает новый запрос , но этот запрос перехватывается установленным ServiceWorker и вместо того, чтобы позволить реальному запросу пройти к реальному серверу и получить реальный ответ (обновленный файл main.js и ресурсы), он просто возвращает обратно в ваше приложение кэшированный ответ, который является старым main.js, а не обновленным, и поэтому он терпит неудачу.

В частности, ServiceWorker кэширует main.js и другие активы, которые генерируются каждый раз, когда вы меняете код. Main.js при загрузке, в свою очередь, лениво загружает (делает http-запрос) другие блоки вашего приложения (например, 5.xxxxx.js).

Когда вы вносите изменения в свой код, и хотя все ваши чанки обновляются, включая main.js и чанк 5.xxxxxx.js, браузер все еще работает со старым main.js. Этот старый main.js имеет ссылку на старшую пару 5.xxxxxx.js, которая больше не существует. На этом этапе, если вы выполняете reload () программно, установленный ServiceWorker отвечает старым кешированным main.js, который, в свою очередь, пытается лениво загрузить более старую версию 5.xxxxx.js с сервера, который не существует и, следовательно, получает ответ об ошибке 404 HTML. Отсюда исключение токена '<' (фактически это первый символ тега страницы ошибки 404).

Это видно из скриншота сетевой панели . Если вы посмотрите на скриншот, вы увидите, что main.js и все другие ресурсы поставляются с (from service worker), что означает, что это кэшированные более старые версии и они отличаются от актуальных обновленных файлов, которые теперь существуют на вашем веб-сервере.

Чтобы решить эту проблему, ServiceWorker должен быть настроен на , а не на кэш main.js, и вместо этого всегда разрешать этому запросу проходить на реальный сервер для получения любой обновленной версии main.js, что, в свою очередь, будет лениво загружать обновленный блок 5.xxxx.js, который теперь фактически существует на веб-сервере.

Вот как вы можете настроить ngsw-config.json из ServiceWorker, чтобы конкретно не кэшировать файл main.js (при условии, что он имеет паттерн main.xxxxxxx.js) для решения этой проблемы:

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "App",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js",
          "/!main*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

запустите ng build --prod, чтобы пересобрать приложение и протестировать его.

UPDATE:

Чтобы убедиться, что это не проблема кэширования браузера, передайте true в качестве первого аргумента методу window.location.reload (). Это дает указание браузеру выполнить перезагрузку без кэшированных ресурсов:

root.component.ts

                    .then(() => {
                        window.location.reload(true);
                    });

ОБНОВЛЕНИЕ 2:

Согласно документации Angular, Service Worker, установленный уже в браузерах клиентов, кэшируется. Если вы обновите код Service Worker (как вы недавно сделали), то новый код Service Worker должен быть обновлен во всех браузерах клиентов, на которых он уже установлен. Документация Angular гласит, что существует периодическая проверка, но она не указывает ее, которая проверит, нуждается ли сам Service Worker в обновлении, и сделает это. https://angular.io/guide/service-worker-devops#service-worker-updates

Возможно, решение проблемы только в производственной, а не в разработке или в режиме Private Browser (чистый браузер без установленного ПО), кажется, что это так.

Чтобы убедиться в этом, вы должны сравнить SW существующего браузера, который работает на странице, используя инструменты разработчика Chrome, с обновленным кодом SW, который должен быть установлен. Если они отличаются, это означает, что он еще не был обновлен, и это является причиной проблемы.

Вы можете сделать это с помощью браузера Chrome, который обнаруживает эту проблему, и перейдя к Инструменты Chrome Dev -> Вкладка приложения -> Сервисные работники (в верхней части левой панели) и появится ПОкоторый зарегистрирован в браузере.Нажмите на ссылку «Источник», и она откроет фактический код ПО.Сравнение кода строка за строкой (или использование утилиты diff) с обновленным кодом SW, который должен быть запущен.

6 голосов
/ 19 марта 2019

Дважды проверьте заголовки кеша. Chrome не всегда слушает без кеша. Если вы недавно посещали сайт, Chrome загрузит index.html из кеша. Вызывает ошибку.

Мы изменили наш index.html на no-store, и это, похоже, решило проблему. https://gertjans.home.xs4all.nl/javascript/cache-control.html

1 голос
/ 15 апреля 2019

Я столкнулся с этой ошибкой, когда ленивая загрузка модулей .

Это была угловая / иконическая PWA, связанная с отсутствующим импортом:

import { SharedModule } from '@shared/shared.module';

Такжесм .:

1 голос
/ 14 апреля 2019

Я не эксперт по угловой проблеме.Лучшее, что я могу сделать, это помочь вам решить проблему, помочь вам отладить.

  1. Если вы просто хотите, чтобы ошибка исчезла (и у вас есть контроль над сервером, обслуживающим вашу index.html),Я уверен, что это можно решить, установив эти http заголовки ответа ( source ):
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

Однако такая установка более или менее превосходит точку PWA.PWA позволяет пользователям использовать ваш сайт даже в автономном режиме, они должны иметь возможность сначала получить index.html, поэтому необходимо кэшировать index.html.

Кэширование index.html является правильнымвещь которую нужно сделать.Вероятно, у вашей настройки управления кэшем для вашего старого .js и других ресурсов есть некоторые проблемы.Я не уверен, какая часть работает неправильно, но ожидаемое поведение таково: эти ресурсы должны кэшироваться так же, как index.html, 404 никогда не должно происходить в первую очередь, так как браузер должен читать из локального кэша.

Одна вещь, которую вы можете сделать, чтобы быстро «прикрыть» проблему, это просто сохранить все старые ресурсы предыдущей версии на сервере.Таким образом, кешируется index.html ссылка на старые активы, они остаются доступными, нет 404, поэтому нет «неожиданного токена».

Теперь я бы предложил сначала попробовать пункт 4, чтобы проверитьмоя теория в пункте 3 верна.Также проверьте, правильно ли работает SwUpdate.В идеале вы должны увидеть следующее:

  1. вы публикуете более новую версию кода
  2. вы посещаете сайт, должны видеть кешированную более старую версию
  3. после того, как более старая версия приложениязагруженный, SwUpdate автоматически обновляет страницу для вас и загружает более новую версию.

Подводя итог, моя теория состоит в том, что в пункте 3 дела идут плохо. Старые активы должны бытьправильно кэшировать, используя правильные заголовки http-контроля кэша (или у сервисного работника angular есть другой способ сделать то же самое? Я не знаком с этой частью).Если вы не можете понять это, просто оставьте старые ресурсы доступными на сервере.

1 голос
/ 19 марта 2019

Обычно это происходит для меня, когда запрашиваемый ресурс возвращает 404, и вместо запрошенного JS-файла вы получаете ваш index.html, в котором есть теги, следовательно, токен «<».Может ли это быть как-то так?Скажем, у вас есть версии в ваших скриптах, а перестроение делает недействительными старые URL или что-то в этом роде. </p>

0 голосов
/ 12 июля 2019

У меня есть одно решение этой проблемы. Для получения https://angular.io/cli/build

получите ваше приложение angular.json файл. И измените значение outputHashing all на media

           "architect": {
                "build": {
                    ...
                    "configurations": {
                        "production": {
                            ...
                            "outputHashing": "media", // "all"
                        }
                    }
                }
            }
...