Под катом — про single-page, сбор ошибок, шаблонизацию и переход от одного URL-а к другому без перезагрузки страницы.
Single-Page
Интерфейс главной страницы «Mail.Ru для бизнеса» достаточно простой и удобный. Именно на этой странице начинается процесс добавления доменов. После регистрации первого домена вам станет доступна админка. Всё вместе — главная и админка (и другие скрытые страницы) – это одно полноценное single-page приложение.
Для реализации single-page приложения были использованы техники, которые с успехом применялись при разработке Почты, Календаря и Адресной книги. Например, отрисовка страниц происходит на клиентской стороне с помощью шаблонизатора Fest (подробнее о нем – чуть ниже), а всё общение с сервером ограничивается обращением к методам API.
Некоторые же приёмы, наоборот, были применены впервые, и я очень рада, что мне удалось привнести что-то новое в процессе разработки 🙂
Мультиавторизация
Помимо красоты и удобства, single-page открывает нам доступ к плюшкам мультиавторизации. Мультиавторизация у нас есть и на основной Почте; для новой же почты с доменами и красивостями это, как говорится, то, что доктор прописал. Можно одновременно сидеть в корпоративном и личном ящике, и быстро переключаться между ними. При этом опять-таки не происходит перезагрузки страницы.
CSS-анимация
Мы решили отказаться от поддержки старых браузеров, поскольку считаем, что администраторы доменов — достаточно продвинутые пользователи. Например, IE мы поддерживаем, начиная с 8-й версии и выше. Благодаря этому у нас развязались руки в плане использования современных плюшек. Особо выделяется среди них CSS-анимация. Как известно, пользователи любят, чтобы на странице все было красиво и плавно. Можно, конечно, это «красиво и плавно» реализовать теми же скриптами, но если браузер сам дает нам возможность организовать динамику стандартными средствами, почему бы этим не воспользоваться?
Основная работа по реализации анимации выполняется с помощью CSS.
.panel { … top: -110px; transition: top 0.5s 0; } /*показ панели */ .panel__show { top: 0; }
Скриптом мы только запускаем ее в нужные нам моменты.
var $panel = $('#id1') , $wrap= $('#id2') , offsetTop = $wrap.offset().top + $wrap.outerHeight() ; $(window).scroll(function() { var show = this.scrollTop() > offsetTop; $panel.toggleClass('panel__show', show); }.throttle(200, $(window)));
Но мы решили пойти дальше и сделать анимацию даже для тех пользователей, которые пользуются браузерами, не поддерживающими эти замечательные CSS-плюшки. Для них мы делаем анимацию средствами jQuery. Для того, чтобы определить, поддерживает ли браузер CSS-красоту или нет, мы используем стандартную браузерную функцию CSS.supports:
var cssTransitions = CSS.supports("transition", "all 1s 1s") || CSS.supports("-webkit-transition", "all 1s 1s") //… etc ; ... if( cssTransitions ) { //анимация средствами css } else { //анимация средствами jQuery }
CSS.supports — это что-то типа стандартизированного Modernizr, только нативного и выполняющего одну конкретную задачу – проверку поддержки определенного CSS-свойства. Нативная поддержка есть в браузерах Chrome 28+, FireFox 22+, Opera 12.1+. Для остальных браузеров, мы используем полифил, написанный одним из разработчиков нашей почты. Этот полифил по-умолчанию доступен практически на любом проекте Mail.Ru и с успехом применяется в некоторых специфических ситуациях.
Fest
Fest — это наша разработка. Он представляет из себя шаблонизатор общего назначения, с помощью которого мы формируем страницы. Fest подходит для шаблонизации как на стороне клиента, так и на сервере. Основное его достоинство – скорость работы: шаблоны «собираются» на клиенте действительно быстро. Другая особенность — это поддержка модульности на уровне шаблонов: мы создаем один шаблон и используем его для множества разнообразных страниц или форм.
Все формы, которые присутствуют на странице — добавление домена, пользователей, попапы, добавление/удаление администратора — строятся на одном шаблоне. Благодаря универсальности Fest мы можем позволить себе внутри одного и того же шаблона отрисовывать абсолютно разные контролы: попапы с дропдаунами, попапы без дропдаунов, страницы с чек-боксами. Формирование всех этих форм происходит в одном месте.
Подробнее об использовании Fest рассказывал Константин Замякин на Форуме Технологий в докладе, посвященном разработке Календаря Mail.Ru.
History API
Как вы могли заметить, мы считаем хорошей практикой использование сторонних разработок, если они хороши и подходят для наших целей: зачем изобретать велосипед? Так, в нашем проекте мы активно используем полифил History API – форк библиотеки от Devote. Применение History API существенно упрощает жизнь разработчикам. Mail.Ru для бизнеса «магическим образом» переходит от одного URL-а к другому без перезагрузки страницы — в зависимости от того, какие действия совершал пользователь. History API позволяет нам передавать нужные параметры, которые никак не отображаются в UI, и вуаля: новый слайд отрисован. При этом, если пользователь перезагрузит страницу, то он окажется ровно на том месте, где был.
Как это все работает? API предоставляет нам доступ к объекту типа History, который есть у каждой вкладки и располагается по адресу window.history в объектной модели. С помощью JavaScript мы можем манипулировать с адресной строкой, «переходя» по страницам с помощью методов .pushState(state, title, url), которые добавляют новый url в историю браузера и .replaceState(state, title, url), которые меняют текущий url без добавления нового пункта в историю.
Свойство же state объекта history всегда будет указывать на актуальное «состояние» текущего url-а. Например, если мы сделали pushState({ data: “test1” }, “”, “url1”) и pushState({ data: “test2” }, “”, “url2”) – т.е. совершили два «перехода» по страницам с добавлением пунктов в историю браузера и оказались на странице “url2”, а пользователь нажал «Back» в браузере и оказался на странице “url1”, то значение свойства history.state.data будет “test1” – то, что нам нужно.
Особенно это удобно на главной странице: если пользователь хотел перейти на вполне конкретный адрес, но в данный момент не авторизован, то в pushState мы передадим объект state, содержащий нужный адрес, а после авторизации мы перенаправим его туда, куда ему хотелось попасть — таким образом, ему не придется снова переходить ручками. Вся логика переходов выполняется в браузере, без перезагрузки страницы, при этом адрес страницы выглядит «по-человечески», без хешей (“#”).
//определяем текущий URL var path = history.location.pathname; // history.location – from polyfill if( /* пользователь не авторизован */ ) { var state = { notAuth: true , urlBack: (path != "/") ? path : "" }; //запоминаем текущее состояние пользователя и // переходим на главную страницу с отрисовкой соответствующего слайда history.pushState( state, null, "/"); }
Затем, после авторизации, мы понимаем, куда хотел отправиться пользователь, и перекидываем его в место назначения.
RequireJS vs SingleFile
Большое singe-page приложение это всегда много js-модулей. Какие-то модули — это общий функционал, который нужен практически всегда, какие-то модули нужны только на отдельных страницах. При разработке достаточно сложного функционала количество js-файлов растёт очень быстро. При этом разработчику нужно постоянно думать о зависимостях и о порядке подключения js-файлов. Ошибившись в порядке или забыв подключить нужный файл, можно получить ошибку в самом непредсказуемом месте. AMD API как раз решает эту проблему. Разработчику просто нужно указывать список зависимостей и объявлять модули специальным образом, а о загрузке этих модулей в правильном порядке позаботится RequireJS.
Но было бы неправильно заставлять пользователя ждать загрузки множества файлов каждый раз, когда он заходит в наше приложение. Поэтому RequireJS мы используем только на этапе разработки, а в бой идёт «сборка» — один js-файл, который содержит все модули, необходимые для работы приложения. Сборка производится библиотекой r.js, которая является частью проекта RequireJS.
Понятно, что в большинстве случаев весь набор модулей не нужен и может показаться, что некоторые из них подключаются «зря», увеличивая количество данных, передаваемых в браузер. Но это только на первый взгляд. На самом деле gzip-сжатие на сервере отдаваемых js-файлов практически нивелирует эту проблему. Тем более, что мы экономим на http-запросах. В результате один большой js-файл загрузится быстрее, чем несколько маленьких, даже с учётом того, что маленькие файлы будут загружаться «лениво» — т.е. только по мере необходимости. Для подробного ознакомления с вопросом предлагаю к прочтению статью Загрузка и инициализация JavaScript или самостоятельный research на тему.
Сборка проекта
Такие вещи, как описанная в предыдущем разделе сборка js-файлов, не должны делать руками. Перед выкладкой в продакшн, помимо сборки js-файлов, нужно собрать css-ки из scss-файлов, «скомпилировать» fest-шаблоны и полученный результат передать js-сборщику, нужно обновить номер версии в конфигах и т.д. Все эти вещи нужно было как-то автоматизировать.
Было принято решение использовать Grunt – это менеджер задач общего назначения, написанный на js под nodejs. Он позволяет js-разработчику писать таски для сборки проекта на сервере. Для Grunt существует база готовых тасков, из которой и были взяты, например, grunt-contrib-sass и grunt-contrib-requirejs. Grunt запускает нужные таски автоматически при выполнении git push.
Cбор ошибок
Веб-разработчику полезно знать об ошибках на клиентской стороне. Еще круче было бы где-то их собирать, хранить и потом исправлять. Мы для этого используем стороннюю платформу Sentry.
Sentry – это опенсорсный проект, который работает с целой россыпью языков и платформ. Он позволяет отслеживать как серверные, так и клиентские ошибки и делать по ним статистику. Этой платформой также пользуются, например, в Mozilla и Instagram.
Sentry умеет отправлять письма с описанием ошибок. При этом можно не опасаться, что одинаковые репорты о малозначительных неполадках забьют ящик: параметры сообщений, которые достойны того, чтобы беспокоить разработчика, можно легко настроить.
С помощью дашборда мы можем не только отслеживать ошибки в реальном времени, но и фильтровать их по самым разным параметрам – от источника бага до статуса, который мы сами же ему и назначили.
Особенно незаменима Sentry при ловле экзотических багов, которые мы воспроизвести не можем. Согласитесь, имея перед глазами строчку кода и описание ошибки (в том числе частоту появления и браузер), гораздо удобнее разбираться, что пошло не так.
Однако простого описания ошибки и номера строки может оказаться не достаточно. Для некоторых экзотических или сложно воспроизводимых ошибок нужна ещё дополнительная информация для анализа этой ошибки. Например для ошибки “$ is not defined” нужно понять, а загрузился ли у нас jQuery? Для сбора браузерных ошибок мы используем библиотеку Raven.js, которая обладает одной недокументированной возможностью – вызывать обработчик dataCallback перед каждой отправкой ошибки на сервер:
var ravenSetTimeout = typeof setTimeout === 'function' && setTimeout; var options = { dataCallback: function(obj){ var userData; if( typeof obj === "object" ) { userData = obj["sentry.interfaces.User"]; if( !userData ) { userData = obj["sentry.interfaces.User"] = {}; } userData.inFrame = window.parent !== window; userData.jQueryVersion = typeof jQuery === 'function' && jQuery.fn.jquery; userData.setTimeoutBody = typeof setTimeout === 'function' && (setTimeout + "").contains("[native code]") ? "[native]" : ravenSetTimeout ? setTimeout === ravenSetTimeout ? "[raven]" : "[other]" : "[none]" ; } return obj; } }; Raven.config("…", options).install();
В этом примере мы проверяем, не запушены ли мы в iframe’ме, версию jQuery (если она вообще загружена) и код setTimeout (т.к. он может быть подменён). Мы же собираем чуть больше статистической информации для правильной диагностики ошибок. Значение свойства «sentry.interfaces.User» будет отображаться в интерфейсе Sentry в специальном поле.
Discuss
Так работает наша Почта для бизнеса. Надеюсь, она будет радовать пользователей красотой, удобством и функциональностью. Подробнее про «Mail.Ru для бизнеса» можно почитать прямо здесь. Напомню, что сервис сейчас в бете и мы будем рады вашим отзывам и предложениям по почте feedback@biz.mail.ru или в комментариях к этому посту.
С удовольствием окажем любую поддержку пользователям Хабра, которые захотят попробовать наш новый сервис.
Ольга Алексеева,
Егор Халимоненко
Группа frontend-разработки Почты
ссылка на оригинал статьи http://habrahabr.ru/company/mailru/blog/190340/
Добавить комментарий