JavaScript на клиенте быстро меняется: библиотеки, фреймворки, упаковщики быстро появляютcя и сменяют один другой. Но несмотря на это, многие ключевые операции одинаковы для любого клиентского приложения. А в современных фронт-энд фреймворках, при всем их разнообразии, есть много общего.
- Статья 1: Основы
- Статья 2: Сервер
- Статья 3: Клиент
Статья базируется на коде проекта 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!
Комментарии (8)
AlexZaharow
23.09.2016 13:22Если API разрабатываются отдельно, то ошибка обрабатывается по HTTP коду или кастомному строчному коду, это лучше делать сразу в методе сервиса который делает AJAX запрос.
Остальные ошибки чаще всего вообще никак не обрабатываются, предполагается что их быть не должно, а если все-таки случились, пользователь догадается перегрузить страницу."
Нет ли всё-таки другого варианта сообщить о «странном» ответе от сервера? Насколько я могу судить — это очень болезненная тема в разработке с ajax-запросами. Ведь ошибки бывают в бизнеслогике и её уже никак не свалить на код 500. В этом случае сервер будет пытаться честно предупредить клиента. Что же делать клиенту? Вдруг у него форма на 20-30 полей и перезагрузка страницы ооочень расстроит его. Простите, что несколько придираюсь и утрирую, но не может быть, чтобы не было решения?Yeggor
27.09.2016 03:03Общепринятая практика при разработке API при ошибке вернуть подходящий ошибочный HTTP код (не 200) и какие-то данные об ошибке. На своих проектах я предпочитаю при любой ошибке возвращать стандартный ошибочный код 500 (Server Error) и передавать строковый код ошибки (например no_rights_to_edit), тогда на клиенте можно проверить код ошибки и в зависимости от него показывать нужное сообщение. Если при этом API используются только для одного клиента, то можно сразу с сервера передавать сообщение, которое можно показать на клиенте.
Ошибки AJAX запросов можно и нужно обрабатывать. Я имел в виду, что не обрабатываются другие ошибки, например обращение к underfined переменной или прочие непредвиденные ситуации.
AlexZaharow
27.09.2016 09:40undefined — это работа на клиенте, тут всё просто и по идее до клиента такие ошибки доходить не должны.
Просто если сосредоточиться на обработке ошибок ajax, то с сервера в ответ на ajax запрос может прийти ответ, к которому клиент совсем не будет готов и при этом это не будет ошибкой сервера! Ошибку может сгенерировать фильтр сервера, когда до вашего приложения дело ещё не дошло и формат ошибки тоже может быть неожиданным для клиента. Вот вы ожидаете text, а приходит xml или json, а может csv!? Но это тоже не гарантия, что сервер вернул вам что-то из разряда того, что вы ожидаете и что именно ваше приложение сформировало сообщение об ошибке.
За те года, что существует ajax я не видел универсального решения этой проблемы. Ведь проблема формулируется просто — надо как-то дать понять, что именно моя программа обработала запрос. Даже если вы возвращаете ошибку 500 это не гарантирует клиенту, что именно ваша серверная часть вернула эту ошибку. По сути 500 ошибку, которую сгенерировала ваша программа не отличить от такой же ошибки, сгенерированной самим сервером. Процентах в 90 случаев это может выручить, но никогда нельзя быть уверенным, что если в вашем проекте на тесте надежда что 90% уверенности работали в 100% случаев, то в продакшене не случиться, что эти 90% уверенности работают только в 50% случаев, а то и меньше.
Простите за занудство. Меня эта проблема бесит. :)
i360u
Среди альтернатив стоит указать веб-компоненты, которые уже являются стандартом и получают нативную поддержку в браузерах в этом году (пока требуется полифил). Этот подход делает React в качестве решения для декомпозиции интерфейса не самым оптимальным, а такая штука как Polymer — делает работу с компонентами весьма приятной (скоро выходит Polymer 2, в котором описание компонентов будет ближе к ванильному). Помимо этого, веб-компоненты позволяют произвольно совмещать обычную верстку и кастомные теги со сложной логикой внутри, а значит и использовать любые серверные шаблонизаторы (в отличие от того-же React).
baka_cirno
Я бы еще добавил, что Vue позволяет писать свои .vue-компоненты в виде, очень близком к стандарту веб-компонентов (та же структура, слоты, эмуляция изолированных стилей). В будущем это позволит легко переключиться на нативную поддержку.
gearbox
Вторая версия компонент — это да, сказка. Жду не дождусь когда она уже в обычном хроме будет по умолчанию доступна.
i360u
А чего ждать то? Google используют в продакшене. Когда совсем станет мейнстримом — лучше быть во всеоружии и уже сейчас как следует объездить эту лошадку, хотя-бы пока с помощью полифиллов. Я уже более года как перешел с Реакта на веб-компоненты, и не жалею ни капли. Это ведь не какой-то очередной "супер-пупер-фреймфорк тм", а стандарт.
gearbox
Так то я уже пользую в одном своем проекте, причем вместе с tsx — очень удобно, шаблоны получается не нужны. Эдакий реакт на компонентах. Но нативно они только в канарейке работают, я об этом. Про полифилл в курсе.