Строим свой full-stack на JavaScript: Клиент

в 7:48, , рубрики: contoso-express, express.js, full-stack apps, javascript, node.js, postgresql, React, sequelize, TypeScript, vue.js, Проектирование и рефакторинг
Строим свой full-stack на JavaScript: Клиент - 1

JavaScript на клиенте быстро меняется: библиотеки, фреймворки, упаковщики быстро появляютcя и сменяют один другой. Но несмотря на это, многие ключевые операции одинаковы для любого клиентского приложения. А в современных фронт-энд фреймворках, при всем их разнообразии, есть много общего.

Статья базируется на коде проекта Contoso Express.

Список ресурсов на которых можно более детально ознакомиться с некоторыми темами статьи здесь.

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

Веб стандарты меняются медленно: например, последняя версия HTML 5 была утверждена 2 года назад, а предыдущая XHTML впервые появилась в 2000, т.е. промежуток между новыми версиями был 14 лет. При этом, в новой версии ничего принципиально нового не появилось. CSS тоже развивается не быстро: в первый раз я услышал про flexbox layout в 2012, прошло 4 года, и вот, вроде бы можно его использовать, если вас устроит частичная поддержка в IE11, и ее отсутствие в более ранних версиях. Даже JavaScript меняется не так быстро как может показаться: перед недавним обновлением ES6, предыдущая мажорная версия 5.0 была выпущена целых 6 лет назад. При этом, появление нового стандарта не означает что его можно начать использовать сразу. Должно пройти долгое время, пока все основные браузеры начнут его поддерживать (а для IE можно не дождаться).

Тем не менее, требования к клиентским приложениям постоянно растут. Планку поднял Google, выпустив в далеком 2005 Гугл Карты и Gmail клиент — интерактивные SPA (Single Page Application) c невиданным до этого уровнем удобства использования. Такие приложения было сложно писать, и не каждая компания могла себе это позволить. Широкое распространение они получили относительно недавно (3-4 года назад).

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

SPA

SPA становится стандартом в современных веб приложениях, предоставляя лучший уровень удобства работы (user experience). Приложение загружает основные ресурсы (стили, код, разметку) при первой загрузке страницы, после этого последующие изменения состояния происходят на клиенте. Данные с сервера грузятся через AJAX реквесты, HTML рендерится на клиенте на основе HTML шаблонов или прямо из JS (React).

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

Веб приложения можно писать как набор нескольких SPA модулей или комбинируя с частями, работающими в классическом режиме постбэков (round trips). В Contoso страницы аутентификации работают через постбэки, все остальное приложение сделано как SPA.

Препроцессоры стилей

Синтаксис CSS имеет ряд недостатков, которые устраняются препроцессорами — языками надстройками над CSS: LESS/SASS/Stylus

Главные улучшения стандартного CSS:

  • вложенные селекторы:

.my-class {
    background-color: blue;
    div {
        background-color: red; // -> .my-class div { background-color: red; }
    }
}

  • переменные, выражения и функции:

@myPadding = 10px;

.my-class {
    padding-top: @myPadding; // 10px
    padding-bottom: @myPadding + 5px; // 15px
}

  • mixings:

.error {
    color: red;
    font-weight: bold;
}

.admin-error {
    border: 1px solid black;
    .error // -> .error styles inserted here
}

Препроцессоры облегчают написание и поддержку стилей, особенно, если их много. В Contoso используется LESS, хотя сейчас SASS более популярен, разница между ними не большая.

Выбираем CSS фреймворк

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

Основные опции:

Bootstrap — это основной выбор и первый массовый css фреймворк. Изначально создан Twitter, хорошо подходит как для десктопной так и для мобильной разработки. Большой набор компонентов. Из-за большой популярности сайт на bootstrap может выглядеть шаблонно.

Foundation — популярный выбор, хороший набор компонентов и удобная система позиционирования.

Semantic UI — делает упор на хорошо продуманной семантике (именовании) классов элементов, не так хорош для мобильной разработки.

Material Design — это не фреймворк, а рекомендации по стилю от Google, которые используются в продуктах компании (Android). Есть реализации этого дизайна для React Material UI и для других JS фреймворков.

Дополнительно можно использовать библиотеку с набором иконок в виде шрифта, самая популярная из подобных Font Awesome.

В Contoso используются Font Awesome и Bootstrap.

Выбираем клиентский JS фреймворк

Давайте рассмотрим основные опции:

JQuery — библиотека давшая возможность удобной прямой манипуляции DOM элементами c единым интерфейсом для разных браузеров, хорошо документирована, поддерживает AJAX запросы и написание плагинов. Плохо подходит для написания сложных клиентских приложений, не предоставляет возможностей реактивной связи данных (reactive data binding).

Реактивность в клиентских JS библиотеках — это способность динамической связи данных (модели) и отображения (HTML view). Когда меняется модель, автоматически (реактивно) меняется отображение, и наоборот, изменение состояния отображения (поменялось значение input) меняет модель. Вот ссылка jsfiddle на простой пример реактивности с использованием Vue.js.Оцените насколько это проще, чем выполнять все DOM манипуляции вручную.

Angular 1.X — первый массово-популярный реактивный фреймворк (были и другие, например Knockout). Помимо реактивности, Angular предоставила способ структурной организации приложения, поддержки повторно используемых компонентов (директив) и многое другое. К сожалению, Angular 1.x излишне запутан, имеет много костылей, сложен для изучения и разработки, особенно это касается реактивности. Имеют место проблемы с производительностью. Изначально, Angular была независимой open-source библиотекой. Затем разрабатывается и поддерживается Google. Angular достиг пика своей популярности в 2014 в дальнейшем активно вытесняется React.

React — в отличии от Angular, это библиотека, а не фреймворк, которая предназначена для генерации клиентских отображений. Использует технику Virtual DOM для более быстрого рендеринга. Это позволяет React каждый раз полностью заново рендерить отображение при каждом изменении данных, без существенного ущерба для производительности.

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

React предполагает применение JSX, который позволяет оперировать XML (HTML) синтаксисом внутри JS кода. Это используется вместо традиционных HTML темплейтов. JSX дает возможность применять все возможности JS для генерации темплейтов, но может быть сложнее для поддержки и совместной работы с верстальщиками. React был создан facebook и используется в некоторых из их проектов.

Angular 2.x — самый свежий фреймворк (финальный релиз сентябрь 2016). Ориентирован на разработку сложных клиентских приложений, отличная производительность. Написан с чистого листа и имеет мало сходства с Angular 1.x. Рекативность в нем реализована гораздо проще: для байндинга используются нативные атрибуты HTML (например, click, а не ng-click). Применяет много продвинутых возможностей TypeScript: например, Dependency Injection реализована через конструктор классов и тип переменной (как в C#/Java). Интенсивно используются декораторы. Для работы Angular требует подключение сторонних библиотек (zone.js) использует полифилы и переопределяет многие стандартные функции браузера. На данный момент, еще не понятно что будет со стандартной практикой для организации состояния системы. Angular 2 может работать вместе с Flux архитектурой (например с использованием Redux), но Flux не рекомендуется как единственно верная архитектура.

VueJS — отличная альтернатива React/Angular. Придерживается принципа от простого к сложному (progressive). С Vue очень легко начать, получив базовые реактивные возможности, но при этом, Vue можно расширить, добавляя все необходимое для построения сложных SPA. Vue просто интегрировать, он быстрый и интуитивно понятный. Уже долгое время постоянно попадает в список JS trending repos на GitHub и для этого есть весомые причины. Синтаксис темплейтов схож с Angular2. Для хранения состояния используется Vuex с архитектурой Flux. Скоро выходит 2я версия Vue, где помимо прочего, улучшается производительность (с Virtual DOM) и опционально добавляется возможность использовать JSX.

Другие — фреймворков/библиотек очень много. Если вам интересно рассмотреть другие опции, стоит посмотреть на сайт TodoMVC где собраны реализации простого TODO лист приложения на различных MV* фреймворках.

Что выбрать

Это зависит от конкретного проекта. Если вы собираетесь строить сложное SPA приложение с самого начала, то стоит попробовать React, как самую популярную опцию на сегодняшний день. Но это потребует какое-то время на изучение новых концептов и настройку окружения.

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

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

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

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

Еще один критерий для выбора — это набор плагинов/библиотек для конкретного фреймворка. В этом плане React/Angular1 явно лидируют, хотя для VueJs уже есть много вспомогательных библиотек компонентов, а для Angular2 их количество должно стремительно вырасти после недавнего релиза. В репозитории Awesome Lists вы можете найти awesome (восхитительный) список ресурсов по интересующему вас фреймворку.

В дальнейшем, я буду описывать различные аспекты написания клиентских приложений с особенностями реализации на React/Vue. В Contoso основная версия сделана с использованием React, есть альтернативная ветка на Vue, я хотел также добавить ветку с реализацией на Angular2, но, к сожалению, экосистема фреймворка еще не стабильна (библиотеки и инструменты используюищие Angular2 еще не успели полностью адаптироваться к новому релизу).

Стуктура

Структура клиентской части зависит от выбранного фреймворка. В Contoso общей частью для клиентских реализаций (React/Vue) являются:

Сервисы (services) — модули доступа к данным через API. Если необходимо, тут происходят дополнительные преобразования данных.

Форматеры (formatters) — предоставляют форматирование данных на клиенте (дата, валюта, отображение имени, т.д.).

Помощники (helpers) — выполняют такие общие для клиента операции, как AJAX запросы, доступ к конфигурации, отображение всплывающих сообщение и подобное.

Помимо этого есть части, которые реализуются в зависимости от выбранного фреймворка:

Компоненты (components) — строительные блоки для клиентского интерфейса. Все современные фреймворки поддерживают создание UI как дерева компонентов, но конкретная реализация различается. Обычно компоненты группируются по папкам в зависимости от функционала (Project, UserAccount, Orders, e.g.).

Отдельно хранится состояние (данные) и события системы: для React — это папки 'actions', 'store', 'reducers', для Vue — папка 'vuex'.

Маршрутизация

При классической организации веб приложения, различные маршруты (URLs) загружают различные страницы с сервера. В SPA приложении все данные грузятся сразу. При переходе на другую страницу, загрузки новой страницы с сервера не происходит и URL остается таким же.

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

Есть несколько способов добавления маршрутизации в SPA:

  • С хэштегом — используется для поддержки старых браузеров (IE9), адрес выглядит как www.mysite.com/#/product/list
  • С HTML5 History API — адрес выглядит так же, как и в обычном веб приложении www.mysite.com/product/list

Для каждого клиентского фреймворка есть отдельная библиотека для маршрутизации.

В Contoso используется режим History API (pushState).

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

AJAX запросы

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

Если в проекте все равно для чего-то используется JQuery, то можно его использовать и для AJAX запросов. В Contoso JQuery обернут промисами и используется через модуль httpHelper.

Еще хорошая опция — axios. Эта библиотека имеет удобный API, поддерживает промисы, и что очень важно для full-stack разработки, работает одинаково как на сервере так и на клиенте.

Использовать XMLHttpRequest напрямую из JS сложно, поэтому ему на замену предлагается нативный Fetch метод, он еще не поддерживается во всех основных браузерах, но как всегда в подобных случаях, есть библиотека с полифилом fetch polyfill, который работает с тем же API.

Существует несколько действий, которые можно выполнять при каждом AJAX запросе.

Можно блокировать UI, чтобы пользователь не мог совершить еще какие-то действия, пока AJAX запрос не отработает и состояние системы не обновится.

Если во время запроса возникла ошибка, по-умолчанию можно показывать общее сообщение (например "Server Error") для любого запроса.

Опционально можно добавить локальное логирование всех AJAX запросов в консоль браузера.

Валидация

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

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

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

В Contoso, в React, для валидации не используется вспомогательных библиотек, для Vue используется пакет "vue-validator".

Конфигурация

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

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

В Contoso это реализовано именно так, смотри файл "helpers/clientConfig".

Обработка ошибок

Основной источник ошибок на клиенте — это AJAX запросы. Если вы разрабатываете и клиентскую и серверную части, бывает проще сразу пересылать текст ошибки который можно показать на клиенте. Если API разрабатываются отдельно, то ошибка обрабатывается по HTTP коду или кастомному строчному коду, это лучше делать сразу в методе сервиса который делает AJAX запрос.

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

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

Упаковка

При написании большого клиентского приложения приходится разбивать его на много мелких модулей, при этом браузер плохо работает с большим количеством файлов (HTTP2 это исправит). Поэтому, есть практика клиентских билдов, когда несколько файлов, с кодом или стилями, объединяются в одну или несколько сборок (bundles). Для этого используются разные инструменты.

Менеджеры задач (task runners) предназначены для широкого спектра задач, таких как объединение и сжатие JS файлов, компиляция SASS/LESS, копирование, переименование файлов и многое другое. Необходимая работа разбивается на задачи, которые могут выполняться отдельно через CLI (консоль). Для выполнения разнообразных задач, используются плагины для конкретного менеджера. Самые популярные менеджеры задач Gulp и Grunt. Последнее время есть тенденция использовать инструменты npm для запуска скриптов и писать сами скрипты на Node без использования менеджера задач и его плагинов.

Сборщики модулей (module bundlers) помимо общих задач клиентской сборки, анализируют зависимости в JavaSrcript файлах и объединяют исходную сборку соответственно. Например, если у вас есть module1.js который импортирует module2.js, которые в свою очередь импортирует module3.js и lodash. То в исходной сборке модули будут в нужной последовательности (lodash, 3, 2, 1). Сборщики модулей по умолчанию ожидают увидеть внешние модули как npm пакеты в папке 'node_modules'.

Самым популярным на сегодняшний момент является WebPack. Первым инструментом подобного типа был Browserify, он проще чем WebPack, но требует больше настройки. Набирает популярность Rollup — он отличается тем, что при использовании ES6 синтаксиса для импорта можно определить, какие части внешних библиотек используются и включать только их, что позволяет уменьшить размеры клиентской сборки.

В Contoso для клиентских сборок используется WebPack, для запуска задач — npm или непосредственно консоль. WebPack делает следующее: объединение модулей, транспиляцию TypeScript в ES5, преобразование стилей из LESS в CSS и объединение их в один файл, минификацию JS и CSS для продакшин сборки.

Для разработки нужно запускать WebPack в режиме наблюдения, при этом при любом изменении клиентских файлов, сборка будет собрана заново и нужно перегрузить страницу в браузере.

webpack --watch //-w shorter

WebPack позволяет для некоторых JS фреймворков поддерживать режим HotReload — это когда происходит обновление JS и CSS без полной перегрузки страницы. Поддерживается в React и Vue, но сложно в настройке и иногда не отрабатывает корректно. В Contoso не используется.

Что дальше?

В следующей статье я расскажу про деплоймент приложения, опишу опции для облачного хостинга, особенности клиентских сборок, управление Node процессами (pm2/forever) мониторинг приложения и другое.

Буду рад замечаниям комментариям.

Stay tuned!

Автор: Yeggor

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js