Как вы, наверное, знаете, я создаю инструмент под названием Malla. Это отличный способ хранить текст для вашего веб-сайта и управлять им. И я хочу заботиться о моих замечательных пользователях, чтобы они не вертели пальцами во время загрузки сайта.

Это мое вступление, давайте начнем.

Первым шагом к значительному сокращению времени загрузки является довольно плохое время загрузки для начала. Вот время загрузки Маллы до и после настройки. (Это для вернувшегося пользователя, загружающего средний объем данных.)

Что я на самом деле измеряю?

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

Затем Chrome Dev Tools добавил диафильм, и я подумал, вот где она.

Но если вы загружаете кучу данных через веб-сокет, инструменты разработчика не знают, когда ваша страница будет готова.

Ниже представлена ​​сетевая панель, показывающая загрузку старого Malla. Страница загружается за 2,8 секунды; сокет установлен, но данные все еще поступают, поэтому пользователь сидел и смотрел на пустую страницу еще секунду или около того.

Итак, как мне измерить?

Секундомер. Множественные прогоны. Медиана. **

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

Не делать невыполнимого.

Во-первых, моя архитектура:

  • Node отображает некоторую разметку (используя React) и отправляет ее в браузер.
  • В браузере я подключаюсь к своей базе данных (Я использую Firebase), которая извлекает данные пользователя и заполняет ими хранилище Redux.
  • React соответствующим образом обновляет пользовательский интерфейс.

Давайте сначала посмотрим на клиента.

На клиенте

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

Страница обрабатывается сервером, и у меня нет файла CSS, поэтому мне нужен один файл размером 3,9 КБ для визуализации (пустой) страницы. Мой JavaScript, аналитика и весь этот мусор асинхронны, поэтому проблема не в них на самом деле.

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

Окей, Google, «сокращение локального хранилища npm».

Конечно, не бывает только одного пакета. Итак, я начал смотреть на звезды GitHub, проблемы, количество загрузок, кто их написал. Тогда меня осенило: я нарушаю собственное правило: откажитесь от преждевременной упаковки *!

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

[возиться, возиться, возиться]

О, это три строчки кода.

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

Окей, Google, «убей npm лучший».

Но ждать! Насколько сложно сделать это самому?

[возиться, возиться, возиться]

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

Это сработает.

Затем мне нужно загрузить его в магазин при загрузке страницы. Я прочитаю его из локального хранилища и отправлю в качестве исходного состояния при создании магазина:

Одна строчка кода.

Итак, в 17 строках кода мы получили хранилище, которое синхронизируется с диском по мере обновления и загружается примерно за 0,0009 секунд.

После загрузки данных я подключаюсь к Firebase, и данные начинают поступать, вызывая редукторы Redux и объединяю *** в хранилище.

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

  1. Пользователь входит в систему, добавляет на страницу несколько текстовых полей и уходит домой.
  2. Дома они входят в систему на другой машине и удаляют кое-что.
  3. На следующий день на работе они загружают Маллу. В локальном хранилище все еще есть удаленные текстовые поля, поэтому они отображаются на странице. Поскольку приложение просто представляет текущее состояние данных, оно не имеет понятия «этот элемент был удален вчера». Итак, как мне избавиться от этих коробок?

Вы, наверное, уже разобрались с этим. Почему я удаляю файлы? Почему бы просто не добавить к элементу свойство удалено. Вместо того, чтобы удалять что-то из базы данных, я просто заполню удаленное поле датой. Я могу фильтровать свои представления, чтобы игнорировать элементы, помеченные как «удаленные», а когда придет время создавать функцию отмены, я буду благодарен прошлому Дэвиду за то, что, наконец, понял что-то правильно.

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

Вот и все. Время загрузки уменьшено с 3,6 секунды до 0,4 секунды.

Прежде чем я перейду к серверу, я обращусь к нескольким вещам, которые упустил:

  • Если вы выполняете рендеринг на сервере, вам нужно убедиться, что хранилище пытается читать только из локального хранилища на клиенте. Поскольку я передаю localStorage в качестве начального состояния, я получаю предупреждение консоли от React о том, что клиент не соответствует серверу. Это то, с чем мне приходится жить.
  • Версия ключа, который вы используете для хранения / извлечения из локального хранилища. Если вы меняете структуру своего магазина, поменяйте ключ; Вы же не хотите, чтобы через шесть месяцев кто-то открывал свой браузер, а сайт взбесился, потому что его локальное хранилище больше не соответствует форме вашего магазина.
  • Для меня это пока не проблема, но не забывайте, что localStorage имеет ограничение примерно в 5 МБ (в разных браузерах он разный).
  • У меня есть быстрый и грязный помощник, который обертывает localStorage, поэтому я могу просто вызывать .save () и .load ().

На сервере

Теперь давайте приступим к делу и поработаем в Node.

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

Если вы не знакомы с промежуточным программным обеспечением Express, это достаточно просто. Каждое app.use или app.get принимает функцию ('регистрацию промежуточного программного обеспечения'), которая может возвращать что-либо в браузер (в этом случае промежуточное программное обеспечение ниже по странице не будет запускаться. ) или он может вызвать next (), что означает, что будет запущено следующее промежуточное ПО, и так далее, и так далее. Таким образом, в приведенном выше примере при первом запуске cacherMiddleware обнаружит отсутствие кеша и вызовет next () изнутри, и выполнение продолжится до следующего промежуточного программного обеспечения в строке 9. Здесь мы генерируем html, сохраняем его в кеше, а затем возвращаем в браузер.

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

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

До тех пор, пока я не написал этот пост, я фактически сохранял время создания в объекте кеша, а затем проверял, не было ли оно слишком старым, прежде чем возвращать кеш. Но с помощью этого метода мне нужно получать дату / время для каждого отдельного запроса, чтобы сравнить его с кешем.

Установка таймера и удаление ключа сохраняет эти вычисления, упрощает кеш, и давайте "load" просто вернем все, что найдет, что может быть "undefined".

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

Вот диаграмма.

В первый раз должно быть примерно так же. Express неплохо справляется с кэшированием (вот почему вы должны запускать рабочий сервер со средой, установленной на «production»). Но он не знает, что каждый запрос, начинающийся с / s /, будет возвращать один и тот же HTML-код, поэтому мы пинаем его задницу в гонке за кешем.

Фактические цифры, приведенные ниже, на самом деле просто отражают, сколько времени требуется для чтения из кеша (я не записываю никаких сетевых данных), так что это в основном академическое.

(Подсказка: Node может дать вам время с высоким разрешением, если вы хотите время меньше миллисекунды).

Экономия 5 мс - не проблема для пользователя; они, вероятно, все равно потратят все это на Facebook. Но это будет иметь значение для пропускной способности. Если я использую ЦП в течение 5 мсек для каждого пользователя, я могу обслуживать только 200 пользователей в секунду.

Я оптимист; этого будет недостаточно.

Вот примерное количество запросов, которые я мог бы обработать в секунду до и после (за 10 последовательных запусков):

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

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

Хорошего дня.

* Почему я против npm-package-for-everything…

Столкнувшись с задачей, которая, вероятно, была сделана раньше, обычно сразу отправляются в npm. Кто-то наверняка уже делал дебаунды раньше, и локальное хранилище, карусель для картинок, кеширование экспресс-ответов, левый ... неважно.

Я не пытаюсь изменить чье-либо мнение (последнее, что нужно миру, - это больше таких же людей, как я), но вот почему я не перехожу прямо к npm:

  • Учиться весело. Я хочу однажды заработать свой значок JavaScript Guru Badge, я не собираюсь этого делать, если позволю кому-то другому делать все тяжелые вещи.
  • В конечном итоге это быстрее. Чем глубже я понимаю, тем быстрее я могу программировать, когда нахожусь в зоне.
  • Я иду по улице, смотрю в лица людей вокруг меня и думаю про себя: любой из этих людей мог написать мою библиотеку для устранения проблем. Мы предполагаем, что люди, пишущие эти пакеты, - гении. , но вы знаете, что говорят о предположении.

Сказав все это, я только что посчитал, и у меня на сайте 37 библиотек. Все они написаны гениями.

** Усреднение чисел почти всегда - неправильный поступок. Медианы FTW.

*** Одна из радостей отказа от использования массивов: когда вы записываете элемент в список (объект, идентификаторы которого являются ключами), это, по сути, операция «upsert». Таким образом, новые данные «сливаются», если что-то уже было там из локального хранилища.