amorgunov
1.97K subscribers
16 photos
1 video
101 links
Пишу о JavaScript и его экосистеме (TypeScript, React, NextJS), о процессе разработки и архитектуре.

Блог: https://amorgunov.com
По всем вопросам (рекламу не продаю): @saaaaaaaaasha
Download Telegram
Свои правила в ESLint

Вчера писал про @deprecated из JSDoc. Его просто использовать, но он не защитит использование компонента на уровне автоматизации. В CI не будет никаких ворнингов и ошибок, что задепрекейченный компонент или функция используется. Так же бывают кейсы, что нужно запретить что-то сложнее, чем использование компонента: например, не позволять вызывать функцию внутри какой-нибудь другой или, допустим, использовать название переменных меньше двух символов.

Все это можно сделать на уровне ESlint. Можно найти уже готовые статьи на эту тему (например, https://blog.scottlogic.com/2021/09/06/how-to-write-an-es-lint-rule-for-beginners.html). Основной алгоритм: (1) построить абстрактное синтаксическое дерево (AST) JS-фрагмента, (2) на основе него написать правило.

AST - это способ представления исходного кода в виде структуры (дерева), которое потом преобразуется в байт-код. Онлайн его можно построить тут: https://astexplorer.net/. Например, запись const a = 1 будет выглядеть следующим образом:

{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"declarations": [{
"type": "VariableDeclarator",
"id": {"type": "Identifier", "name": "a"},
"init": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}],
"kind": "const"
}]
}

Корень дерева всегда будет нодой с типом Program. У каждой ноды есть поле type (*Declaration, *Expression, Literal и т.д.) и свои дополнительные поля.

Правила, которые должны что-то запрещать (хотя о чем я, весь ESLint построен на запрете всего) можно описывать в виде JavaScript-функции. Так же можно описать в сокращенном виде строкой через правило no-restricted-syntax. Для этого нужно описать так называемый selector (по аналогии с css, нужно описать селектор, только в данном случае такой, использование которого нельзя допустить):

Несколько примеров:

- FunctionExpression - запретить функциональные выражения
- CallExpression[callee.name='require'] - запретить использование require
- CallExpression[callee.name='setTimeout'][arguments.length!=2] - разрешить использовать setTimeout только с двумя параметрами
- FunctionDeclaration[params.length>3] - не допускать объявление функции с более, чем тремя аргументами.

Так же помимо селектора можно указать message и описать ошибку, почему функциональность не разрешена. В моем кейсе нужно было для определённых тестов запретить использование стандартного describe (из jest). Для этого было написано следующе правило:

{
"rules": {
"no-restricted-syntax": [
"error", {
"selector": "CallExpression[callee.name=\"describe\"]",
"message": "Используйте ??? вместо describe",
}
]
}
}



По итогу несколько строчек кода и получаем свое кастомное ESlint-правило, что очень круто на мой взгляд. Если нужно запретить импорт какого-нибудь файла, то можно воспользоваться правилом no-restricted-imports, в котором достаточно указать только путь.
Борщик Parcel2

В Яндексе много лет назад использовался сборщик под названием borschik. Я с ним не так много работал, но у меня о нем сложилось довольно негативное впечатление. Он был недокументирован, никто не знал, как он работает, да и работал он посредственно. Часть файлов он просто склеивал (и правда, зачем нужно строить AST, когда можно просто склеить), но и с этим он не всегда справлялся, к сожалению. В общем, я очень рад, что в какой-то момент вся компания перешла на стек react/webpack и про borschik быстро забыли.

Сейчас многие пишут о релизе новой версии Parcel 2 (можете почитать пост про основные изменения в канале #defront/1026 или в официальном блоге https://parceljs.org/blog/v2/). Основная идея этого сборщика в том, что не нужны никакие конфигурационные файлы, the zero configuration так сказать.

Parcel и правда хорош, так как позволяет попробовать любой популярный фреймворк, язык (TypeScript), технологию (JSX, MDX) без дополнительной настройки окружения. Вообще мне очень нравятся идеи «no config» (когда не нужно конфигурировать инструмент и все работает из коробки) и «no code» (когда не нужно писать код, чтобы завести какую-нибудь фичу). Parcel еще обзавелся неплохой обновленной документацией (первый раз я попробовал этот сборщик еще в 2017 году, когда документации вообще не было).

Но пару месяцев назад, когда пробовал Hyperapp #91, решил использовать Parcel второй версии (тогда она была доступна в бете). Что по итогу:

- Проблемы с TypeScript. У Parcel внутри зашит свой tsconfig (свой конфиг с опциями компиляции), из которого некоторые поля используются без возможности замены (например path). Т.е. определив другой path в своем tsconfig.json, IDE будет показывать, что все ок (и переходить через импорты по файлам), но сборка будет падать. А для того, чтобы полностью использовать свой tsconfig, нужно уже добавлять конфигурационный файл для Parcel;
- Проблемы с JSX. Для JSX в Hyperapp нужно указать другое значение для jsxFactory (тоже в tsconfig), которое Parcel не умеет обрабатывать. Для этого пришлось в babel-конфиге прокинуть кастомную опцию (jsxPragma) для @babel/preset-typescript;
- Обратная совместимость с первой версией. Ее почти нет и вся информация в сообществе именно для первой версии, поэтому для второй приходиться дебажить исходники и тратить немало времени, чтобы разобраться в проблеме.

И это был очень маленький проект. Если проект будет больше, то количество проблем будет только возрастать. Поэтому, если решите попробовать внедрять Parcel, будьте готовы к тому, что не будет «zero config». Когда я занимался настройкой и разбирался в неявных моментах работы, навеяло воспоминаниями о borshik-е. Конечно же, это совсем разный уровень, но все же.

По итогу у нас есть Webpack (энтерпрайз решение с кучей обвесов и огромным сообществом), Rollup (идеально подходит для сборки библиотек, так как добавляет минимум своих оберток), Parcel (для быстрого старта, тестовых и изучения новых технологий). Есть еще Snowpack, но с ним мне не приходилось работать.
Как пишем компонентные тесты

Пару месяцев назад я писал, что интегрирую компонентные тесты на testing-library/react (#45) в рабочий проект. Если кратко, это такие тесты, которые проверяют пользовательские сценарии в изолированном окружении (jsdom), из-за чего они быстрые и стабильные как юниты. На текущий момент все инфраструктурные вещи завершены и уже реализовано почти 200 тестов, которые прогоняются за секунды. Конечно, интеграционные кейсы продолжают писаться как автотесты, но для всего остального - компонентные.

Процесс у нас построен относительно просто: тестировщик пишет тесткейсы, разработчик реализует их и на каждый Pull Request генерируется Allure-отчет (html-страничка с описанием шагов каждого теста, https://github.com/zaqqaz/jest-allure).

Что было сделано дополнительно:

- (1) Сразу решили использовать свои кастомные селекторы getBySelector/queryBySelector для возможности поиска элементов по названию css-класса. Авторы библиотеки против такого подхода, так как это уже будет не пользовательский сценарий (юзер же не может кликнуть по классу, он кликает по кнопке). Но это жутко удобно и пока не было проблем в связи с их использованием;

- (2) Написали обертку для монтирования компонентов с Redux-обвязкой;

- (3) Все общие селекторы и методы пишутся как Page Object (PO, https://webdriver.io/docs/pageobjects/). Мы этот подход используем в автотестах и он реально ускоряет написание тестов, если PO были написаны ранее. Например, тест для выбора даты в каком-нибудь компоненте фильтра может выглядеть следующим образом:

po.datepicker.open();
po.datepicket.setDay(5);
po.datepicker.close();


А внутри PO (он может быть классом или обычным объектом) уже и реализован поиск нужных элементов по селекторам, работа с событиями и другая логика.

- (4) Свой метод it, в который прокидываем объект с названием теста и ключом на тесткейс, чтобы связать их вместе;

- (5) Дополнительный хелпер step(), который оборачивает часть тела теста согласно тесткейсу. Например, тестировщик описал 3 шага, внутри теста (it) должно быть 3 вызова step():

it('Проверяем загрузку данных', () => {
step('Монтируем компонент', () => {...};
step('Кликает на кнопку "Загрузить еще", () => {...};
step('Ждем появления данных", () => {...};
});


Эти шаги прорастают в финальный отчет.

===

Плюсы таких тестов очевидны: тесты пишутся быстро и интуитивно (для всех, кто писал хоть какие-то тесты), быстро выполняются (один тест - 50-150мс), обладают стабильностью (не нужен реальный браузер, сетевые запросы мокаются) и еще формируют визуальный отчет, понятный для тестировщиков.

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

Появилась идея добавить режим запуска тестов через puppeteer (testing-library позволяет это делать). В CI прогонять тесты на jsdom, а при проблемах в разработке смотреть в браузере, что пошло не так. Из-за разного API использования придется сделать дополнительный слой абстракции и не исключено, что в каких-нибудь сложных кейсах тесты будут работать по-разному. Но это решило бы проблему сложности локального написания тестов. Так же в CI можно упавшие тесты прогонять в puppetter версии и делать скриншот состояния из браузера с ошибкой.


Но пока это все лишь мысли вслух.
Буферизация вывода в Jest

Все же как удивителен мир опенсорса. Я думаю у каждого была ситуация, когда в какой-нибудь библиотеке что-нибудь не работает и вы ходите по issues на гитхабе, по исходному коду в поисках решений. Иногда не везет (хотя с какой стороны посмотреть), и единственный выход - законтрибьютить (но понимая, что на это может уйти бесконечно много времени). Иногда везет, и есть решения в ишьюсах. А иногда попадаются целые пул реквесты, которые осталось только замержить и проблема будет решена.

Недавно на работе наткнулись на кейс, что unit-тесты просто зависают (используемый нами тестраннер jest по идеи должен через 5 секунд выкидывать исключение по таймауту, но этого не происходило). И проблема усугублялась тем, что в логах было пусто. Когда начали разбираться, поняли, что по умолчанию jest подменяет в тестах объект console и использует BufferedConsole (код), который складывает все логи в буфер и выводит их после выполнения теста. Есть встроенный режим работы для разработки --verbose (https://jestjs.io/docs/cli#--verbose), который должен выводить логи сразу. Мы нашли место падения, но ошибку так и не увидели, так как все логи так же буферизировались и выводились асинхронно на следующий тик.

Еще одна неприятность (не критичная, но все же) - jest мог выводить логи после сообщения, что тесты успешно пройдены. Выводится часть логов, потом успешная отбивка и только потом еще часть логов, которые явно должны выводиться раньше отбивки. Все так же из-за буферизации.

В списке ишьюсов было несколько отбивок по проблеме в стиле «Jest eats up all console.log messages during debugging», а в списке с Pull request-ами попался тот, который решает проблему (ему было уже около 8 месяцев, так как были проблемы с коверейджем и нужно было написать тесты). В нем была сделана небольшая правка VerboseReporter (jest-reporter), чтобы логи сразу выводились в терминал. В этом кейсе автор PR не пропал и всё работал над тем, что бы мерж состоялся, что в итоге и произошло. Через несколько дней (как мы столкнулись с багой) правки выкатились вместе с версией 27.2.5.

Вот такая вот история. За одно мы переопределили объект console для кастомизации вывода логов для нашего проекта:

import {CustomConsole} from '@jest/console';

function format() {/*...*/}

global.console = new CustomConsole(
process.stdout,
process.stderr,
format
);


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

Затронув тему редакса пару недель назад #92 (или как любят называть его мои коллеги, редукс), хочется порассуждать насчет его актуальности. На дворе конец 2021, появление микрофронтендов, бурный рост TypeScript и GraphQL. Как дела у редакса, не умер ли он уже среди массы новых решений по работе с состоянием приложения в React-приложениях?

Если хочется узнать ответ, но нет времени читать: нет, не умер, можете использовать.

За два года популярность редакса упала в полтора раза, это можно посмотреть по трендам в google. Это же подтверждает статистика с npm-stat, скачивания за 2021 не увеличились по сравнению с прошлым годом, а так же отношение разработчиков по опросу state of js, удовлетворенность падает с каждым годом. На свое детище уже давно набрасывает Дэн Абрамов (один из авторов) в стиле: а может он вам не нужен. Основная критика направлена на концепцию глобального единого стора, которая была и остается одной из основных принципов библиотеки.

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

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

Есть ли что-нибудь еще? Всегда можно найти к чему придраться, и следующий недостаток - нет коробочного решения работы с асинхронностью и сайд эффектами. В Яндексе на каких проектах я только не работал, мы везде использовали Redux. И везде был свой способ работы с сайд эффектами (redux-saga, redux-thunk, redux-observable). Везде свои проблемы, подходы, практики.

И конечно же производительность. Сам редакс очень быстр (обычные функции). Но провайдер оборачивает все дерево компонентов, и на каждое изменение все дерево может обновляться (если не использовать connect из react-redux, reselect и мемоизацию компонентов).

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

К счастью есть immer, который позволяет работать с иммутабельным стейтом на нативном JS (проблему поймут те, кто писал на ImmutableJS, где для изменения поля приходилось использовать свои абстракции).

Вернемся к вопросу, а зачем его использовать, если есть столько минусов?

Во-первых, несмотря ни на что, он остается самым популярным решением (по статистике того же npm-stat) с огромной экосистемой, на порядок опережая альтернативы. Если новый разработчик придет на проект, он как минимум будет знать о редаксе (в отличие от рекоилов и эффекторов). Во-вторых, если приложению подходит идея глобального стора (например приложение - это модуль, или оно небольшое). В-третьих, Redux очень круто отделяет view-слой от бизнес-логики и его удобно использовать при миграции UI (например, при редизайне). В четвертых, если есть необходимость сериализуемых экшенов (например при отправке их на сервер или когда нужно возвращаться на предыдущее состояние - например, при разработке редактора).

По итогу, у Redux-а дела совсем неплохи, его точно можно использовать, но не бездумно и не везде. Прежде чем брать его в новый проект, я бы несколько раз подумал и задал себе вопрос: «А точно ли он будет уместен для использования?».
Следующая станция - Самокат.

Это сотый 💯 пост в канале, поэтому в нем расскажу о новостях, связанной со мной, с моими дальнейшими шагами.

В ноябре я перехожу в Самокат, в качестве лида, писать на React Native. Поэтому иногда буду разбавлять историями мобильной разработки.

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

Забавный факт, что лет 9 назад, когда искал стажировку, чуть не устроился на iOS разработчика на Objective-C, но не прошёл финальное собеседование из-за довольно сложных вопросов для стажера (это было в 2012 году; тогда от стажеров ждали на уровень меньше того, что ожидают сейчас, но меня спрашивали про алгоритмы и их сложность, структуры данных и Objective-C, который я вообще не знал). По сути сейчас вернулся к тому, с чего мог начать - к мобильной разработке.

Почему ухожу? Нет конкретной причины, все свелось к общей усталости, зоне комфорта (которая не позволяет расти и развиваться), желанию позаниматься чем то другим. В Яндекс я приходил не на конкретный проект, а просто в компанию (бренд, имидж, на тот момент это казалось чем-то особенным). Сейчас мне это не так интересно. В Самокат иду уже целенаправленно заниматься продуктом.

За развитие самого Яндекса (да и Маркета, в котором я работаю последний год) совсем не переживаю, учитывая, какими шагами он развивается (можете посмотреть на курс акций за последние 5 лет), как экосистема (в виде разных продуктов) вплетается в нашу обычную жизнь и, самое главное, какие крутые люди в нем работают. Поэтому поста, как же в Я плохо, вы не дождетесь :)
Давно я ничего не писал. Возможно сейчас не лучшее время, но написание постов позволит немного отвлечься от текущих событий.

По поводу событий, которые сейчас происходят и которые нельзя называть своими именами.. То, что длится уже третью неделю. Это пиздец. И самый ужас не в почти разрушенной экономике России и постепенном откате к 90-ым, не в санкциях и уходе западных компаний из абсолютно всех сфер. Самый ужас в том, что миллионы судеб сломано, тысячи людей лишились своих домов, сотни гибнут каждый день.

У моего коллеги из команды начали бомбить жилой район в Харькове, он с женой и детьми почти без вещей уехал из города и уже не верит, что можно будет когда-нибудь вернуться домой. Близкие родственники из Черкасс на протяжении 10 дней прятались в бомбоубежищах из-за взрывов и сирен; в итоге тоже уехали из города. Я каждое утро просыпаюсь и открываю телеграмм в надежде, что переговоры дошли до точки, когда военные действия будут прекращены.

Страшно. Больно. Чувство беспомощности от того, что ты ничего не можешь изменить. Чувства стыда, что всю жизнь не интересовался политикой и как губка впитывал то, что рассказывают у нас по телевизору. Я до сих пор не могу поверить, сколько людей поддерживают текущие действия правительства (или которым все равно), несмотря на то, что мы живем в эпоху с доступным интернетом. Хотя с той агрессией, с которой блокируют все ресурсы с альтернативной точкой зрения и новыми законами в стране, когда за твиты «нет войне» формируют уголовные дела, нельзя называть интернет доступным.

Немного успокаивает то, что почти все мое окружение (коллеги, родные, друзья) всё понимают. Что в общей своей массе люди из IT всё понимают и многие не молчат. Пока мы есть друг у друга, мир не потерян.

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

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

Раньше я придерживался мнения, что будущее за вебом. В том плане, что через N лет большинство мобильных приложений будут написаны на JS, запускаться как PWA (progressive web app) через отдельную иконку на рабочем экране и ни чем не уступать нативным мобильным приложениям. Почему? Потому что (1) веб как платформа развивается в несколько раз быстрее, чем нативные, которые ко всему прочему разрабатываются независимо друг от друга (Android и закрытая iOS), (2) бизнесу будет намного дешевле всю кодовую базу фронтенда содержать на одном стеке (чем иметь три разные команды) и (3) история с десктопными приложениями, которые почти все мигрировали в веб.

Пока этот переход на веб невозможен из-за одной просто вещи. И нет, это не производительность. У PWA нет доступа ко многим аппаратным возможностям (платежи, покупки, нотификации, NFС и т.д.). Причем не столько из-за технических сложностей, а просто тот же Apple не хочет этого (в Android ситуация получше). Свой стор, приложения, экосистема, с комиссиями за каждую покупку, это большие деньги, которые веб-приложения не будут приносить. Также безопасность данных пользователей, которую будет сложнее предоставить, если у веб-приложений будут «root» права к девайсу. PWA нельзя просто удалить из стора в случае проблем.

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

Кстати, а вы знали, что первые айфоны презентовали с возможностью писать приложения на веб технологиях (а SDK появился позже). Стив Джобс на презентации сказал: «instant distribution and easy to update». Это (4) еще одна актуальная причина.

Как у меня изменилось мнение за это время? Оно только укрепилось :) Но если говорить про React Native, то как техническое решение он довольно интересен. Потому что является мостиком между вебом (в виде JS и React-а) и нативной разработкой. Дальше расскажу, как работает RN.
Как работает React Native

Что интересно, я задаю этот вопрос на собеседованиях кандидатам, которые пишут на RN больше 3х лет. Пока не один кандидат, которого я успел пособеседовать, не дал исчерпывающий ответ.

Про RN нужно рассказывать в контексте двух архитектур - старой и новой. Новую (Fabrics, JSI, TurboModules) представили еще в 2018 году, но она до сих пор не выкатилась в стабильной версии. Правда, как рассказали пару дней назад в официальном блоге, новая архитектура будет доступна под флагом в следующей версии RN (0.68.0), а попробовать ее можно уже сейчас. Но про новую расскажу потом, сейчас про текущую.

JS код, который мы пишем (React-компоненты, бизнес логика) собирается в бандл с помощью специального сборщика Metro (аналог Webpack, но только для RN) и упаковывается в финальный APK-файл нативного приложения (в случае Android-a).

Этот JS-бандл при открытии приложения запускается в отдельном потоке (JavaScript thread) в виртуальной машине JavaScriptCore (движок от Apple, использующийся в Safari). Там крутится весь JS, внутри нативного приложения.

В другом потоке (backgroud thread) формируется React Shadow Tree (аналог DOM-дерева в вебе, в RN никакого DOM нет). Сейчас будет сложно. На основании этого объекта строится дерево React Host Components. Это компоненты, реализация представления которых предоставляется представлением платформы (Android, iOS). Аналогия из веба - React Host Components представляют собой div или span. React Shadow Tree так же содержит значение пропов из React компонентов и их размеры, которые рассчитываются с помощью layout-движка Yoga.

RN предоставляет библиотеку компонентов, которые можно использовать в своих приложениях и для каждого из компонентов есть реализация представления для разных платформ. Например, для компонента Text - Android предоставляет TextView, iOS - UITextView. Так же для всего из стандартной библиотеки есть реализация для Web-а, поэтому приложение на RN можно запускать в браузере.

И наконец Main или UI тред, в котором рендерятся нативные компоненты, которые видит и с которыми взаимодействует пользователь.

Треды общаются друг с другом асинхронно через мост (bridge) путем передачи json-сериализуемых сообщений. Например, что происходит, если пользователь кликает по кнопке в приложении? (1) Создается событие в UI потоке, которое (2) отправляется через мост в JS поток, (3) в JS вызывается колбек onClick. Если дерево компонентов изменилось, (4) то отправляется сообщение в Background поток, (5) формируется следующее Shadow Tree, (6) и посылается еще одно сообщение в UI поток для отрисовки новой вьюхи. Здесь я сильно утрировал, на самом деле вместо одного сообщения отправляется десятки даже при обычном клике.

Мост является источником нескольких проблем: объем данных, которые приходится гонять между потоками (данные сериализуются - копируются) и асинхронность - события сложно синхронизовать и всякие кейсы типа отмены скрола нельзя применить синхронно.

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

Сначала хотел написать про новую архитектуру, но потом передумал. Я не успел ее попробовать и рассказать больше, чем написано в официальной документации (кстати, рекомендую почитать, если интересно), не смогу. Поэтому об этой серии как-нибудь в следующий раз.

Сегодня хочется рассказать про один из практических кейсов отличия от веб-разработки, который меня сильно поразил. Казалось бы, очевидная вещь, версионирование.

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

На мобилках же после каждого релиза в сторах появляется новая версия (которая публикуется от нескольких часов до дней). 40% пользователей обновляются в первые дни, еще 40% "сидят" на 2-3 последних версий, а остальные 20% просто в какой-то момент перестают обновляться. И это кардинально меняет кучу процессов (релизный, итерации продуктовых фич, багфиксинг). Из-за этого страдает доставка новых фич до пользователей (time-to-market). И субъективно по моим ощущениям это делают разработку более медленной.

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

Что делать, если нужно оперативно вырубить часть функционала? Здесь на помощь приходят фич флаги. Они описаны в обычном json-е, который запрашивается с сервера при старте приложения и на основе которого пользователям показываются только включенные фичи. Все просто, за исключением момента, когда фич становится слишком много и возникает "feature flag hell", но и это тоже решается. Фич флаги часто используют и в вебе, поэтому не буду на них останавливаться.

А что делать, если баг критичный и фич флагом нельзя его отключить? Есть такой подход, называющийся "Over-The-Air update" (обновление на лету). Нативные зависимости и пермишены запрещено обновлять таким образом по правилам сторов, но вот JS бандл - можно. А бизнес логика приложений на RN написана именно на JS. Бинго!

Для обновления на лету есть инструмент от Microsoft - ReactNative CodePush. Если в двух словах, при запуске приложения, отправляется запрос на сервера Microsoft (да-да, еще один запрос), который проверяет, есть ли для версии на девайсе новый JS бандл. Если есть, он выкачивается (можно принудительно, можно в фоне) и пользователю доступна обновленная версия.

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

В идеальной вселенной релизы можно было бы почти полностью перенести на CodePush-и, если бы не пару НО. (1) JS бандлы жирные и их скачивание и синхронизация может занять довольно много времени и (2) периодически новые бандлы не могут установиться без крэша приложения и происходит откат на предыдущую версию. Поэтому мы их используем только в самых критичных ситуациях.
Обратил внимание, что stackoverflow выложили инста-фильтры (темы) для сайта. Сначала даже обрадовался какой-то UI-активности на любимом портале для разработчиков, но потом понял, что сегодня же 1 апреля. Будьте осторожны, я думаю многие сервисы придумали какие-нибудь первоапрельские розыгрыши 🙂

(ниже пример с фильтром win95 ↓)
К - Команда

Наконец-то прочитал наикрутейший пост про команду в IT: https://vas3k.ru/blog/team . Я его добавлял в список для чтения не один раз и вот на днях все же прочитал. И получил огромное удовольствие. Для лидов пост вообще обязателен к прочтению.

Интересно, что у многих систем есть свой жизненный цикл. У компании, которая рождается стартапом и в конце пути заканчивает банкротством/поглощением более большой. У команды, когда она рождается из нескольких человек и может умереть при уходе лида или одного тиммейта. Да что уж там, React-компонент, который начинает своей путь от монтирования и заканчивает unmount-ом. Но между этими этапами есть много других и в каждый момент времени система находится на каком-нибудь одном. Стейт машина, в общем.

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

Про жизненный цикл команды в материале не говорится, я в свое время проходил теорию модели Такмэна (еще работая в Яндексе, на курсе для руководителей). По ней путь команды делится на 5 этапов («forming», «storming», «norming», «performing» и «adjourning»), рассказывается как на каждом нужно с командой работать и переводить команду между этапами. Там много теории, но есть несколько полезных кейсов. Например, стабильную команду нужно периодически выводить на этап «performing», (например, выдавая сложные проекты или жесткие дедлайны), после вознаграждать (выдать бонусы, публично похвалить) и возвращаться на этап «norming» (чтобы члены команды не перегорели от режима работы).

---

Но все, что сказано выше, неинтересно без еще одной составляющей - это роли в команде. Есть популярная модель Белбина (даже тест можно пройти, кто ты), которая выделяет 9 ролей. Я когда этот материал проходил, через пару дней забыл почти все роли, там только их названия запомнить чего стоит. А если их не помнить, то и применять не получится.

В посте из блога выделяют 4 роли: 🏃‍♂️ Хуятор, 👩‍🔬 Экспериментатор, 👨‍💼 Бюрократ и 🤝 Интегратор. У каждой роли есть свои достоинства и недостатки, а так же на каком этапе жизни компании/команды роли принесут больше пользы. Роли так же можно скрещивать и получить новые (например, 🧑‍🚀 Стартапер = 🏃‍♂️ Хуятор + 👩‍🔬 Экспериментатор или 👷‍♀️ Архитектор = 👩‍🔬 Экспериментатор + 👨‍💼 Бюрократ). Каждая роль закрывает недостатки другой, поэтому самые сильные команды это те, в которых активны все роли.

Зачем это вообще нужно? Знание ролей (и кто в команде какую занимает, что не сложно определить) и на каком этапе жизненного цикла находится компания/команда помогут понимать, как общаться друг с другом, каких людей нужно нанимать в команду или из кого собирать новую, понимать, когда кто-то вероятнее всего уйдет. Да и просто определять источники проблем в команде (если они есть).

Нужно ли все это обычному разработчику? Конечно! Как минимум понимать, почему никак не удается сработаться с «тем чуваком», как максимум, понимать устройство команды.
Небольшая подборка из трех видео, которые посмотрел за последнее время. Все рекомендую к просмотру, поэтому в этот раз без оценки.

🍿 Браузер. Рендеринг. Производительность (https://www.youtube.com/watch?v=tbDxm1hiEI4)

В начале сказана важная мысль. Мы пытаемся следить за производительностью приложений на уровне фреймворка (например, количеством ререндеров), но забываем следить за производительностью на уровне браузера.

Рассказ про то, как браузер занимается рендерингом. Про основные этапы пайплайна рендеринга: parse → style → layout → paint → compose (по теме есть обзорная статья на web.dev). Какие еще есть этапы, как их посмотреть в devtools и на закуску несколько практических примеров оптимизации производительности отрисовки в браузере. Обо всем этом в докладе.

Кстати, спикер, Сергей Иванов, ведет телеграмм канал UfoStation, в котором много рассказывает про фронтенд (сейчас канал закрыт для входа новых пользователей, но я надеюсь, что это временно).

🍿 Чистая архитектура во фронтенде (https://www.youtube.com/watch?v=h4WQRqNjmX0)

Доклад про то, как чистую архитектуру приложений (которую описал Роберт Мартин) можно применять во фронтенде. Как код можно разбить на три слоя (domain, application и interface adapters) на примере онлайн магазина печенек. У Александра Беспоясова в блоге есть пост по этому докладу, много разных интересных тем затрагивается.

🍿 Time, Numbers, Text (https://www.youtube.com/watch?v=TFBCcNFEmDE)

Бодрый доклад про внутреннее устройство и проблемы при работе со временем, числами и текстом. Про таймзоны (time zones) и часовые пояса (time offsets). Про 0.1 + 0.2 = 0.30......04 и стандарт чисел в языках программированиях. Про суррогатные пары (в канале я уже писал когда-то пост про устройство emoji amorgunov/24), про UTF-8 и UTF-16 и всякие системы писем.
patch-package

Предыстория. Решил я обновить зависимости в своем блоге, накопилось около 200х проблем в безопасности (npm audit), да и думал дел на полчаса максимум, это же просто блог. Почти базовая конфигурация вебпака, минималистичный движок для блога на javascript (11ty), да и собственно все. В очередной раз, как же я ошибался.

Брейкинг ченджи в новых версиях, их несовместимость друг с другом и прочее затянулось на несколько часов исправлений. Одна из проблем была в том, что основной пакет для генерации блога обновил версию шаблонизатора liquid на пару мажорных версий, и это сломало рендеринг всех страниц с маркдаун вставками. На гитхабе по теме даже есть ишьюс, в котором есть обсуждения и причина, почему теперь работает именно так, но нет решения. Переходить на другой шаблонизатор долго, откатывать версию 11ty на прошлую или делать форк не хочется. Что же делать?

На помощь приходит библиотека patch-package, которая позволяет сделать правки в node modules, сохранить их в виде diff-файла и накатить после установки зависимостей.

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

На рабочем проекте мы тоже используем эту библиотеку и подход, если есть необходимость подправить код внешнего модуля под собственный проект (или быстро запатчить баг в новой версии и выкатить хотфикс).
Quokka - JS playground

Про Quokka-у я хотел написать еще пару лет назад, но все никак не доходили руки. Quokka - это песочница для JS и TS-файлов, которая интегрируется в IDE. Она в реальном времени выполняет код и сразу показывает результат внутри редактора кода. На мой взгляд это обязательный плагин для фронтенд разработчика.

Я использую его для кейсов, когда нужно написать или пошагово подебажить какой-нибудь код на различных тестовых данных. Это может быть какая-то алгоритмическая функция или функция преобразования данных. Интерактивность и видимость результата очень круто подходит при решении задач с leetcode или codewars.

У меня пару лет была куплена PRO версия, сейчас использую бесплатную. В бесплатном режиме активны только несколько фич (просмотр значений переменных и покрытие строк кода, которые были вызваны), но сейчас мне их вполне хватает.
Mermaid - строим диаграммы

Раньше я использовал https://app.diagrams.net (бывший draw.io) для построения всего подряд, но соединять стрелочками все блоки и выравнивать их относительно друг друга, порой довольно нудное занятие и хочется более прикладных инструментов.

Около полугода назад нашел интересную библиотеку, mermaid. Она позволяет с помощью простых текстовых шаблонов описывать самые разные диаграммы: сиквенс, ганта, entity-relationship схемы и т.д.

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

Бонус для тех, кто хранит часть документации рядом с проектом в gitlab или github. Они поддерживают эти диаграммы, и их можно отображать в ваших markdown файлах (блогпосты от git-платформ https://docs.gitlab.com/ee/user/markdown.html#mermaid и https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/).
ReactNative for Web

Предыстория. Сейчас я думаю ни для кого не секрет, что приложения российских компаний, которые попадают под санкции, могут удалять из сторов. Для андроида это не так критично, так как можно устанавливать приложения из внешних источников, но вот в iOS нет как-либо способов установки без AppStore (если есть, расскажите в комментариях).

А если у компании на приложение завязан весь бизнес-процесс, то исключение из сторов почти равносильно остановки бизнеса. Понятно, что у пользователей с установленными приложениями ничего не случился (хотя в теории Apple может удалить и их) и компания не умрет в этот же день, но последствия будут катастрофичными.

В Самокате мы тоже подготовили веб-версию как запасной вариант, но об этом как-нибудь в другой раз, так как эта история никак не связана с основным приложением на ReactNative.

...

Какое есть решение? По заголовку я думаю вы все поняли - запустить приложение в вебе.

Что из себя представляет React Native? Это в первую очередь библиотека компонентов с нативной реализацией UI (конечно здесь можно поспорить насчет первой очереди, но не суть). Если написать реализацию компонентов и поддержать API RN в вебе, это позволит запускаться в браузере. Реализация уже написана и завернута в библиотеку react-native-web.

...

Как должно быть устроено такое приложение? Это монорепа с общими модулями и отдельными подпроектами под каждую из платформ (пример: https://github.com/brunolemos/react-native-web-monorepo).

В вебпаке (через который собирается веб-версия) все импорты react-native заменяются на react-native-web, нативные зависимости заменяются стабами, а если необходимы кастомные представления для веба, то создаются отдельные файлы компонентов с префиксом *.web.ts (аналогично для *.ios.ts и *.android.ts). И для каждой платформы создается отдельная точка входа (вебпак работает с точкой входа для веба, бандлер metro для мобильных девайсов).

Писать на react-native-web проект, для которого не нужно мобильное приложение, точно не нужно. А вот если нужно и приложение, и веб версия, которые аналогичны по функциональности, то советую иметь в виду это решение. Мы в команде думали над общими модулями, которые можно использовать и в мобильном, и в веб приложениях. А навигацию, глобальный стор и другие обвязки сделать уже отдельно под каждую платформу, так как полностью изоморфное приложение по ощущениям будет намного сложнее развивать и поддерживать. Но пока дальше обсуждений ничего не зашло.

Кстати, веб-версия Яндекс Лавки (наш прямой конкурент) написана на react-native-web и это легко проверить. В девтулзах можно увидеть RN-специфичные компоненты, как Text или View (спасибо @voloshinskii за наводку).

Если интересно больше узнать по теме, то можете послушать небольшой доклад: https://youtu.be/GIMs2bZ0yww?t=2655
Всем привет, друзья. Давно ничего не постил, уже почти пол года, надо это исправлять.

Для тех, кто оказался в канале случайно или давно подписался, и уже не помнит, а что здесь делает, пару слов о себе, и о чем пишу. Я сейчас работаю техлидом в Самокате и наставником на курсе фронтенд разработчика в Практикуме. Писал и буду писать про фронтенд разработку и смежные области. Мой хороший приятель напомнил, что мне еще нужно дописать про 2 принципа SOLID (о трех других я уже писал ранее в канале: Open-Closed Principle #76, Single Responsibility Principle #74 и Dependency Inversion Principle #72), о них тоже расскажу.

В Самокате последние пару месяцев я переключился на разработку проекта с нуля - веб версии Самоката для пользователей. Уж очень соскучился по вебу, не удержался от предложения принять участие в этой движухе. Но про React Native тоже периодически планирую писать.

Впервые на реальном проекте попробовал redux-toolkit (и rtk-query). После MobX-а, где данные живут отдельно от вьюх в observable-обертках, пришлось пару дней перепривыкать, но сейчас использовать одно удовольствие. Как-нибудь расскажу более подробно, что понравилось, а где ещё остаются вопросики, но в целом уже не вижу причин, когда целесообразно использовать редакс без rtk. Я пол года назад хранил редакс #99, беру свои слова назад (на самом деле просто набрасывал).

Попробовали использовать feature-sliced методологию для структурирования файлов и модулей в проекте. Есть много краевых случаев, неполная документация, но в общем полет отличный. На всех проектах в Самокате сейчас идёт внедрение, чтобы получить единую структуру в каждом репозитории.

Ещё решили написать свой маленький UIKit - песочница для экспериментов (в качестве сборщика решил попробовать vite, который мне советовали в одном из постов в комментариях). И чтобы было удобно разрабатываться, используем монорепу. С витом (читается именно так) проблем не было, с монорепой - тоже.

Больше всего вопросов вызывает ещё одно решение, которое я пробую впервые на боевых проектах - NextJs (фреймворк для разработки приложений на React). Несмотря на активную кор команду, комьюнити, много лет разработки и многих вещей, доступных из коробки, мы одновременно получаем кучу небольших проблем и странных на мой взгляд паттернов. Мы не так долго успели поработать с ним, но уже сейчас можно доклад делать об особенностях и проблемах.

В общем обо всем этом и планирую рассказывать, за последнее время много всего накопилось.