Опыт разработки SPA на VueJS + Nuxt
Наша компания занимается преимущественно разработкой интернет-магазинов и мы хотим поделиться своим опытом разработки проекта на связке VueJS + Nuxt + Laravel.
В статье пойдет речь про то, как мы решили реализовать интернет-магазин как SPA: как мы к этому пришли, трудности, легкости.
Сайт, про который пойдет речь, представляет из себя почти классический интернет-магазин с каталогом, фильтрами, поиском, корзиной, личным кабинетом т.е. почти все, что может быть в магазине. Проект имеет разную логику, ценообразование и отображение для юридических и физических лиц.
Почему SPA
До текущего проекта наш опыт разработки одностраничных приложений заключался только в нескольких внутренних проектах. И во многом SPA для нас оставалось в каком-то смысле темной лошадкой.
Не было четкого понимания проблем, связанных с ростом проекта, его seo-продвижением и стабильностью, когда как опыт и уже давно отлаженные процессы в создании обычных сайтах не вызывали никаких вопросов в решении этих задач.
Выбор подхода вызвал в нашей компании достаточно жаркие споры, обе чаши весов с аргументами были наполнены и решение давалось очень сложно. Нашими разработчиками было принято решение собрать прототип нескольких страниц проекта и посмотреть, какие возникнут трудности при каждом из подходов. Этот подход помог нам с итоговым решением. Прототипы помогли показать, что управление состоянием сайта (каталог, корзина, оформление заказа и т.д.) намного более комфортно и вызывает меньше проблем именно в SPA версии. Скорость разработки и взаимодействия между верстальщиками и программистами значительно увеличилась благодаря тому, что не нужно переносить верстку, достаточно просто добавлять логику в уже готовые компоненты. Также стали более понятны проблемы с которыми мы можем столкнуться и это сподвигло к дальнейшим действиям. Перед нами стал выбор технологий.
За окном лето 2017. В twitter и на medium ни утихают споры, что все-таки лучше, vue или react. Наш офис этот тренд не обошел стороной. Разработчики так же разделились на два лагеря, каждый со своими аргументами. До этого каждый из нас уже работал с обеими технологиями.
Кому-то стал ближе jsx, кто-то предпочитает более привычный html или pug, кто-то считает что иммутабельность помогает лучше следить и управлять состоянием приложения, кому-то это кажется избыточным усложнением. С другой стороны каждый фреймворк предоставляет нам возможность создавать однофайловые компоненты и для обоих есть уже достаточно стабильные библиотеки с набором всех нам нужных функций (ssr, управление глобальным состоянием, роутинг, управление meta-данными). Для react это nextjs, а для vue — nuxtjs. Nuxt на момент выбора был еще в beta-версии, но достаточно стабилен. Т.к. процесс разработки у нас был построен таким образом, что изначально у нас идет верстка, а затем уже построение backend части и перенос сверстанных страниц во frontend, выбор фреймворка был достаточно прост. Нами был выбран vue и nuxtjs, т.к. решено было параллельно верстать сайт и запускать api. При таком подходе удобно верстать сразу компоненты и в них уже добавлять логику. Нашим верстальщикам был ближе подход создания привычного им html.
Немножко о backend
В плане серверных решений и в целом выбора технологий для построения backend мы пошли более привычным путем. Языком был выбран php, для которого мы используем фреймворк laravel. Это все крутится на nginx. В качестве решения для базы данных у нас mysql.
Начало разработки, используемые пакеты и проблемы
Nuxt предоставляет полностью удовлетворяющие нас пакеты для управления состоянием приложения (vuex) и роутинга (vue-router). Поэтому начинать собирать проект и прикручивать к компонентам логику можно было начинать сразу, а далее по мере надобности искать нужные нам пакеты. В первую очередь, конечно же, понадобилось решение для общения с backend частью. Для этого был выбран, стандартный уже для всех, axios, и обертка над ним nuxt-axios-module. Так же сразу помогаем проекту не потеряться в окружениях и запускать в каждом окружении с нужной конфигурацией — выбираем dotenv и обертку nuxt-dotenv-module. Для начала разработки этого достаточно и процесс верстки начался.
Первая пауза случилась, когда нужно было добавлять в верстку слайдер изображений. “Где мой slick-slider, я хочу jquery” было слышно из верстальщицкого конца комнаты. Быстрый обзор готовых решений выявил несколько подходящих нам слайдеров. Но практически все тянули за собой зависимость в виде jquery, которую не хотелось добавлять в готовый бандл, тем самым увеличивая его размер. Какие-то пакеты не поддерживали серверный рендеринг, что тоже было важно для нас. В итоге выбор пал на awesome-swiper, который полностью соответствовал нашим требованиям и даже чуть больше. После того как слайдер был прикручен, наши верстальщики еще долгое время оставались в недоумении. “Это и все, мне больше ничего не нужно делать? Просто указать список изображений и это работает?”
Далее встал выбор компонента для выбора дат. Тут повезло больше, т.к. обертка для любимого нашими верстальщиками flatpickr нашлась быстро. Оставалось только немного его стилизовать.
В нескольких местах на сайте присутствует карта. Но, т.к. нам не нужно была идеальная детализация и проработанность карты, выбор между сервисами не стоял. Тем не менее на момент разработки, да и сейчас, решений которые идеально покрывают все наши потребности нет. Исходя из всех плюсов и минусов был выбран google maps и обертка vue2-google-maps. Пакет имеет достаточно большой размер и тянет за собой много ненужного нам, но свои задачи решает хорошо.
В некоторых формах у нас присутствуют поля для ввода телефона. Пользователю нужно помогать вводить телефон, так как вариантов формата слишком много, да и работать потом в будущем с данными введенными в едином формате проще. Поэтому нужна маска. Хотелось использовать уже привычную text-mask, и тут нам повезло, у них было уже решение для vue — vue-text-mask.
Эти пакеты покрыли практически все наши требования. Оставалось только отслеживать клики вне компонента, в чем на помог vue-click-outside. Быструю прокрутку вверх страницы мы реализовали с помощью vue-backtotop. Для работы с датами используем moment.
Итоговый размер bundle и откуда взялся 1 мегабайт
Стоит учитывать, что важным критерием при выборе пакетов являлся их вес.
В середине проекта мы решили провести анализ итогового проекта и посмотреть размеры сборки. Результаты наc мягко говоря удивили. Размер банда app.js составлял чуть большее 950kb gzip. Команда npm run analyze вывела нам красивый график с размером всех модулей, из которого мы поняли, что некоторые модули тянут за собой ненужные нам зависимости в виде jquery, lodash и т.д. От этих пакетов пришлось отказаться и найти им альтернативу. На текущий момент размер всего бандла составляет 480kb gzip.
Следите за зависимостями и периодически проверяйте размер вашего приложения.
Первоначальная загрузка страница и данные, получаемые по api
Nuxt предоставляет удобную возможность наполнить store данными на серверной стороне до загрузки клиента. Для этого используется action nuxtServerInit. У нас это выглядит так:
Так как категории и некоторые другие сущности у нас используются сразу в нескольких компонентах, нам было удобнее получить их сразу и положить в store.
Но тут возникает проблема с размером json, который вы получаете. Так как сервер отдает все полученные данные на клиент для первоначальной отрисовки, размер html может быть слишком большим. Мы с этим столкнулись, когда в категориях начали еще передавать ненужные нам на всех страницах изображения, описание и другие поля принадлежащие каждой категории. Размер json составлял более 2mb. К счастью это легко поправить, убрав ненужные поля из данных, которые отдает сервер.
Утечки памяти
Спустя некоторое время работы приложения на нашем тестовом сервере мы начали наблюдать неестественный рост потребления памяти. pm2 занимал до 90% всей памяти сервера и приложение периодически падало. На github странице nuxt уже висело несколько issue с такой же проблемой.
Проблема возникала, когда мы в методе asyncData наших страниц делали несколько реквестов.
К счастью, эту проблему разработчики nuxt достаточно быстро решили, и на текущий момент процесс потребляет около 40mb памяти.
Интересные проблемы и их решения
Статьи с компонентами
В панели управления сайтом есть возможность добавлять статьи и вставлять в содержание статьи различные блоки, например блок с товаром.
HTML, который приходит с сервера, выглядит примерно так:
$product-4 указывает на то, что на месте этого указателя должен находится компонент Product.vue с идентификатором 4. Vue нам предоставляет широкие возможности рендеринга компонента с помощью метода render. Сначала ищем все упоминания указателей на компоненты в пришедшем html и получаем по api данные, нужные для отображения этого компонента. Далее разбиваем весь html на дерево. В этом нам помогла библиотека himalaya. И затем собираем обратно html заменяя указатели на уже готовые компоненты.
… А больше сил писать статью не хватило) Статью начинали писать летом 2017 по ходу разработки проекта, а на дворе уже лето 2018, проект запущен, а статья не выпущена.
Поэтому публикуем то, что насобирали, но у нас еще много интересных тем, наблюдений.
Если будет интересно — пишите, ставьте лайки) Ну и о чем было бы интересно еще услышать, что упустили.
Хочу сказать, что проект работает уже около 4 месяцев. Особых проблем с ним не замечено, а скорость работы сайта впечатляет и выгодно выделяет его на фоне других магазинов.