Как мы улучшили время загрузки нашего приложения VueJS с 15 до 1 с

📝 Контекст

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

Livspace Hub - это веб-приложение, которое мы разработали для домовладельцев, чтобы отслеживать все обновления и документы, связанные с их проектами, в одном месте. Это универсальный магазин для отслеживания прогресса своего проекта. Домовладельцы, которые проектируют свои дома с помощью Livspace, внутри называются «клиентами», а их проекты внутри называются «проектами» (кажется очевидным, но терминология имеет значение, и мы предпочитаем, чтобы номенклатура была простой, но ясной). В остальной части статьи я буду называть Livspace Hub «Hub».

🗓 Немного истории

Изначально Hub был спроектирован как приложение Laravel, обслуживающее пользовательский интерфейс и внутренний сервер. Позже пользовательский интерфейс был разделен на Vue SPA, а сервер Laravel остался и стал нашим прокси-уровнем.

Нашей основной целью при первоначальной перестройке (разделении нашего пользовательского интерфейса на SPA) была скорость - мы хотели как можно скорее предоставить нашим клиентам SPA-версию нашего приложения. Тогда мы могли бы сосредоточиться на улучшении общей архитектуры.

Это, очевидно (и, к сожалению), привело к некоторым компромиссам в том, КАК наша реализация.

Вот как выглядела наша первоначальная высокоуровневая схема архитектуры для Hub после разделения пользовательского интерфейса на Vue SPA:

Такая скорость выхода на рынок привела к созданию SPA, который (по сути) был собран вместе. Среднее время загрузки, с которым сталкивались наши клиенты, составляло около 15 секунд (без ограничения скорости)! 🤯

Вот как выглядела наша оценка маяка при имитации троттлинга:

В этом посте мы расскажем о шагах, которые мы предприняли, чтобы улучшить это, и о том, как мы перешли от 15 секунд до менее 1 секунды.

🧱Дополнительные улучшения

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

Мы разработали план действий, чтобы улучшить опыт наших клиентов, и разделили его на 3 основные цели:

1) Удалите зависимость от Laravel.
Tl; dr
Основной причиной этого были трудности с обслуживанием - сочетание устаревшего кода и недостаток опыта в технологиях, и к нам присоединяются новые члены команды.

Мы заменили этот слой тонким сервером NodeJS express.

2) Добавьте уровень GraphQL
Tl; dr
Livspace имеет (неожиданно неожиданно) архитектуру микросервисов на бэкэнде и на стороне клиента. приложения должны вызывать API-запросы к нескольким службам, чтобы получать данные для отображения любой заданной страницы.

Имея это в виду, для нас было разумным (здравым) смыслом добавить слой GraphQL, который может агрегировать эти данные для нас (из разных сервисов), а также удаляя ненужные биты из ответа.

Это также помогло нам предоставить меньшую полезную нагрузку нашим трем приложениям - веб, Android и iOS.

Вот как теперь выглядит наша высокоуровневая архитектура для Hub после реализации пунктов 1 и 2.

Наш клиент может получить доступ к Hub через веб-приложение (написанное на VueJS) или через собственные приложения для iOS и Android (написанные на ReactNative).

В оставшейся части этой статьи мы сосредоточимся на улучшениях, внесенных в наше веб-приложение. Наше приложение VueJS создано с использованием образа докера Nginx и развернуто в кластере Kubernetes, размещенном на AWS.

Веб-приложение в основном взаимодействует со шлюзом Hub - нашим прокси-уровнем NodeJS - шлюз, в свою очередь, обращается к нескольким службам, в первую очередь Darzi - нашему уровню сшивания данных graphql, который отвечает за агрегирование данных от целого ряда микросервисов.

3) Сокращение времени загрузки внешнего интерфейса
Tl; dr
Что касается внешнего интерфейса, то SPA для Hub казался адекватным, поскольку он служил хорошо для наших пользователей. Мы сознательно решили не использовать что-то вроде Nuxt (с SSR / SSG), поскольку попытка «переписать» с помощью Nuxt на самом деле не дала бы нам значительно лучшего приложения по сравнению с хорошо оптимизированным SPA, а также поскольку SEO не является необходимостью. для Hub.

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

👀 Выявление узких мест в производительности

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

Анализ проблем
Для этого мы использовали VueCLI, Chrome Devtools и Lighthouse, что является довольно стандартным набором инструментов.

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

VueCLI3 имеет несколько удивительных функций, одной из которых является vue ui, который предоставляет разработчикам графический интерфейс для визуализации и управления конфигурациями, зависимостями и задачами проектов.

Самый простой способ проанализировать производственную сборку - это перейти к:
Задача ›сборка› Выполнить задачу | Запустить анализатор

Вот моментальный снимок того, как выглядит анализатор,

Если вы использовали Webpack Bundle Analyzer, он может показаться вам знакомым, просто у него (намного) более приятный пользовательский интерфейс.

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

Мы определили проблемные части нашего приложения:
- Bootstrap Vue
- MomentJS
- Неиспользуемые пакеты и ресурсы
- Наши файлы фрагментов сборки были огромными - в порядок МБ.

🛠 Внесение исправлений

1. Bootstrap Vue
Наша исходная кодовая база импортировала bootstrap-vue целиком,

// Don’t do this!
import { BootstrapVue, IconsPlugin } from ‘bootstrap-vue’

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

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

Затем наш импорт изменился на

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

2. MomentJS
Moment - это фантастическая библиотека, но, к сожалению, она достигла конца жизни, по крайней мере, с точки зрения активной разработки.
Она также плохо работает с встряхиванием деревьев алгоритмов, что становится проблематичным, поскольку вы получаете всю библиотеку.

В качестве варианта замены мы выбрали date-fns, который дал нам все, что мы хотели, а также имел небольшой размер.

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

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

Что касается пакетов, то тщательный обзор нашего файла `package.json` и нашей файловой структуры дал нам достаточно информации, чтобы идентифицировать пакеты и код приложения, которые не использовались, и в основном это были функции, которые в какой-то момент находились в активной разработке, но теперь продвигаются. в отставку.

4. Уменьшение размера файла пакета приложений.

4.1 Оптимизация производительности маршрутизатора Vue
Vue предоставляет несколько нестандартных способов оптимизации и отложенной загрузки маршрутов и ресурсов, связанных с маршрутами. Маршруты с отложенной загрузкой помогают оптимизировать способ, которым webpack генерирует граф зависимостей для вашего приложения, и, следовательно, уменьшить размер ваших файлов фрагментов.

В нашей исходной кодовой базе не было ленивой загрузки маршрутов, поэтому простое изменение значительно зафиксировало размер нашего основного пакета. Вот фрагмент того, как выглядит отложенная загрузка конфигурации vue-router:

4.2 Предварительное сжатие статических объектов

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

Хотя Nginx обеспечивает динамическое сжатие статических ресурсов, в ходе нашего тестирования мы обнаружили, что предварительное сжатие ресурсов во время сборки привело к улучшению коэффициентов сжатия наших файлов и помогло сэкономить еще несколько КБ!

4.3 Предварительная загрузка важных объектов

Это подсказка от Lighthouse, которую мы решили включить в наш этап сборки. Основная идея - предварительно загрузить все важные ресурсы, которые понадобятся вашей (целевой) странице.

4.4 Разделение кусков

Самый простой способ сделать splitChunks - просто добавить следующую конфигурацию,

optimization: {
 splitChunks: {
   chunks: “all”
 }
}

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

Вот как выглядят наши файлы конфигурации,

И наша конфигурация nginx требовала только следующих строк:

# Enable gzip for pre-compressed static files
gzip_static on;
gzip_vary on;

Вы можете подробно прочитать об оптимизации производительности VueJS здесь.

🎉 Конечный результат

Рабочий стол - [Нет] Очистить хранилище - [Нет] Имитация регулирования

Мобильный - [Нет] Очистить хранилище - [Нет] Имитация регулирования

Рабочий стол - [Да] Очистить хранилище - [Да] Имитация регулирования

Мобильный - [Да] Очистить хранилище - [Да] Имитация троттлинга

🔮 Планы на будущее

Мы планируем сократить время загрузки мобильных устройств при имитации троттлинга, цель - как можно меньше! Это потребует от нас пересмотреть наш шлюз и слои GraphQL, и мы обязательно поделимся блогом части 2, в котором будут обсуждаться детали наших обновлений.

Мы также изучаем сжатие Brotli, кеширование и добавление поддержки http2 / 3, поскольку это определенно поможет добавить некоторый уровень оптимизации на уровне сети. Конечно, это не только для Hub, но и для веб-приложений, ориентированных на дизайнеров и поставщиков.

🎳 Команда

Огромный привет всей команде, стоящей за Hub!

Снеха Монтейро, владелец продукта

Амит Пармар, Гита-старший, команда разработчиков веб-приложений

Випул Гарг, Джайрадж Качария - команда разработчиков ReactNative

Нилеш Гупта, инженер по тестированию

💻 Мы нанимаем!

Мы всегда в поисках удивительных талантов, ознакомьтесь с работой, которую мы делаем в Livspace Engineering, здесь. Мы набираем сотрудников на разные должности, подробности о которых вы найдете здесь.