In the Lab: Crafting the No-Code Editor POC [ukr]
Приєднуйтесь до нас, щоб ексклюзивно познайомитися з нашим No-Code Editor. Я розповім вам про його дизайн, архітектуру та рішення, які ми прийняли, але не хвилюйтеся, я збережу технічний жаргон доступним і легким для розуміння. Дізнайтеся, як структурований наш проєкт і які бібліотеки ми використовуємо. Це перепустка за лаштунки нашого стартапу, все просто!
Транскрипція доповіді
Добрий день, всіх вас вітаю! Сподіваюся, що ви чуєте мене. Трошки нервую за своєю першою доповіддю, але сподіваюся, що вона буде цікавою для вас. І отже, трошки про мене. Зараз я працюю в компанії BlinkIn.io. Загалом, у мене є близько 10 років цікавого досвіду, і, якщо хтось помітив, знизу є така іконочка. Якщо ви знаєте, вітаю вас, ви вже старенький. Це іконка Dreamweaver, перший, мабуть, UI-редактор коду, створений ще в 1997 році. А тепер трошки із мого досвіду: в 2007 році я створив свій перший сайт за допомогою цього редактора - HTML, CSS і щось на PHP. Загалом я люблю динамічні середовища, де все швидко змінюється і розвивається, і де є максимальна гнучкість для моїх рішень та ідей, а також для ідей моїх колег.
Ще трошки про мою роботу: я працював 7 років, з яких близько 3 в компанії BlinkIn. І навіть є казуси, коли на співбесіді мені говорили, що все класно, ти цікавий чоловік, але у тебе такий покерфейс, і ти здаєшся стриманим. Це правда, після трьох років роботи я отримав цей покерфейс. Щодо моїх основних інструментів, які я використовую зараз - це React, TypeScript і Node. Я вже працюю з React близько 5-6 років. Трошки про BlinkIn: мета - поліпшити customer experience в усьому світі через два напрямки, B2B і B2C. Зараз ми орієнтуємося на B2B.
Я хочу розповісти про наш продукт - BlinkIn. Це інструмент, який надає AI-асистент для кращого сприйняття користувачем інструкцій щодо встановлення придбаного товару. Наш NoCode редактор є ядром нашої екосистеми, де бізнес може створювати гайди, а AI автоматизує цей процес, генеруючи гайди автоматично. Ми також маємо плани для розвитку B2C, де користувачі матимуть можливість створювати цікаві гайди. Це вже інша сторона нашого продукту.
І тепер я хочу розповісти про історію нашого стартапу: як ми починали, до чого прийшли, які рішення приймали і які бібліотеки використовували. Ми завжди фокусувалися на time market, працюючи на початковому рівні, не конкуруючи з великими продуктами, і розвивали наше спрямування протягом усього часу. Ми розпочинали з аналізу обов'язків, розглядаючи функціональні та нон-функціональні вимоги. Ми вивчали досвід інших компаній, таких як Miro та Figma, досліджуючи їхні рішення для колаборації та інші аспекти. Просто шукали цікаві статті.
Малювали схеми, прототипи, взаємодії компонент у системі. Ну, і максимально намагались підготуватись до цього. Далі, коли у нас був список, такий основний базовий фіч, який необхідно буде реалізувати, ми його, тобто, склали і вже почався процес ресерчу. От, якщо ви можете побачити, це прев'ю, це реалтайм, це історія, це drag-and-drop resize з Uavio. Якісь текстові імедж-редактори, там налаштування теми, multi-selection. Так, і тут і чат GPT був, але це вже не для цієї конференції. Сервіси такі основні, які ми обрали, це Azure. Чому? Тому що це просто і швидко. Я буквально перший раз залогінувшись в Azure, я за 3-4 години зміг створити веб-аплікейшн статичний, налаштувати pipeline з усіма чеками і задеплоїти, маючи, тобто, вже фінальне посилання для того, аби подивитися результати.
Плюс ми випробували Azure на інших продуктах, тому вирішили просто таки йти з ним. Але залишатись таким платформ-агностик і мати потім можливість перейти до інших. Також, нам потрібно Simple Sign-On, ми обрали Auth0. І вирішили довго не гратися за тип Postgres. Тому що з однієї сторони нам потрібно було JSON-B Storage, а з іншої сторони мати реляційні таблиці. Це там довкола редактори ще й інші фічі, такі як форма, інтеграція, юзера, тобто статистика. І не буду просто вдаватися в подробиці. І ми перейшли до наступного кроку. У нас був цілий місяць. Нам Solutions Tech сказав так ось. Ніякого MVC. Нічого. Ви не починаєте кодити якісь фінальні рішення. Просто відкриваєте у себе локально умовний сандбокс і починаєте шукати рішення для кожної з... По всім цим реквайерам там, так? І просто грайте з цим. Грайте, шукайте підводні киміння. Розумієте, чи це рішення підходить, чи це рішення підходить, так? І по факту ми просто отримали, так, щось таке страшненьке. Але ми вже знали. Які бібліотеки використати, де нам писати кастомні рішення. Просто як йти далі, так?
Але аби це все занотувати, ми створювали Architectural Decision Record. Тобто, в звичайному проєкті є, мабуть, у кожного документація. Але не в звичайному, а, скоріш за все, це винятки. На передній конференції по архітектурі з усією аудиторією менше 10 людей підняли руки і сказали, що так, у них є нормальна, актуальна документація. Тому ADR — це саме та річ, на яку ви витрачаєте дуже мало часу, але нотуєте основні речі. По кожній, наприклад, чому ми обрали Monorepo, а який саме сервіс для Monorepo ми обрали, роздивлялись. І трошечки контексту, так? Потім ви відкриваєте просто статус Open, передаєте це архітекту або СТО, або будь-хто, хто керує технічними процесами. І далі просто йде якась мінімальна комунікація з коментарями і закривається зі статусом Done. Тому це дійсно важлива штука. Потім також це скоротить час онбордінгу. Людина може прийти подивитись, як це влаштовано, чому саме ви обрали це, бо їй буде цікаво. Ну і швидке конвертінг продукту. А тепер я вам покажу трошечки тему, що це за продукт. Ну такі основні речі, як він працює. Аби ви мали розуміння, що тут можна робити. Якийсь зум, завантажувати елементи. Не знаю, з локальної папки там картинки завантажувати, одразу перетворювати її в компоненти. Все редагування онлайн, чи картинок, чи тексту. Шрифти міняти. Ну і купа можливостей.
Хтось навіть може виявити схожість з Amiro. Основні базові можливості. Там їх багато, але на все часу не вистачить. Так. І що взагалі, з чого побудований наш аплікейшн. Тобто backend це ExpressJS TypeScript. Frontend це MonoRepo, це TurboRepo. React також ми використовуємо. І ми дуже щасливі. Я коли перейшов просто з Create React App на нього, я не маю жодних проблем. Все працює швидко, без проблем. Налаштовується швидко, мігрується швидко. Взагалі задоволений. Також ми використовуємо Husky. Просто аби трошки зменшити час pipeline'ів. І локально проводити всі чеки. Там тест, лінк і інше. Чому саме MonoRepo? Тобто є у нас редактор. Перше, де ви створюєте оці гайди. Друге, у нас є Houston. Це як інша платформа на іншому домені розміщена. Де кінцевий користувач може передивлятись це. У нас є предактор. Ви хочете подивитись перед тим, як опублікувати цю версію. Ну і плюс є також embedding-віджет, як той же YouTube. Ви можете десь інтегрувати у себе на веб-сайті. І можливо ще щось виникне. Тому тут не важко здогадати, що у нас є багато реалізабельних компонент.
Таких собі віджитів. І нам необхідно було щось таке як підійти до цього. Звісно, можна було, не знаю, зробити NPM-пакет. Але кожен розуміє, що це багато часу на публікацію, на релізи. На оновлення версій в інших пакетах. І це дуже незручно, окрім патчів для стартапів. Тому ми обрали MonoRepo. Це швидко, класно. Також був створений ADR, чому саме TurboRepo. І скажу так, на сьогоднішній день, мабуть, це найнормальніший варіант. Він підтримується компанією. Всі чудово знають, хто це. І всі знають, що таке Next.js. Тому ходімо далі. Трошечки про структуру. Я не є адептом Featured Slides Design. Але основні підходи ми використовуємо. Є такі базові, як воно розподілено. Є базові рівні. Це АПП, де знаходяться ваші провайдери. Наприклад, TeamProvider, там, AusProvider. Є сторінки. Так. Є віджити. Віджит — це така річ, яка може поєднувати в собі якісь фічі. І вона може інтегруватися як в пейджу, так і, в принципі, окремо. Це ось той User Interaction, який відповідає за якісь Business Value.
Наприклад, не знаю, створити там відео компоненту. Це, по суті, Business Entities. Але саме в Redactor'і ми використовували це як просто розбити. Наприклад, є нода, є компонента всередині ноди. Є форма, є integration. Ну, все розбилося по слоям. І є Share. Це ось... Тут є вказівка, що ми використовуємо Atomic Design. Там, наприклад, є Shared UI. Є там батони, є якісь лояути. Звісно ж, все розкладено на атоми, молекули, організми. Я рекомендую. Знайомитись з цими двома підходами. Тому що вони дуже спрощують масштабування. Причому, коли у вас все дуже динамічно і дуже швидко. Це дуже зручно рухатись з цим. Ну, і приклад. Ось сегментація. Наприклад, є Entity компонента. Саме, як у нас побудовано. Є API, який там стосується цієї компоненти. Їхні мапери. Якісь ліби. Тобто, утіл звичайний. Моделі. Все, що стосується Store. Наприклад, там лежить слайс для зв'язку. Це відповідає за Store цих компонент. Є якісь саме кастомні хуки, які відносяться до компоненту. І UI презентація, яка відноситься до цього всього. Трошечки про ієрархію. Дуже так поверхнево. Тобто, є там Editor, Page. У неї може бути там якісь віджити інтегровані. Вона може використовувати компоненти з Entities. UI. Там також може використовуватися якісь плагіни. Наприклад, у нас це текстовий редактор або ImageProp плагін. На нижчому рівні це, звичайно, якісь там Shared конфіги. Бібліотеки. API-сервіс. EventBus. Drag&Drop менеджер-сервіс. Або, звичайно, там UI.
Як ми організували комунікацію між Back-End та Front-End та їх взаємодію? Все, насправді, дуже просто. Так, довго ми думали, але прийшли до простого рішення. Користувач, звичайний користувач, в редакторі робить, не знаю, за умовною секунду чотири зміни. Асинхронні, так? Спочатку ми їх збираємо в базі. Збираємо бандл PendingChanges. Потім паралельно оновлюємо, ну, синхронно оновлюємо сам Store, наприклад, компонент. І протягом цих 500 мс, вибачте, ми збираємо ці бандли. Потім просто пакуємо їх в два об'єкти. Є Update для оновлення та Remove для видалення ентіті.
І відсилаємо на API. Вони виходять дуже локалізовані та маленькі. І працює це дуже швидко. Далі йде менеджмент. У нас дві частини. Перша частина - це Xustent. Обожнюю його за модульність і простоту. Пригадуючи Redux, мені просто захочеться плакати. Пару прикладів, так? Це Components, Media, Fonts, якісь системи типу, чи можна зараз перетягувати мишкою елементи, чи не можна там ноди. Друга частина - це React Query. Також використовуємо його, є інтеграції, є форми. Є якась бібліотека іконок, яку потрібно зафетчити. React Query дуже класний, бо все вміщується в декілька рядків коду.
І друге - це кешування плюс автоматична інвалідація кешу. Можливість десь щось зафетчити, перефетчити, оновити цей кеш в будь-який момент. Воно виглядає більш гармонійно. І, звісно ж, за допомогою цього механізму, воно зменшує навантаження на ваш сервер. Ось приклад щодо шрифтів. Отже, маю перший Use Queries, Use Fonts. Потім я використовую їх десь у компоненті. У мене вже є автоматично там такі речі, як Loading State. Я не повинен там створювати ще купу якихось стейтів, як ви, наприклад, робили окремо властивість Loading, і уявіть, що у вас там є 100 entities. Потім є мутація.
Мутація типу Post, Update, Delete. І цікаво, що, якщо у вас є мутація, зазвичай ви хочете після цієї мутації оновити стейт. У моєму випадку я хочу оновити список шрифтів. І як бачите, я можу просто зробити інвалідацію кешу за ключем. Це Query, Key. Я задаю його під кожний Query свій власний. Там можна навіть передавати цілі об'єкти, як гідри. Не буду глибоко вдаватися в це, оскільки наступна презентація саме про React Query. Далі у нас Blue UAD. Це як фундаментальний атрибут. Це важливо, бо... Перший інтерактив, загалом ідентифікація всіх об'єктів, воно має відбуватися на фронтенді. І як це робити? Можна використовувати таймстемпи як ID-шніки, можна робити якісь інкременти, але це все досить дивно. І коли є рішення, яке вже пройшло часом, просто використовуйте в даному випадку цей UAD. Він завжди надає випадкові ID-шніки, і проблем з колізіями ніколи не виникає.
Наступна дуже цікава річ - це текстовий редактор. Перший раз, коли я задумався про онлайн редагування тексту, звісно ж, виникли питання. Ground.js, наприклад, була класною бібліотекою, якою я користувався у попередньому проєкті, і все, я прибіг. Але прибіг, зупинився, і виявилося, що вона депрекейтиться. Однак пізніше я знайшов посилання на, скажімо так, продовження цієї бібліотеки. Це Lexical, від Facebook. Це не просто бібліотека, але Framework. Там ви на такому низькому рівні можете налаштувати ваші власні кастомні правила. Будь-що, що захочете. Тобто, і слід розуміти, що, наприклад, це їхній демо-сендбокс.А UI, яку вони показали, вона не надається з коробки. Ви самі створюєте ці речі. Тобто, вона дуже неспорівнева. Але, з іншої сторони, вона надає вам максимальні фоксибіліті в побудуванні свого текстового редактора.
Наступна річ, яка, можливо, трошки специфічна для нашого редактора, це React Flow для побудови Node-based редакторів. Тобто, ми використали його для створення архітектури linked list. У нас є, наприклад, один крок від цього кроку. Людина може перейти або на цей крок, або на інший, в залежності від умов. Таким чином, немає чіткого списку для всіх цих вузлів. Є завжди батьківський вузол і завжди наступний вузол. І таким чином ми будуємо Flow.
Це CRDT. У нас є... Така річ... Тобто, є, наприклад, компанія, яка хоче створювати гайди. Вони запрошують 10 осіб до свого аккаунту, які будуть створювати ці гайди. Там може бути дизайнер, менеджер, який контролює все, копірайтер. Кожен відповідає за свою частину роботи. І, звісно, треба синхронізувати всі зміни між цими людьми, якщо вони працюють онлайн. Кожен працює в Figma чи Miro, і бачить, як це все працює на практиці. І саме як реалізувати цю колаборацію так, щоб у людей не було головної болі, це ось CRDT підхід. Conflict-free replicated data types. Так, ну і нащо нам це знати. Є... PageJS. Багато часу витрачено на дослідження того, що використовувати та як використовувати. Я знайшов реалізацію CRDT в JavaScript у цій бібліотеці. Вона використовує такий підхід. Є якісь дані. Дві людини завантажують їх у свій редактор. Кожного разу у кожного є своя репліка. І одна людина робить інсерт букви "і", інша - "о". Ці зміни перетворюються в структуру саме PageJS. Він підтримує це, об'єднує всі конфлікти.
Під капотом використовується алгоритм LMPort timestamp, який широко використовується в розподілених системах. Не буду деталізувати, але є посилання на це. Вважаю, що ви знайдете всю необхідну інформацію самі. І ось приклад того, як це може виглядати. Ми створили новий документ, мапу в цьому документі, підписались на всі зміни. І коли приходить зміна, ми реагуємо на це. Можна налаштувати провайдер для цього, використовуючи, наприклад, peer-to-peer провайдер через WebRTC або WebSocket. Може синхронізуватись навіть із MongoDB на сервері. Все, що потрібно. І це рішення вже готове та легко використовується, і я не знаю, чи є альтернатива.
Далі щодо іншої болючої теми... Наприклад, позиціювання. Отже, ви вибрали якусь компоненту, хочете показати тулбар. Якщо цей тулбар не вміщується ліворуч чи праворуч, він перекриває інший елемент. Це викликає купу конфліктів. На початку я думав, як це все реалізувати. Кожен раз мені приходили різні сценарії з цікавими випадками, і я казав, блін, ось ще одна проблема. Я такий втомився дивитися на це. Давайте вже закінчимо це. Тоді я знайшов Floating UI. Ось простий приклад використання цієї бібліотеки. Один із сотні прикладів. Це може дозволити вам інтегрувати свої middleware, використовувати вже готові, наприклад, як цей флот. І там, де вказано, де можна розмістити мій тулбар. Яка стратегія розміщення? Яке є дефолтне розміщення, якщо всі чотири підходять? Якщо щось піде не так, куди перенести? Який офсет для мого якоря, скажімо, 15 пікселів? Як контролювати стан, тобто відкрито чи ні, або якщо не відкрито, то що змінити? Дійсно, просто відкривайте документацію, там все детально описано. Рекомендую ознайомитися з цим. Тут буде посилання на їхній веб-сайт.
Ще одна болюча проблема, яку ми мали, - це можливість переміщення, зміни розміру, обертання компоненту, прив'язка до границь або автоматичне вирівнювання між компонентами, а також робота з мультивибором та зміна розміру для трьох компонентів одночасно. Тобто створення компоненту одразу. При цьому кожен компонент має своє власне відношення сторін, як ви сказали в малюнку, а у тексті такого немає. Спочатку я не міг знайти цю бібліотеку і використовував ReactDND, дописуючи для снепінгу купу своїх утиліт і тестів. Коли я зрозумів, що це вже виглядає досить непорядно, я знову провів дослідження і використав бібліотеку Movable. Вона просто охоплює всі випадки. Конфігурація зайняла менше сотні рядків коду, і це все. Я просто викинув увесь зайвий код, лишивши лише кілька тестів. Це дуже класна бібліотека. Наприклад, її впровадження. Тобто я можу відразу бачити, наприклад, 47 пікселів до наступного компонента. Я вже зробив снепінг посередині свого стеку, і все працює.
Так, були проблеми з порядком слайдів. Це стосується YGS. Це все, що я розповідав. Наступний момент стосується UserBuffer. Ви копілюєте щось, наприклад, виділили три компоненти та одну ноду. Ви хочете це скопіювати, але не зараз, а, наприклад, через 10 хвилин. Ви скопіювали один раз, щось зробили, виділили. Тут ви згадали, що це вже є в буфері. Тож ви хочете вставити тут. Спочатку ми робили простий in-memory storage, де просто копіювали ID-шники. Потім ми зрозуміли, що це дивно і неправильно, бо ми дуже залежимо від стану. І якщо щось там зміниться, ми можемо втратити консистентність. Тому ми створили свою структуру даних для цього кліпборду та буфера. Це тип даних-буферу, певний політ. Це компоненти, ноди. Ми повністю копіювали всі дані, перетворюючи зображення в BSC4, оскільки воно може бути видалене зі сховища. Використовували NavigatorClipboard для записування цього у вигляді тексту. Потім, під час події вставлення, ми парсили його і розуміли, чи це наш власний буфер чи сторонній. Сторонній оброблялися по-іншому.
І, нарешті, тестування. Ніколи раніше я не займався Test-Driven Development (TDD). Коли я зрозумів, що пишу фічу протягом, скажімо, 5 годин, а потім витрачаю годину на написання тестів до неї, і тести на те, що вже працює, і на поведінку, яка вже сформована, я зрозумів, що через день мене питають про баг. Я перевіряю, дійсно, баг. Я тоді переходжу до іншої задачі. Таким чином, я спочатку створював тестовий файл, описував поведінку і створював умови, при яких щось мало працювати. Наприклад, для Snapping, я описував, що якщо різниця менше 5 пікселів, позиція компоненти та наступної має змінитися на близько 3 пікселів вліво. Таким чином, я описував всі можливі тестові кейси. Потім я виконував реалізації. Це економило мені стільки часу, що я раджу переглянути свій підхід. І, можливо, саме TDD буде корисним для вашого продукту.
І так, дякую всім. Якщо у вас є бажання, приєднуйтесь до нашої розмови, поспілкуйтеся чи знайдіть відповіді на свої запитання. Або просто долучайтеся до контактів.