Оптимизация производительности приложения Vue.js: часть 1 — Введение в оптимизацию производительности и отложенную загрузку.
В этой серии статей я подробно расскажу о методах оптимизации производительности Vue, которые мы используем в Vue Storefront и которые вы можете использовать в своих приложениях Vue.js, чтобы мгновенно загружать и выполнять их. Моя цель — сделать эту серию полным руководством по настройки производительности в приложениях Vue.
Часть 1 — Введение в оптимизацию производительности и отложенную загрузку.
Часть 2 — Отложенная загрузка маршрутов и анти-паттерн использования vendors.js.
Часть 3 — Отложенная загрузка Vuex и отдельных компонент
Часть 4 — Отложенная загрузка библиотек
Часть 5 — Использования кеша Service Worker
Часть 6 — Предзагрузка
Как работает сборка проекта через Webpack?
Большинство советов в этой серии будут посвящены уменьшению размера окончательной сборки (бандла) проекта JS. Чтобы понять, насколько это важно, сначала нам нужно понять, как Webpack объединяет все файлы проекта.
В процессе сборки ресурсов Webpack создает нечто, называемое графом зависимостей (dependency graph). Это граф, связывает все файлы с помощью импорта. Предполагая, что у нас есть файл с именем main.js, указанный как точка входа в конфигурации проекта webpack, он будет корнем нашего графа зависимостей. Теперь каждый js-модуль, который мы импортируем в этот файл, станет его листом на графике, а каждый модуль, импортированный в эти листья, станет их листом.
Webpack использует этот граф зависимостей, чтобы определить, какие файлы нужно включить в окончательную сборку. Сборка в данном случае представляет из себя один (или несколько, как мы увидим в последующих частях) файл javascript, содержащий все модули из графа зависимостей.
Этот процесс можно проиллюстрировать следующим образом:
Теперь, когда мы знаем, как работает сборка проекта, становится очевидным, что чем больше будет расти наш проект, тем больше будет окончательный пакет сборки JavaScript. Чем больше будет пакет сборки, тем дольше будет загружаться файл, а значит тем больше пройдет времени когда пользователь сможет увидит что-то значимое на странице. А чем дольше пользователь будет ждать, тем более вероятно, что он покинет наш сайт.
Короче говоря, больше зависимостей = меньше пользователей. По крайней мере, в большинстве случаев.
Отложенная загрузка
Итак, как мы можем одновременно сократить размер пакета окончательной сборки, и имеет возможность добавлять новые функции и улучшать наше приложение? Ответ прост — отложенная загрузка и разбиение кода.
Как следует из названия, отложенная загрузка — загрузка нашего приложения не сразу а по частям. Другими словами — загружать части приложения только тогда, когда они нам действительно нужны. Разделение кода — это просто разделение приложения на отдельные независимые фрагменты кода.
В большинстве случаев нам не нужен весь код приложения сразу после того, как пользователь заходит на наш сайт. Неправильно когда в нашем приложении существует несколько разных маршрута, но независимо от того, где находиться пользователь, ему всегда нужно загрузить и выполнять весь код приложения, даже если на самом деле ему нужен только часть приложения. Все это влечет за собой большую трату времени!
Отложенная загрузка позволяет разбить окончательную сборку на части и загружать только те части которые необходимы в данный момент.
Чтобы увидеть, какая часть кода JavaScript фактически используется на любом веб-сайте, можно перейти к devtools (в Хроме) -> cmd + shift + p -> тип покрытия (type coverage) -> нажать «запись» («record»). Теперь мы можем увидеть, сколько загруженного кода действительно использовалось.
Все, что помечено красным — это то, что не нужно на текущем маршруте и может быть загружено позже. Если вы используете исходные карты (maps), вы можете нажать на любой файл в этом списке и посмотреть, какие его части не были вызваны. Как мы видим, даже у vuejs.org есть огромные возможности для улучшения.
Пришло время посмотреть, как мы можем использовать это в нашем приложении Vue.js.
Динамический импорт
Мы можем легко загружать отдельные части нашего приложения с помощью динамического импорта веб-пакетов. Давайте посмотрим, как они работают и чем они отличаются от обычного импорта.
Если мы используем обычный импорт модулей JS то код будет выглядеть как то так:
// main.js import ModuleA from './module_a.js' ModuleA.doStuff()
Загружаемый модуль будет добавлен в виде листа main.js в граф зависимостей.
Но что если нам понадобится ModuleA только при определенных обстоятельствах, таких как реакция на взаимодействие с пользователем в каком то редком случае? Объединение этого модуля с нашим основным пакетом сборки — плохая идея, так как он может вообще не понадобиться. Нам нужен способ сообщить нашему приложению, когда оно должно загрузить этот кусок кода.
Вот где нам может помочь динамический импорт! Теперь взглянем на этот пример:
//main.js const getModuleA = () => import('./module_a.js') // invoked as a response to some user interaction getModuleA() .then({ doStuff } => doStuff())
Кратко рассмотрим, что здесь произошло:
Вместо прямого импорта module_a.js мы создали функцию, которая возвращает функцию import (). Теперь в пакет сборки будет входит функция динамического импорта модуля из отдельного файла, и если функция не будет вызвана, импорта не будет, и файл не будет загружен.
Делая динамический импорт, мы в файле main.js обрезаем лист, который будет добавлен к графу зависимостей, и делаем отдельный лист, который будет загружен, только тогда когда мы решим, что это необходимо.
Давайте посмотрим на другой пример, который лучше проиллюстрирует этот механизм.
Предположим, у нас есть 4 файла: main.js, module_a.js, module_b.js и module_c.js. Чтобы понять, как работает динамический импорт, нам нужен только исходный код двух файлов: main.js и module_a.js:
//main.js import ModuleB from './mobile_b.js' const getModuleA = () => import('./module_a.js') getModuleA() .then({ doStuff } => doStuff())
//module_a.js import ModuleC from './module_c.js'
Делая module_a.js динамически импортируемым модулем, мы разрезаем часть графа зависимостей. Когда module_a.js динамически импортируется, он загружается вместе с модулями, импортированными внутри него.
Другими словами, мы просто создаем новую точку входа в граф зависимостей.
Отложенная загрузка компонентов Vue
Мы теперь знаем, что такое отложенная загрузка и зачем она нужна. Пришло время посмотреть, как мы можем использовать его в нашем приложении Vue.
Хорошей новостью является то, что это чрезвычайно просто, и мы можем отложено загружать весь SFC вместе с его CSS и HTML с тем же синтаксисом, что и раньше!
const lazyComponent = () => import('Component.vue')
… это все, что нам нужно! Теперь компонент будет загружен только по запросу. Вот наиболее распространенные способы вызова динамической загрузки компонента Vue:
- вызывается функция с импортом
const lazyComponent = () => import('Component.vue') lazyComponent()
- по запросу компонент только в этот момент рендерит шаблон
<template> <div> <lazy-component /> </div> </template> <script> const lazyComponent = () => import('Component.vue') export default { components: { lazyComponent } } </script>
Обратите внимание, что вызов функции lazyComponent произойдет только тогда, когда компонент будет запрошен для рендеринга в шаблоне.
Еще небольшое дополнение. Если мы добавим в компонент условие v-if:
<lazy-component v-if="false" />
то в этом случае сразу при загрузке компонент не будет динамически импортироваться, поскольку он не добавлен в DOM. Он будет добавлен в DOM, как только значение изменится на true, что является хорошим способом условно отложенной загрузки компонентов во Vue.
Заключение
Отложенная загрузка — это один из лучших способов повысить производительность веб-приложения и сократить его размеры. Мы узнали, как использовать отложенную загрузку с компонентами Vue. В следующей части этой серии я покажу вам, как разделить код приложения Vue с помощью маршрутизации vue-router и асинхронных маршрутов. Спасибо Divante LTD.