Вы также можете прочитать эту историю на dev.to!

TL;DR

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

Ошибки случаются.

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

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

Ошибка, которую я видел, выглядела так:

Uncaught SyntaxError: Unexpected token '<'

После некоторых исследований выяснилось, что сценарий возникновения ошибки был примерно таким:

  • Браузер пользователя кэширует страницу при первом посещении веб-сайта. Пользователь не посещает сайт снова до дня X
  • Мы активно развиваем сайт и делаем релизы
  • Каждый новый релиз добавляет на сервер бандл с уникальным хешем
  • Мы храним на сервере несколько последних релизов, однако, так как ресурсы сервера ограничены, с каждым приходом нового релиза мы стираем самый старый релиз
  • Сегодня день X, и пользователь с кешированной версией страницы с радостью входит
  • Браузер пользователя пытается получить bundle.[too-old-hash].js, но его нет на сервере, так как мы уже сделали несколько развертываний, и этот старый выпуск был стерт.
  • Впоследствии сервер отвечает кодом 404, который представляет собой HTML-страницу.
  • Компилятор JS не может анализировать HTML и выдает SyntaxError
  • Наше приложение обычно отображается с помощью React на стороне клиента, но, поскольку пакета нет, пользователь видит пустую страницу.

Итак, как вы обрабатываете ошибку, которая возникает из-за того, что все ваше приложение недоступно? Ниже я покажу возможное решение только для внешнего интерфейса.

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

Настраивать

Мы используем webpack для компиляции и связывания нашего кода и HtmlWebpackPlugin для создания HTML-страницы, на которой в конечном итоге будет жить наше приложение.

Наше приложение может быть любым. Эта статья не зависит от фреймворка.

Возможные решения и предостережения

Во-первых, логически мы ничего не сможем сделать в bundle.[hash].js, потому что этот файл не загрузится и будет недоступен во время выполнения.

Так что же нам делать? Ну, мы могли бы добавить немного встроенного JS на нашу страницу. Он всегда будет присутствовать и, следовательно, сможет выполнять некоторую обработку.

Давайте создадим src/index.ejs, который является местом по умолчанию для шаблона, используемого HtmlWebpackPlugin для создания HTML-страницы. Создав этот файл, мы сможем настроить HTML-скелет сгенерированной страницы.

Моей первой наивной попыткой было добавить встроенный JS в шаблон HtmlWebpackPlugin для прослушивания события ошибки в теге скрипта приложения, например:

Однако это не сработает, поскольку при выполнении встроенного кода <script> <script src="bundle.foo12.js"></script> еще даже не существует в DOM, поскольку он расположен под тегом встроенного скрипта и еще не проанализирован браузером. . Хорошо, давайте подождем, пока DOM будет готов, и как только он будет готов, давайте сделаем то же самое и прикрепим прослушиватель событий (ниже я буду опускать неизмененные части документа для краткости):

К сожалению, это тоже не сработает, потому что, когда браузер видит тег простого скрипта, который пытается загрузить наш бандл, он сразу же извлекает и выполняет бандл, затем возобновляет синтаксический анализ HTML и, достигнув </html>, запускает событие DOMContentLoaded.

Вот как это выглядит графически:

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

На этом этапе мы могли бы попробовать проверить наличие скрипта бандла в DOM с очень коротким интервалом или каким-то другим методом перебора такого типа.

К счастью, в этом нет необходимости, поскольку HtmlWebpackPlugin предоставляет нам все необходимое для реализации элегантного и эффективного решения.

Решение

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

Хорошо, давайте перехватим управление.

Прежде всего, давайте скажем HtmlWebpackPlugin, что мы не хотим, чтобы он внедрял на страницу <script>, которые мы не можем контролировать.

Теперь у нас вообще нет тега <script> пакета, поэтому наше приложение никогда не будет загружено. Это нехорошо, но мы можем сами создать тег <script>, используя информацию, которую нам предоставляет HtmlWebpackPlugin.

Если вы используете шаблон HtmlWebpackPlugin передаст ему переменную с именем htmlWebpackPlugin. Здесь мы получаем доступ к htmlWebpackPlugin.files.js, который представляет собой массив, содержащий пути всех пакетов javascript, созданных веб-пакетом во время этого запуска.

Эта странная конструкция <%= … %> — всего лишь синтаксис Embedded JavaScript templates для вывода информации в документ.
В этом случае при сборке она будет преобразована в что-то вроде ['bundle.foo12.js'].

Как только мы получим массив путей, мы можем пройтись по этому массиву и создать тег <script> для каждого пути.
Прежде чем вставить только что созданный <script> в документ, мы прикрепим к нему наш прослушиватель ошибок. На этот раз мы подключили прослушиватель вовремя, поэтому, если произойдет какая-либо ошибка, он будет запущен.

Обратите внимание, что здесь я пытаюсь использовать синтаксис, совместимый с ES5, потому что этот код не будет скомпилирован и будет отправлен в браузер как есть.

Бонус: вставьте изображения в сообщение об ошибке

В реальной жизни мы, вероятно, хотим показать какую-нибудь красивую «страницу с ошибкой» вместо сообщения в окне предупреждения браузера. Возможно, мы хотим показать изображение на странице ошибки.

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

Прежде всего, давайте установим файл-загрузчик, который может работать с изображениями.

npm install file-loader --save-dev

Теперь давайте скажем webpack использовать этот загрузчик.

Теперь мы можем напрямую запрашивать изображения внутри нашего шаблона index.ejs следующим образом:

<%= require('./path_to_image').default %>

Вот полный файл index.ejs.

Вывод

Надеюсь, это было полезно! Вы можете найти весь код финальной версии в этом репо.

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