Сегодня я расскажу, как попал в контрибьюторы Angular. Оказалось, это совсем не сложно и весьма увлекательно! Получилась драма с четырьмя актами и одной моралью. Вот, как всё было.
Я занимаюсь фронтендом в Setronica. Мы живём по Ecommerce-Driven-Development.
У нас в компании 10-летняя история фронтенд-разработки. Кода накопилось много и разного. С его помощью можно пройти все пять стадий принятия, но сейчас не об этом. Сейчас о том, что у нас вообще было: исторически, фронтенд был написан на Grails-фреймворке. Это Java-подобный MVC-фреймворк, который по факту использовался для SSR. JQuery добавлял немного интерактива – всё как в старые добрые времена.
Спустя несколько лет (видимо когда фронтенд перестали писать бэкенд-разработчики), мы пришли к AngularJS. В то время он был очень популярным фреймворком для фронтенд разработки, все были рады, написали 700 000+ строк кода. Все было классно.
Но время шло, кода становилось всё больше, и вот проблема – в нашем мире всё очень быстро устаревает. Устарел и AngularJS. Мы честно обновляли его до версии 1.5, тем временем вышел Angular v2 и обновление фреймворка перестало быть лёгкой задачей. Так мы решили оставить те приложения, которые уже написаны, как есть. И выбрать новый стек для фронтенда. Нюанс в том, что наша платформа состоит из независимых приложений, каждое приложение загружается отдельно от остальных, а значит может быть написано на чем угодно.
Так мы обновили окружение и добавили React. Перестали собирать проекты с помощью Browserify и переключились на Webpack. Сейчас новые приложения пишем на React. Но политика компании в этом смысле осталась прежней – команда, которая работает над приложением, сама решает на чем его писать. Команды старой закалки все еще пишут на AngularJS, ребята с бородой до сих пор правят баги в Grails-приложениях.
Идею настроить инфраструктуру для Angular 8 предлагали многие. Некоторые разработчики даже смогли показать демо приложения, а некоторые просто восклицали, что хотят, но не могут. После того, как мы стали собирать приложения с помощью Webpack, ситуация резко улучшилась и казалось, что нужно просто добавить несколько лоадеров и все готово. Но получилось немного сложнее.
Что-то пошло не так
Мы используем UI-компоненты, которые написали коллеги из нашей компании. Это независимая от фреймворка библиотека, которая по работе ничем не отличается от Material UI и подобных. Элементы объявляются определенной структурой HTML-кода, классы добавляют стилизацию, специальные атрибуты задают данные для отображения. Также элементы можно создавать и управлять ими из JS-кода. Проблему нашла моя коллега Ира. Она просто решила написать очередное приложение на Angular 8 и очень быстро наткнулась на интересную вещь.
Исторически так сложилось, что наша библиотека UI-компонентов получают на вход данные через HTML-атрибуты элементов. Атрибуты не простые, а в большинстве случаев составные. Например, у нас есть компонент, реализующий модальное окно. Чтобы задать заголовок окна, мы добавляем атрибут data-ts.title:
<dialog data-ts="Modal" data-ts.title="Example Modal">
<div data-ts="Panel">
<p>Modal content.</p>
</div>
</dialog>
Звучит несложно. Но что будет, если мы захотим задать текст заголовка динамически в контроллере? Angular предоставляет стандартный способ сделать это:
<dialog data-ts="Modal" [attr.data-ts.title]="modalTitle">
<div data-ts="Panel">
<p>Modal content.</p>
</div>
</dialog>
Выглядит неплохо, но давайте посмотрим на результат в браузере:
Как можно заметить, парсер шаблонов в Angular подставил значение, но атрибут получил только часть имени (data-ts), слово title после точки пропало. Значение исходного атрибута data-ts было заменено результатом интерполяции. Получается, что мы не только не смогли задать заголовок модального окна, но даже не смогли объявить что этот блок сам по себе является модальным окном. Это было проблемой и она блокировала переход на фреймворк в нашей компании.
Надо что-то делать
Первые несколько дней поисков в гугле ничего не дали. Казалось, что это только наша специфика и никому больше нет дела до такого синтаксиса. Справедливо, но с другой стороны HTML поддерживает такой формат, а значит парсер Angular должен поддерживать. Поэтому я решил спросить напрямую – пошел в репозиторий проекта и завел Issue. Начало было многообещающее – команда откликнулась и выставила теги на мой баг. Много информации из них не получить, но можно понять что это действительно баг, что (очевидно) он неприоритетный и что баг находится в Angular compiler. Все в целом как я и рассчитывал. Что произошло дальше? Я стал ждать…
…Спустя 2 месяца ничего не произошло. Я подумал, что так как моя проблема не в приоритете, то ждать можно ещё очень долго. С другой стороны, команда подтвердила, что баг существует, а значит если я возьму всё в свои руки и исправлю неконсистентное поведение, то мне не откажут. Итак, я делаю форк репозитория Angular.
Побежал исправлять
Первое, что я сделал – прочитал правила для внесения изменений в проект (я заранее сделал форк репозитория, так что будем считать это за нулевой шаг). Они содержатся в стандартном файле CONTRIBUTING.md. Этот файл описывает шаги, которые должны выполнить сторонние разработчики для того, чтобы команда Angular приняла их ещё тёпленький Pull Request на рассмотрение. Файл включает такие вещи, как ответственность сторон, принадлежность кода, покрытие тестами, соглашение о формате описания комита и тому подобное.
Далее, вам нужно подписать Contributor License Agreement с компанией Google о том, что вы согласны написать код с такими условиями. Ссылку на соглашение можно найти в конце CONTRIBUTING.md. С правилами разобрались, теперь про сам проект.
Сам репозиторий это обычный yarn-проект, просто очень большой. Вы пишете yarn install и в целом всё готово. Вернее так: пишете yarn install, ждете минут 5-7 и всё готово.
На этом моменте я приготовился, потому что меня ждало самое увлекательное – открыть наконец исходный код. Сначала мне было страшно, потому что это огромный фреймворк и осознать, что происходит вокруг, с первого взгляда не представляется возможным. Но, немного привыкнув, я осознал, что большой размер кода это огромный плюс. Когда у вас такой большой проект, совершенно другие вещи и другие подходы становятся приоритетными. В Angular я увидел, как логично структурированный проект сам документирует себя. Каждый модуль назван соответсвенно тому, что он делает, вложенность директорий раскрывает контекст проиходящего. Навигация по коду здесь – это что-то совсем простое; понимание того, что делает тот или иной блок кода, приходит само собой. Я уже знал, что моя проблема лежит где-то в коде компилятора, так что с легкостью нашел нужную строку:
Итак, что здесь происходит? Мы получаем на вход очередной атрибут элемента (хранится в boundProp). Так как у нас есть префикс attr, мы разбиваем строку на части по разделителю (это точка), чтобы проверить, есть ли префикс или нет. И если да, то подставляем вторую часть от всего имени атрибута. Очевидно, что здесь нужно объединять все части имени атрибута без префикса. Поправим, сделаем как надо:
Отлично! Теперь надо, чтобы никто не сломал нашу функциональность. Напишем тест. В таком проекте, как Angular, это сделать очень легко. Unit-тесты это требование для каждого изменения, так что рядом с каждым модулем вы найдете набор соответствующих spec-файлов. Берем файл того модуля, который изменили, находим там очень близкий по смыслу тест, копируем его и подставляем свои данные на входе и на выходе. Я подал на вход HTML элемент со сложным атрибутом и проверил, что компилятор корректно вернул его имя. Готово, ваши изменения протестированы! Казалось, что будет сложно, но на деле всё получилось достаточно тривиально:
Итак, код я поправил. Делаю коммит, пишу описание в соответствии с правилами и… ставлю чайник, потому что в фреймворк покрыт тестами и их очень много: 37000+. Первый запуск займет очень много времени, но каждый следующий будет длиться гораздо меньше времени, так что ждать пришлось только однажды. Коммит сделал. Открываю Pull-Request. Жду…
Развязка
… Спустя 2 месяца я решил, что ждал уже достаточно и написать код было меньшей из проблем. Что делать дальше? Я решил применить социальную инженерию и стал искать людей, которые делают ревью в репозитории, конкретно тех, кто пишет код в компилятор Angular. Первым в моем поиске выпал Andrew Kushnir, я нашел его фейсбук. Написал. Ждал (гораздо меньше, чем 2 месяца). Решил, что он редко пользуется фейсбуком или не читает сообщения от незнакомых людей. Следующим на очереди появился Pete Bacon Darwin – я сразу заметил, что он указал самый надежный способ общения в 2019 году – почту. И это сработало!
Я чувствовал, что ожидание длилось слишком долго, поэтому действовал наверняка – написал длинное письмо о том, в чем заключается проблема и как устроен наш UI-фреймворк. Добавил ссылки на Issue и Pull-Request в GitHub. В заключение я немного рассказал о себе и нашей компании.
Буквально на следующий день я получил ответ от Пита. Он сказал, что совсем не против, что я написал на почту по поводу своего кода и что впервые видит, чтобы HTML-атрибуты писали в таком стиле. Дал пару советов про то, как улучшить описание коммита и поставил апрув на PR. Дальше события развивались с огромной скоростью… пока я спал.
В штатах наступил день и команда, которая работает над компилятором Angular перевела мой PR в состояние релиза. Они прогнали ещё один набор тестов (видимо, на этот раз интеграционных), и оказалось, что в одном HTML-шаблоне svg-блок содержит атрибут [attr.height.px]="16", и до моих изменений он превращался в height=”16”. После небольшого обсуждения, коллеги решили, что исправят этот svg-шаблон и мои изменения попадут в 9 версию фреймворка!
Мой коммит добавили в основные бранчи, Pull-Request закрыли. Всё это пролетело мимо меня, а когда я проснулся утром и прочитал комментарии, успел испугаться, расстроиться и обрадоваться практически за один момент.
К вечеру я получил еще одно письмо от Пита, где он отметил, что мой код добавили в проект, поблагодарил за вклад в проект и сказал, что будет ждать очередной Pull-Request.
Я в свою очередь поблагодарил за уделенное время и за помощь. Сказал, как важно для меня было сделать этот шаг. Что дальше?
А дальше, оставалось только следить за релизом очередной версии Angular. Я увидел, что мои изменения попали в Angular 9.0.0-rc.2. И вот, буквально месяц назад вышла стабильная 9я версия фреймворка.
Мораль
Для меня главная мораль в этой истории: в любой непонятной ситуации пробуйте и идите до конца. Проверяйте разные варианты. Продвигайте то, что вы считаете правильным. Ищите людей, которые вам помогут достичь желаемого результата.
Если проект большой и популярный, это не значит, что вам нельзя вносить туда свои правки. Если у вас есть хорошая идея или вы уверены, что что-то работает не так, как ожидается – попробуйте. Даже если цена ваших изменений – одна строка. Зачастую команда разработчиков очень позитивно настроена на любую внешнюю помощь. И часто они сами просто не успевают починить ту или иную проблему.
Если получилось у меня, получится и у вас!
Автор: Sergey Nikitin